diff options
author | jbudorick <jbudorick@chromium.org> | 2015-08-25 14:36:59 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-08-25 21:38:00 +0000 |
commit | f091dcb5146ce071e183303bd316f06e92b6ee3e (patch) | |
tree | 352bd2a373ac3423cd3604c7ed3d9ef799cc6a9f | |
parent | d088d16429595fd6702e5e8542089f1b4fc34b7d (diff) | |
download | chromium_src-f091dcb5146ce071e183303bd316f06e92b6ee3e.zip chromium_src-f091dcb5146ce071e183303bd316f06e92b6ee3e.tar.gz chromium_src-f091dcb5146ce071e183303bd316f06e92b6ee3e.tar.bz2 |
[Android] Add gyp support for multidex. (RELAND)
Reland of https://codereview.chromium.org/1278573002
BUG=272790
TBR=thakis@chromium.org,mmenke@chromium.org
Review URL: https://codereview.chromium.org/1318513003
Cr-Commit-Position: refs/heads/master@{#345443}
25 files changed, 459 insertions, 16 deletions
diff --git a/base/BUILD.gn b/base/BUILD.gn index b6fb63e..a4e5a5c 100644 --- a/base/BUILD.gn +++ b/base/BUILD.gn @@ -1591,6 +1591,7 @@ if (is_android) { ] deps = [ + "//third_party/android_tools:android_support_multidex_java", "//third_party/jsr-305:jsr_305_javalib", ] @@ -1622,12 +1623,29 @@ if (is_android) { DEPRECATED_java_in_dir = "test/android/javatests/src" } + # TODO(jbudorick): Remove this once we roll to robolectric 3.0 and pull + # in the multidex shadow library. crbug.com/522043 + # GYP: //base.gyp:base_junit_test_support + java_library("base_junit_test_support") { + testonly = true + java_files = [ "test/android/junit/src/org/chromium/base/test/shadows/ShadowMultiDex.java" ] + deps = [ + "//third_party/android_tools:android_support_multidex_java", + "//third_party/robolectric:android-all-4.3_r2-robolectric-0", + "//third_party/robolectric:robolectric_java", + ] + } + # GYP: //base.gyp:base_junit_tests junit_binary("base_junit_tests") { - java_files = [ "android/junit/src/org/chromium/base/LogTest.java" ] + java_files = [ + "android/junit/src/org/chromium/base/BaseChromiumApplicationTest.java", + "android/junit/src/org/chromium/base/LogTest.java", + ] deps = [ ":base_java", ":base_java_test_support", + ":base_junit_test_support", ] } diff --git a/base/android/java/src/org/chromium/base/BaseChromiumApplication.java b/base/android/java/src/org/chromium/base/BaseChromiumApplication.java index d7c7b05..6d73c8fe 100644 --- a/base/android/java/src/org/chromium/base/BaseChromiumApplication.java +++ b/base/android/java/src/org/chromium/base/BaseChromiumApplication.java @@ -11,6 +11,8 @@ import android.os.Bundle; import android.view.KeyEvent; import android.view.Window; +import org.chromium.base.multidex.ChromiumMultiDex; + import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -20,6 +22,15 @@ import java.lang.reflect.Proxy; * Basic application functionality that should be shared among all browser applications. */ public class BaseChromiumApplication extends Application { + + private static final String TAG = "cr.base"; + + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + ChromiumMultiDex.install(this); + } + /** * Interface to be implemented by listeners for window focus events. */ diff --git a/base/android/java/src/org/chromium/base/annotations/MainDex.java b/base/android/java/src/org/chromium/base/annotations/MainDex.java new file mode 100644 index 0000000..0b35ade --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/MainDex.java @@ -0,0 +1,21 @@ +// 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.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation that signals that a class should be kept in the main dex file. + * + * This generally means it's used by renderer processes, which can't load secondary dexes + * on K and below. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface MainDex { +} diff --git a/base/android/java/src/org/chromium/base/multidex/ChromiumMultiDex.java b/base/android/java/src/org/chromium/base/multidex/ChromiumMultiDex.java new file mode 100644 index 0000000..2c28673 --- /dev/null +++ b/base/android/java/src/org/chromium/base/multidex/ChromiumMultiDex.java @@ -0,0 +1,58 @@ +// 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.base.multidex; + +import android.content.Context; +import android.os.Build; +import android.os.Process; +import android.support.multidex.MultiDex; + +import org.chromium.base.Log; +import org.chromium.base.VisibleForTesting; + +import java.lang.reflect.InvocationTargetException; + +/** + * Performs multidex installation for non-isolated processes. + */ +public class ChromiumMultiDex { + + private static final String TAG = "cr.base.multidex"; + + /** + * Installs secondary dexes if possible. + * + * Isolated processes (e.g. renderer processes) can't load secondary dex files on + * K and below, so we don't even try in that case. + * + * @param context The application context. + */ + @VisibleForTesting + public static void install(Context context) { + try { + // TODO(jbudorick): Back out this version check once support for K & below works. + // http://crbug.com/512357 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && processIsIsolated()) { + Log.i(TAG, "Skipping multidex installation: inside isolated process."); + } else { + MultiDex.install(context); + Log.i(TAG, "Completed multidex installation."); + } + } catch (NoSuchMethodException e) { + Log.wtf(TAG, "Failed multidex installation", e); + } catch (IllegalAccessException e) { + Log.wtf(TAG, "Failed multidex installation", e); + } catch (InvocationTargetException e) { + Log.wtf(TAG, "Failed multidex installation", e); + } + } + + // Calls Process.isIsolated, a private Android API. + private static boolean processIsIsolated() + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + return (boolean) Process.class.getMethod("isIsolated").invoke(null); + } + +} diff --git a/base/android/junit/src/org/chromium/base/BaseChromiumApplicationTest.java b/base/android/junit/src/org/chromium/base/BaseChromiumApplicationTest.java index d3441f7..239de67 100644 --- a/base/android/junit/src/org/chromium/base/BaseChromiumApplicationTest.java +++ b/base/android/junit/src/org/chromium/base/BaseChromiumApplicationTest.java @@ -13,6 +13,7 @@ import android.view.KeyEvent; import junit.framework.Assert; import org.chromium.base.BaseChromiumApplication.WindowFocusChangedListener; +import org.chromium.base.test.shadows.ShadowMultiDex; import org.chromium.testing.local.LocalRobolectricTestRunner; import org.junit.Test; import org.junit.runner.RunWith; @@ -25,10 +26,12 @@ import org.robolectric.util.ActivityController; /** Unit tests for {@link BaseChromiumApplication}. */ @RunWith(LocalRobolectricTestRunner.class) -@Config(manifest = Config.NONE, application = BaseChromiumApplication.class, - shadows = {BaseChromiumApplicationTest.TrackingShadowActivity.class}) +@Config(manifest = Config.NONE, + application = BaseChromiumApplication.class, + shadows = {BaseChromiumApplicationTest.TrackingShadowActivity.class, ShadowMultiDex.class}) public class BaseChromiumApplicationTest { + /** Shadow that tracks calls to onWindowFocusChanged and dispatchKeyEvent. */ @Implements(Activity.class) public static class TrackingShadowActivity extends ShadowActivity { private int mWindowFocusCalls; diff --git a/base/base.gyp b/base/base.gyp index a9038cd..d57ec6e 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -1450,6 +1450,7 @@ 'base_java_library_process_type', 'base_java_memory_pressure_level', 'base_native_libraries_gen', + '../third_party/android_tools/android_tools.gyp:android_support_multidex_javalib', '../third_party/jsr-305/jsr-305.gyp:jsr_305_javalib', ], 'includes': [ '../build/java.gypi' ], @@ -1507,12 +1508,30 @@ 'includes': [ '../build/java.gypi' ], }, { + # TODO(jbudorick): Remove this once we roll to robolectric 3.0 and pull + # in the multidex shadow library. crbug.com/522043 + # GN: //base:base_junit_test_support + 'target_name': 'base_junit_test_support', + 'type': 'none', + 'dependencies': [ + '../testing/android/junit/junit_test.gyp:junit_test_support', + '../third_party/android_tools/android_tools.gyp:android_support_multidex_javalib', + ], + 'variables': { + 'src_paths': [ + '../base/test/android/junit/', + ], + }, + 'includes': [ '../build/host_jar.gypi' ] + }, + { # GN: //base:base_junit_tests 'target_name': 'base_junit_tests', 'type': 'none', 'dependencies': [ 'base_java', 'base_java_test_support', + 'base_junit_test_support', '../testing/android/junit/junit_test.gyp:junit_test_support', ], 'variables': { diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseInstrumentationTestRunner.java b/base/test/android/javatests/src/org/chromium/base/test/BaseInstrumentationTestRunner.java index 35fe9b9..0eab231 100644 --- a/base/test/android/javatests/src/org/chromium/base/test/BaseInstrumentationTestRunner.java +++ b/base/test/android/javatests/src/org/chromium/base/test/BaseInstrumentationTestRunner.java @@ -8,6 +8,7 @@ import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Build; +import android.os.Bundle; import android.test.AndroidTestRunner; import android.test.InstrumentationTestRunner; import android.text.TextUtils; @@ -17,6 +18,7 @@ import junit.framework.TestResult; import org.chromium.base.Log; import org.chromium.base.SysUtils; +import org.chromium.base.multidex.ChromiumMultiDex; import org.chromium.base.test.BaseTestResult.SkipCheck; import org.chromium.base.test.util.MinAndroidSdkLevel; import org.chromium.base.test.util.Restriction; @@ -32,6 +34,12 @@ public class BaseInstrumentationTestRunner extends InstrumentationTestRunner { private static final String TAG = "cr.base.test"; @Override + public void onCreate(Bundle arguments) { + ChromiumMultiDex.install(getTargetContext()); + super.onCreate(arguments); + } + + @Override protected AndroidTestRunner getAndroidTestRunner() { AndroidTestRunner runner = new AndroidTestRunner() { @Override diff --git a/base/test/android/junit/src/org/chromium/base/test/shadows/ShadowMultiDex.java b/base/test/android/junit/src/org/chromium/base/test/shadows/ShadowMultiDex.java new file mode 100644 index 0000000..b3ff57e --- /dev/null +++ b/base/test/android/junit/src/org/chromium/base/test/shadows/ShadowMultiDex.java @@ -0,0 +1,21 @@ +// 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.base.test.shadows; + +import android.content.Context; +import android.support.multidex.MultiDex; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +/** Do-nothing shadow for {@link android.support.multidex.MultiDex}. */ +@Implements(MultiDex.class) +public class ShadowMultiDex { + + @Implementation + public static void install(Context context) { + } + +} diff --git a/build/android/ant/apk-package.xml b/build/android/ant/apk-package.xml index e8b76f7e..cb79560 100644 --- a/build/android/ant/apk-package.xml +++ b/build/android/ant/apk-package.xml @@ -54,6 +54,9 @@ <property name="resource.package.file.name" value="${RESOURCE_PACKAGED_APK_NAME}" /> <property name="intermediate.dex.file" location="${DEX_FILE_PATH}" /> + <condition property="multidex.enabled" value="true"> + <equals arg1="${MULTIDEX_ENABLED}" arg2="1"/> + </condition> <!-- Macro that enables passing a variable list of external jar files to ApkBuilder. --> @@ -70,13 +73,32 @@ hascode="${HAS_CODE}" previousBuildType="/" buildType="${build.is.packaging.debug}/${build.is.signing.debug}"> - <dex path="${intermediate.dex.file}"/> + <dex path="${intermediate.dex.file}" /> <nativefolder path="${native.libs.absolute.dir}" /> <extra-jars/> </apkbuilder> </sequential> </macrodef> + <macrodef name="multidex-package-helper"> + <element name="extra-jars" optional="yes" /> + <sequential> + <apkbuilder + outfolder="${out.absolute.dir}" + resourcefile="${resource.package.file.name}" + apkfilepath="${out.packaged.file}" + debugpackaging="${build.is.packaging.debug}" + debugsigning="${build.is.signing.debug}" + verbose="${verbose}" + hascode="false" + previousBuildType="/" + buildType="${build.is.packaging.debug}/${build.is.signing.debug}"> + <zip path="${intermediate.dex.file}" /> + <nativefolder path="${native.libs.absolute.dir}" /> + <extra-jars/> + </apkbuilder> + </sequential> + </macrodef> <!-- Packages the application. --> <target name="-package"> @@ -89,7 +111,14 @@ </package-helper> </then> <else> - <package-helper /> + <if condition="${multidex.enabled}"> + <then> + <multidex-package-helper /> + </then> + <else> + <package-helper /> + </else> + </if> </else> </if> </target> diff --git a/build/android/apkbuilder_action.gypi b/build/android/apkbuilder_action.gypi index 27807d8..e073e9b 100644 --- a/build/android/apkbuilder_action.gypi +++ b/build/android/apkbuilder_action.gypi @@ -56,6 +56,11 @@ '-DDEX_FILE_PATH=<(dex_path)', ] }], + ['enable_multidex == 1', { + 'action': [ + '-DMULTIDEX_ENABLED=1', + ] + }] ], 'action': [ 'python', '<(DEPTH)/build/android/gyp/ant.py', diff --git a/build/android/gyp/dex.py b/build/android/gyp/dex.py index c26d23a..3b4141f 100755 --- a/build/android/gyp/dex.py +++ b/build/android/gyp/dex.py @@ -4,21 +4,57 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import logging import optparse import os import sys +import tempfile +import zipfile from util import build_utils from util import md5_check -def DoDex(options, paths): +def DoMultiDex(options, paths): + main_dex_list = [] + main_dex_list_files = build_utils.ParseGypList(options.main_dex_list_paths) + for m in main_dex_list_files: + with open(m) as main_dex_list_file: + main_dex_list.extend(l for l in main_dex_list_file if l) + + with tempfile.NamedTemporaryFile(suffix='.txt') as combined_main_dex_list: + combined_main_dex_list.write('\n'.join(main_dex_list)) + combined_main_dex_list.flush() + + dex_args = [ + '--multi-dex', + '--minimal-main-dex', + '--main-dex-list=%s' % combined_main_dex_list.name + ] + + DoDex(options, paths, dex_args=dex_args) + + if options.dex_path.endswith('.zip'): + iz = zipfile.ZipFile(options.dex_path, 'r') + tmp_dex_path = '%s.tmp.zip' % options.dex_path + oz = zipfile.ZipFile(tmp_dex_path, 'w', zipfile.ZIP_DEFLATED) + for i in iz.namelist(): + if i.endswith('.dex'): + oz.writestr(i, iz.read(i)) + os.remove(options.dex_path) + os.rename(tmp_dex_path, options.dex_path) + + +def DoDex(options, paths, dex_args=None): dx_binary = os.path.join(options.android_sdk_tools, 'dx') # See http://crbug.com/272064 for context on --force-jumbo. dex_cmd = [dx_binary, '--dex', '--force-jumbo', '--output', options.dex_path] if options.no_locals != '0': dex_cmd.append('--no-locals') + if dex_args: + dex_cmd += dex_args + dex_cmd += paths record_path = '%s.md5.stamp' % options.dex_path @@ -54,9 +90,14 @@ def main(): 'is enabled.')) parser.add_option('--no-locals', help='Exclude locals list from the dex file.') + parser.add_option('--multi-dex', default=False, action='store_true', + help='Create multiple dex files.') parser.add_option('--inputs', help='A list of additional input paths.') parser.add_option('--excluded-paths', help='A list of paths to exclude from the dex file.') + parser.add_option('--main-dex-list-paths', + help='A list of paths containing a list of the classes to ' + 'include in the main dex.') options, paths = parser.parse_args(args) @@ -76,7 +117,14 @@ def main(): paths = [p for p in paths if not os.path.relpath(p, options.output_directory) in exclude_paths] - DoDex(options, paths) + if options.multi_dex and options.main_dex_list_paths: + DoMultiDex(options, paths) + else: + if options.multi_dex: + logging.warning('--multi-dex is unused without --main-dex-list-paths') + elif options.main_dex_list_paths: + logging.warning('--main-dex-list-paths is unused without --multi-dex') + DoDex(options, paths) if options.depfile: build_utils.WriteDepfile( diff --git a/build/android/gyp/main_dex_list.py b/build/android/gyp/main_dex_list.py new file mode 100755 index 0000000..6442001 --- /dev/null +++ b/build/android/gyp/main_dex_list.py @@ -0,0 +1,84 @@ +#!/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. + +import argparse +import os +import sys +import tempfile + +from util import build_utils + +sys.path.append(os.path.abspath(os.path.join( + os.path.dirname(__file__), os.pardir))) +from pylib import constants + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--android-sdk-tools', required=True, + help='Android sdk build tools directory.') + parser.add_argument('--main-dex-rules-path', action='append', default=[], + dest='main_dex_rules_paths', + help='A file containing a list of proguard rules to use ' + 'in determining the class to include in the ' + 'main dex.') + parser.add_argument('--main-dex-list-path', required=True, + help='The main dex list file to generate.') + parser.add_argument('paths', nargs='+', + help='JARs for which a main dex list should be ' + 'generated.') + + args = parser.parse_args() + + with open(args.main_dex_list_path, 'w') as main_dex_list_file: + + shrinked_android_jar = os.path.abspath( + os.path.join(args.android_sdk_tools, 'lib', 'shrinkedAndroid.jar')) + dx_jar = os.path.abspath( + os.path.join(args.android_sdk_tools, 'lib', 'dx.jar')) + paths_arg = ':'.join(args.paths) + rules_file = os.path.abspath( + os.path.join(args.android_sdk_tools, 'mainDexClasses.rules')) + + with tempfile.NamedTemporaryFile(suffix='.jar') as temp_jar: + proguard_cmd = [ + constants.PROGUARD_SCRIPT_PATH, + '-forceprocessing', + '-dontwarn', '-dontoptimize', '-dontobfuscate', '-dontpreverify', + '-injars', paths_arg, + '-outjars', temp_jar.name, + '-libraryjars', shrinked_android_jar, + '-include', rules_file, + ] + for m in args.main_dex_rules_paths: + proguard_cmd.extend(['-include', m]) + + main_dex_list = '' + try: + build_utils.CheckOutput(proguard_cmd) + + java_cmd = [ + 'java', '-cp', dx_jar, + 'com.android.multidex.MainDexListBuilder', + temp_jar.name, paths_arg + ] + main_dex_list = build_utils.CheckOutput(java_cmd) + except build_utils.CalledProcessError as e: + if 'output jar is empty' in e.output: + pass + elif "input doesn't contain any classes" in e.output: + pass + else: + raise + + main_dex_list_file.write(main_dex_list) + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) + diff --git a/build/android/main_dex_action.gypi b/build/android/main_dex_action.gypi new file mode 100644 index 0000000..06717dd --- /dev/null +++ b/build/android/main_dex_action.gypi @@ -0,0 +1,44 @@ +# 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. + +# This file is meant to be included into an action to provide a rule that +# generates a list of classes that must be kept in the main dex file. +# +# To use this, create a gyp target with the following form: +# { +# 'action_name': 'some name for the action' +# 'actions': [ +# 'variables': { +# 'jar_path': 'path to jar', +# 'output_path': 'output path' +# }, +# 'includes': [ 'relative/path/to/main_dex_action.gypi' ], +# ], +# }, +# + +{ + 'message': 'Generating main dex classes list for <(jar_path)', + 'variables': { + 'jar_path%': '', + 'output_path%': '', + 'main_dex_list_script': '<(DEPTH)/build/android/gyp/main_dex_list.py', + 'main_dex_rules_path': '<(DEPTH)/build/android/main_dex_classes.flags', + }, + 'inputs': [ + '<(jar_path)', + '<(main_dex_list_script)', + '<(main_dex_rules_path)', + ], + 'outputs': [ + '<(output_path)', + ], + 'action': [ + 'python', '<(main_dex_list_script)', + '--main-dex-list-path', '<(output_path)', + '--android-sdk-tools', '<(android_sdk_tools)', + '--main-dex-rules-path', '<(main_dex_rules_path)', + '<(jar_path)', + ] +} diff --git a/build/android/main_dex_classes.flags b/build/android/main_dex_classes.flags new file mode 100644 index 0000000..d9689b2 --- /dev/null +++ b/build/android/main_dex_classes.flags @@ -0,0 +1,3 @@ +-keep @**.MainDex class * { + *; +} diff --git a/build/android/pylib/constants/__init__.py b/build/android/pylib/constants/__init__.py index 4617119..43c0a6d 100644 --- a/build/android/pylib/constants/__init__.py +++ b/build/android/pylib/constants/__init__.py @@ -189,6 +189,9 @@ ANDROID_SDK_TOOLS = os.path.join(ANDROID_SDK_ROOT, ANDROID_NDK_ROOT = os.path.join(DIR_SOURCE_ROOT, 'third_party/android_tools/ndk') +PROGUARD_SCRIPT_PATH = os.path.join( + ANDROID_SDK_ROOT, 'tools', 'proguard', 'bin', 'proguard.sh') + EMULATOR_SDK_ROOT = os.environ.get('ANDROID_EMULATOR_SDK_ROOT', os.path.join(DIR_SOURCE_ROOT, 'android_emulator_sdk')) diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni index 3f14b6762..88276bf 100644 --- a/build/config/android/rules.gni +++ b/build/config/android/rules.gni @@ -940,6 +940,9 @@ template("java_library") { # proguard_preprocess: If true, proguard preprocessing will be run. This can # be used to remove unwanted parts of the library. # proguard_config: Path to the proguard config for preprocessing. +# supports_android: If true, Android targets (android_library, android_apk) +# may depend on this target. Note: if true, this target must only use the +# subset of Java available on Android. # # Example # java_prebuilt("foo_java") { diff --git a/build/java.gypi b/build/java.gypi index 614d50a..dc642b9 100644 --- a/build/java.gypi +++ b/build/java.gypi @@ -64,6 +64,7 @@ 'instr_stamp': '<(intermediate_dir)/instr.stamp', 'additional_input_paths': [], 'dex_path': '<(PRODUCT_DIR)/lib.java/<(_target_name).dex.jar', + 'main_dex_list_path': '<(intermediate_dir)/main_dex_list.txt', 'generated_src_dirs': ['>@(generated_R_dirs)'], 'generated_R_dirs': [], 'has_java_resources%': 0, @@ -113,6 +114,7 @@ 'variables': { 'input_jars_paths': ['<(jar_final_path)'], 'library_dexed_jars_paths': ['<(dex_path)'], + 'main_dex_list_paths': ['<(main_dex_list_path)'], }, }, }], @@ -309,6 +311,14 @@ ] }, { + 'action_name': 'main_dex_list_for_<(_target_name)', + 'variables': { + 'jar_path': '<(javac_jar_path)', + 'output_path': '<(main_dex_list_path)', + }, + 'includes': [ 'android/main_dex_action.gypi' ], + }, + { 'action_name': 'instr_jar_<(_target_name)', 'message': 'Instrumenting <(_target_name) jar', 'variables': { diff --git a/build/java_apk.gypi b/build/java_apk.gypi index 6dbb858..52902a0 100644 --- a/build/java_apk.gypi +++ b/build/java_apk.gypi @@ -70,11 +70,14 @@ 'variables': { 'tested_apk_obfuscated_jar_path%': '/', 'tested_apk_dex_path%': '/', + 'tested_apk_is_multidex%': 0, 'additional_input_paths': [], 'create_density_splits%': 0, 'language_splits': [], 'input_jars_paths': [], 'library_dexed_jars_paths': [], + 'main_dex_list_path': '<(intermediate_dir)/main_dex_list.txt', + 'main_dex_list_paths': ['<(main_dex_list_path)'], 'additional_src_dirs': [], 'generated_src_dirs': [], 'app_manifest_version_name%': '<(android_app_version_name)', @@ -131,7 +134,7 @@ 'jar_path': '<(PRODUCT_DIR)/lib.java/<(jar_name)', 'obfuscated_jar_path': '<(intermediate_dir)/obfuscated.jar', 'test_jar_path': '<(PRODUCT_DIR)/test.lib.java/<(apk_name).jar', - 'dex_path': '<(intermediate_dir)/classes.dex', + 'enable_multidex%': 0, 'emma_device_jar': '<(android_sdk_root)/tools/lib/emma_device.jar', 'android_manifest_path%': '<(java_in_dir)/AndroidManifest.xml', 'split_android_manifest_path': '<(intermediate_dir)/split-manifests/<(android_app_abi)/AndroidManifest.xml', @@ -160,6 +163,7 @@ 'unsigned_apk_path': '<(intermediate_dir)/<(apk_name)-unsigned.apk', 'unsigned_abi_split_apk_path': '<(intermediate_dir)/<(apk_name)-abi-<(android_app_abi)-unsigned.apk', 'create_abi_split%': 0, + 'enable_multidex%': 0, }, 'unsigned_apk_path': '<(unsigned_apk_path)', 'unsigned_abi_split_apk_path': '<(unsigned_abi_split_apk_path)', @@ -193,6 +197,11 @@ }, { 'managed_input_apk_path': '<(unsigned_apk_path)', }], + ['enable_multidex == 1', { + 'dex_path': '<(intermediate_dir)/classes.dex.zip', + }, { + 'dex_path': '<(intermediate_dir)/classes.dex', + }], ], }, 'native_lib_target%': '', @@ -213,6 +222,7 @@ 'native_lib_placeholder_stamp': '<(apk_package_native_libs_dir)/<(android_app_abi)/native_lib_placeholder.stamp', 'native_lib_placeholders': [], 'main_apk_name': '<(apk_name)', + 'dex_path': '<(dex_path)', 'enable_errorprone%': '0', 'errorprone_exe_path': '<(PRODUCT_DIR)/bin.java/chromium_errorprone', }, @@ -231,6 +241,7 @@ 'apk_output_jar_path': '<(jar_path)', 'tested_apk_obfuscated_jar_path': '<(obfuscated_jar_path)', 'tested_apk_dex_path': '<(dex_path)', + 'tested_apk_is_multidex': '<(enable_multidex)', }, }, 'conditions': [ @@ -752,8 +763,7 @@ ], }, ], - }, - ] + }], ], 'dependencies': [ '<(DEPTH)/tools/android/md5sum/md5sum.gyp:md5sum', @@ -885,6 +895,14 @@ ], }, { + 'action_name': 'main_dex_list_for_<(_target_name)', + 'variables': { + 'jar_path': '<(javac_jar_path)', + 'output_path': '<(main_dex_list_path)', + }, + 'includes': [ 'android/main_dex_action.gypi' ], + }, + { 'action_name': 'instr_jar_<(_target_name)', 'message': 'Instrumenting <(_target_name) jar', 'variables': { @@ -1005,14 +1023,40 @@ { 'action_name': 'dex_<(_target_name)', 'variables': { + 'dex_additional_options': [], 'dex_input_paths': [ - '>@(library_dexed_jars_paths)', '<(jar_path)', ], 'output_path': '<(dex_path)', 'proguard_enabled_input_path': '<(obfuscated_jar_path)', }, + 'conditions': [ + ['enable_multidex == 1', { + 'variables': { + 'dex_additional_options': [ + '--multi-dex', + '--main-dex-list-paths', '>@(main_dex_list_paths)', + ], + }, + 'inputs': [ + '>@(main_dex_list_paths)', + ], + }] + ], 'target_conditions': [ + ['enable_multidex == 1 or tested_apk_is_multidex == 1', { + 'variables': { + 'dex_input_paths': [ + '>@(input_jars_paths)', + ], + }, + }, { + 'variables': { + 'dex_input_paths': [ + '>@(library_dexed_jars_paths)', + ], + }, + }], ['emma_instrument != 0', { 'variables': { 'dex_no_locals': 1, diff --git a/build/secondary/third_party/android_tools/BUILD.gn b/build/secondary/third_party/android_tools/BUILD.gn index a6b3758..175409b 100644 --- a/build/secondary/third_party/android_tools/BUILD.gn +++ b/build/secondary/third_party/android_tools/BUILD.gn @@ -49,6 +49,11 @@ android_java_prebuilt("android_support_design_java") { jar_path = "$android_sdk_root/extras/android/support/design/libs/android-support-design.jar" } +java_prebuilt("android_support_multidex_java") { + supports_android = true + jar_path = "$android_sdk_root/extras/android/support/multidex/library/libs/android-support-multidex.jar" +} + android_java_prebuilt("android_support_v13_java") { jar_path = "$android_sdk_root/extras/android/support/v13/android-support-v13.jar" diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn index 67e7710..49e3598 100644 --- a/chrome/android/BUILD.gn +++ b/chrome/android/BUILD.gn @@ -474,6 +474,7 @@ junit_binary("chrome_junit_tests") { ":chrome_java", "//base:base_java", "//base:base_java_test_support", + "//base:base_junit_test_support", "//sync:sync_java_test_support", "//sync/android:sync_java", "//third_party/junit:hamcrest", diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/remote/AbstractMediaRouteControllerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/remote/AbstractMediaRouteControllerTest.java index 54522d8..d8809d2 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/media/remote/AbstractMediaRouteControllerTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/media/remote/AbstractMediaRouteControllerTest.java @@ -32,6 +32,7 @@ import android.support.v7.media.MediaRouter.RouteInfo; import org.chromium.base.ApplicationStatus; import org.chromium.base.BaseChromiumApplication; import org.chromium.base.CommandLine; +import org.chromium.base.test.shadows.ShadowMultiDex; import org.chromium.base.test.util.Feature; import org.chromium.chrome.browser.media.remote.MediaRouteController.MediaStateListener; import org.chromium.chrome.browser.media.remote.MediaRouteController.UiListener; @@ -51,8 +52,8 @@ import org.robolectric.internal.ReflectionHelpers; /** Tests for {@link AbstractMediaRouteController}. */ @RunWith(LocalRobolectricTestRunner.class) -@Config(manifest = Config.NONE, shadows = AbstractMediaRouteControllerTest.ShadowMediaRouter.class, - application = BaseChromiumApplication.class) +@Config(manifest = Config.NONE, application = BaseChromiumApplication.class, + shadows = {AbstractMediaRouteControllerTest.ShadowMediaRouter.class, ShadowMultiDex.class}) public class AbstractMediaRouteControllerTest { /** Reset the environment before each test. */ diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 9348234..68ed2e1 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -3045,6 +3045,7 @@ 'chrome_java', '../base/base.gyp:base', '../base/base.gyp:base_java_test_support', + '../base/base.gyp:base_junit_test_support', '../sync/sync.gyp:sync_java_test_support', '../testing/android/junit/junit_test.gyp:junit_test_support', ], diff --git a/net/android/BUILD.gn b/net/android/BUILD.gn index 86b9c86..c891da7 100644 --- a/net/android/BUILD.gn +++ b/net/android/BUILD.gn @@ -87,6 +87,7 @@ junit_binary("net_junit_tests") { ":net_java", "//base:base_java", "//base:base_java_test_support", + "//base:base_junit_test_support", "//third_party/junit:hamcrest", ] } diff --git a/net/android/junit/src/org/chromium/net/HttpNegotiateAuthenticatorTest.java b/net/android/junit/src/org/chromium/net/HttpNegotiateAuthenticatorTest.java index f6a97c0..7edc4e3 100644 --- a/net/android/junit/src/org/chromium/net/HttpNegotiateAuthenticatorTest.java +++ b/net/android/junit/src/org/chromium/net/HttpNegotiateAuthenticatorTest.java @@ -29,6 +29,7 @@ import android.os.Bundle; import android.os.Handler; import org.chromium.base.BaseChromiumApplication; +import org.chromium.base.test.shadows.ShadowMultiDex; import org.chromium.testing.local.LocalRobolectricTestRunner; import org.junit.Before; import org.junit.Test; @@ -45,9 +46,9 @@ import java.io.IOException; * Robolectric tests for HttpNegotiateAuthenticator */ @RunWith(LocalRobolectricTestRunner.class) -@Config(manifest = Config.NONE, - shadows = HttpNegotiateAuthenticatorTest.ExtendedShadowAccountManager.class, - application = BaseChromiumApplication.class) +@Config(manifest = Config.NONE, application = BaseChromiumApplication.class, + shadows = {HttpNegotiateAuthenticatorTest.ExtendedShadowAccountManager.class, + ShadowMultiDex.class}) public class HttpNegotiateAuthenticatorTest { // Since the account manager is an SDK singleton (it is fetched using AccountManager.get()) we // can't validate its method calls with Mockito, so do so using our shadow method. Since the diff --git a/net/net.gyp b/net/net.gyp index 66d8ae3..d4fe51b 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -1545,6 +1545,7 @@ 'net_java', '../base/base.gyp:base', '../base/base.gyp:base_java_test_support', + '../base/base.gyp:base_junit_test_support', '../testing/android/junit/junit_test.gyp:junit_test_support', ], 'variables': { |