Commit c748dfca authored by Tibor Goldschwendt's avatar Tibor Goldschwendt Committed by Commit Bot

Auto-generate Java that provide information about modules at runtime

The first piece of information is the native libraries a module depends
on. We will use this info in following CLs to auto-load the libraries
on first module access.

The Java is generated in the chrome_feature_module template via a new
module_desc_java template. Module_desc_java lives in
//components/module_installer since this component is using the Java and
has expectations about its format.

Modules that don't use module descriptors (e.g. they are packaged into
base such as tab_management in this CL) have to manually create a
module_desc_java to be able to use Module.java's loading goodies.

APKs won't automatically have module_desc_javas. In order to make the
Module.java API work in APKs (e.g. test APKs) we use stub descriptors.
This should be fine as APKs already load all native libraries at
startup.

See go/native-dfm-load-v2 for more context and details.

Also in this CL, moving native lib loading before impl initialization
so that the module is fully set up by the time module code is handed
control.

Bug: 870055
Change-Id: I8b6112bfc57b7d49698702b4fc12d2874b55fa12
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1873911
Commit-Queue: Tibor Goldschwendt <tiborg@chromium.org>
Commit-Queue: Christopher Grant <cjgrant@chromium.org>
Auto-Submit: Tibor Goldschwendt <tiborg@chromium.org>
Reviewed-by: default avatarChristopher Grant <cjgrant@chromium.org>
Reviewed-by: default avatarAndrew Grieve <agrieve@chromium.org>
Cr-Commit-Position: refs/heads/master@{#709165}
parent 5ef34b5a
...@@ -1351,6 +1351,7 @@ _ANDROID_SPECIFIC_PYDEPS_FILES = [ ...@@ -1351,6 +1351,7 @@ _ANDROID_SPECIFIC_PYDEPS_FILES = [
'build/android/test_wrapper/logdog_wrapper.pydeps', 'build/android/test_wrapper/logdog_wrapper.pydeps',
'build/protoc_java.pydeps', 'build/protoc_java.pydeps',
'chrome/android/features/create_stripped_java_factory.pydeps', 'chrome/android/features/create_stripped_java_factory.pydeps',
'components/module_installer/android/module_desc_java.pydeps',
'net/tools/testserver/testserver.pydeps', 'net/tools/testserver/testserver.pydeps',
'testing/scripts/run_android_wpt.pydeps', 'testing/scripts/run_android_wpt.pydeps',
'third_party/android_platform/development/scripts/stack.pydeps', 'third_party/android_platform/development/scripts/stack.pydeps',
......
...@@ -508,7 +508,10 @@ java_group("chrome_all_java") { ...@@ -508,7 +508,10 @@ java_group("chrome_all_java") {
} }
if (disable_tab_ui_dfm) { if (disable_tab_ui_dfm) {
deps += [ "//chrome/android/features/tab_ui:java" ] deps += [
"//chrome/android/features/tab_ui:java",
"//chrome/android/features/tab_ui:module_desc_java",
]
} }
if (dfmify_dev_ui) { if (dfmify_dev_ui) {
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import("//build/config/android/rules.gni") import("//build/config/android/rules.gni")
import("//chrome/android/features/tab_ui/buildflags.gni") import("//chrome/android/features/tab_ui/buildflags.gni")
import("//chrome/common/features.gni") import("//chrome/common/features.gni")
import("//components/module_installer/android/module_desc_java.gni")
java_strings_grd("java_strings_grd") { java_strings_grd("java_strings_grd") {
defines = chrome_grit_defines defines = chrome_grit_defines
...@@ -180,3 +181,7 @@ android_library("java") { ...@@ -180,3 +181,7 @@ android_library("java") {
"//ui/android:ui_java", "//ui/android:ui_java",
] ]
} }
module_desc_java("module_desc_java") {
module_name = "tab_management"
}
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import("//build/config/android/rules.gni") import("//build/config/android/rules.gni")
import("//build/config/locales.gni") import("//build/config/locales.gni")
import("//chrome/android/modules/chrome_feature_modules.gni") import("//chrome/android/modules/chrome_feature_modules.gni")
import("//components/module_installer/android/module_desc_java.gni")
# Instantiates a Chrome-specific app bundle module. # Instantiates a Chrome-specific app bundle module.
# #
...@@ -27,6 +28,55 @@ template("chrome_feature_module") { ...@@ -27,6 +28,55 @@ template("chrome_feature_module") {
_include_32_bit_webview = _include_32_bit_webview =
defined(invoker.include_32_bit_webview) && invoker.include_32_bit_webview defined(invoker.include_32_bit_webview) && invoker.include_32_bit_webview
_loadable_modules_32_bit = []
if (defined(_module_desc.loadable_modules_32_bit)) {
_loadable_modules_32_bit += _module_desc.loadable_modules_32_bit
}
_loadable_modules_64_bit = []
if (defined(_module_desc.loadable_modules_64_bit)) {
_loadable_modules_64_bit += _module_desc.loadable_modules_64_bit
}
_shared_libraries = []
if (use_native_partitions && defined(_module_desc.native_deps) &&
_module_desc.native_deps != []) {
_arch = ""
_toolchain = ""
_root_out_dir = root_out_dir
if (android_64bit_target_cpu && _is_monochrome_or_trichrome) {
if (_is_64_bit_browser) {
_arch = "_64"
} else {
_toolchain = "($android_secondary_abi_toolchain)"
_root_out_dir = get_label_info(":foo($android_secondary_abi_toolchain)",
"root_out_dir")
}
}
if (_is_monochrome_or_trichrome) {
_base_target_name = "libmonochrome${_arch}"
} else {
_base_target_name = "libchrome${_arch}"
}
_shared_libraries += [
"//chrome/android:${_base_target_name}_${_module_desc.name}${_toolchain}",
]
_native_library = "${_root_out_dir}/${_base_target_name}_partitions/lib${_module_desc.name}.so"
# Pass the correct library as both the 32 and 64-bit options. Underlying
# logic will choose from the correct variable, and supply a dummy library
# for the other architecture if required.
_loadable_modules_32_bit += [ _native_library ]
_loadable_modules_64_bit += [ _native_library ]
} else {
not_needed([ "_is_monochrome_or_trichrome" ])
}
module_desc_java("${target_name}__module_desc_java") {
module_name = _module_desc.name
shared_libraries = _shared_libraries
}
android_app_bundle_module(target_name) { android_app_bundle_module(target_name) {
forward_variables_from(invoker, forward_variables_from(invoker,
[ [
...@@ -40,59 +90,18 @@ template("chrome_feature_module") { ...@@ -40,59 +90,18 @@ template("chrome_feature_module") {
]) ])
android_manifest = _module_desc.android_manifest android_manifest = _module_desc.android_manifest
target_sdk_version = android_sdk_version target_sdk_version = android_sdk_version
deps = [] deps = [
":${target_name}__module_desc_java",
]
if (defined(_module_desc.java_deps)) { if (defined(_module_desc.java_deps)) {
deps += _module_desc.java_deps deps += _module_desc.java_deps
} }
# Don't embed more translations than required (http://crbug.com/932017). # Don't embed more translations than required (http://crbug.com/932017).
aapt_locale_whitelist = locales aapt_locale_whitelist = locales
proguard_enabled = !is_java_debug proguard_enabled = !is_java_debug
package_name = _module_desc.name package_name = _module_desc.name
_loadable_modules_32_bit = []
_loadable_modules_64_bit = []
if (defined(_module_desc.loadable_modules_32_bit)) {
_loadable_modules_32_bit += _module_desc.loadable_modules_32_bit
}
if (defined(_module_desc.loadable_modules_64_bit)) {
_loadable_modules_64_bit += _module_desc.loadable_modules_64_bit
}
if (use_native_partitions && defined(_module_desc.native_deps) &&
_module_desc.native_deps != []) {
_arch = ""
_toolchain = ""
_root_out_dir = root_out_dir
if (android_64bit_target_cpu && _is_monochrome_or_trichrome) {
if (_is_64_bit_browser) {
_arch = "_64"
} else {
_toolchain = "($android_secondary_abi_toolchain)"
_root_out_dir =
get_label_info(":foo($android_secondary_abi_toolchain)",
"root_out_dir")
}
}
if (_is_monochrome_or_trichrome) {
_base_target_name = "libmonochrome${_arch}"
} else {
_base_target_name = "libchrome${_arch}"
}
deps += [ "//chrome/android:${_base_target_name}_${_module_desc.name}${_toolchain}" ]
_native_library = "${_root_out_dir}/${_base_target_name}_partitions/lib${_module_desc.name}.so"
# Pass the correct library as both the 32 and 64-bit options. Underlying
# logic will choose from the correct variable, and supply a dummy library
# for the other architecture if required.
_loadable_modules_32_bit += [ _native_library ]
_loadable_modules_64_bit += [ _native_library ]
} else {
not_needed([ "_is_monochrome_or_trichrome" ])
}
# Specify native libraries and placeholders. # Specify native libraries and placeholders.
if (_loadable_modules_32_bit != [] || _loadable_modules_64_bit != []) { if (_loadable_modules_32_bit != [] || _loadable_modules_64_bit != []) {
# Decision logic: Assign decision variables: # Decision logic: Assign decision variables:
......
...@@ -8,11 +8,12 @@ import("//chrome/android/modules/buildflags.gni") ...@@ -8,11 +8,12 @@ import("//chrome/android/modules/buildflags.gni")
android_library("module_installer_java") { android_library("module_installer_java") {
java_files = [ java_files = [
"java/src/org/chromium/components/module_installer/builder/Module.java", "java/src/org/chromium/components/module_installer/builder/Module.java",
"java/src/org/chromium/components/module_installer/builder/ModuleDescriptor.java",
"java/src/org/chromium/components/module_installer/builder/ModuleEngine.java", "java/src/org/chromium/components/module_installer/builder/ModuleEngine.java",
"java/src/org/chromium/components/module_installer/engine/ApkEngine.java", "java/src/org/chromium/components/module_installer/engine/ApkEngine.java",
"java/src/org/chromium/components/module_installer/engine/EngineFactory.java",
"java/src/org/chromium/components/module_installer/engine/FakeEngine.java", "java/src/org/chromium/components/module_installer/engine/FakeEngine.java",
"java/src/org/chromium/components/module_installer/engine/InstallEngine.java", "java/src/org/chromium/components/module_installer/engine/InstallEngine.java",
"java/src/org/chromium/components/module_installer/engine/EngineFactory.java",
"java/src/org/chromium/components/module_installer/engine/InstallListener.java", "java/src/org/chromium/components/module_installer/engine/InstallListener.java",
"java/src/org/chromium/components/module_installer/engine/SplitCompatEngine.java", "java/src/org/chromium/components/module_installer/engine/SplitCompatEngine.java",
"java/src/org/chromium/components/module_installer/engine/SplitCompatEngineFacade.java", "java/src/org/chromium/components/module_installer/engine/SplitCompatEngineFacade.java",
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
package org.chromium.components.module_installer.builder; package org.chromium.components.module_installer.builder;
import org.chromium.base.BuildConfig;
import org.chromium.base.StrictModeContext; import org.chromium.base.StrictModeContext;
import org.chromium.base.VisibleForTesting; import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.JNINamespace;
...@@ -113,15 +114,6 @@ public class Module<T> { ...@@ -113,15 +114,6 @@ public class Module<T> {
if (mImpl != null) return mImpl; if (mImpl != null) return mImpl;
assert isInstalled(); assert isInstalled();
// Accessing classes in the module may cause its DEX file to be loaded. And on some
// devices that causes a read mode violation.
try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
mImpl = mInterfaceClass.cast(Class.forName(mImplClassName).newInstance());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException
| IllegalArgumentException e) {
throw new RuntimeException(e);
}
// Load the module's native code and/or resources if they are present, and the Chrome // Load the module's native code and/or resources if they are present, and the Chrome
// native library itself has been loaded. // native library itself has been loaded.
if (sPendingNativeRegistrations == null) { if (sPendingNativeRegistrations == null) {
...@@ -132,6 +124,7 @@ public class Module<T> { ...@@ -132,6 +124,7 @@ public class Module<T> {
// initialization. // initialization.
sPendingNativeRegistrations.add(mName); sPendingNativeRegistrations.add(mName);
} }
mImpl = mInterfaceClass.cast(instantiateReflectively(mImplClassName));
return mImpl; return mImpl;
} }
} }
...@@ -139,8 +132,11 @@ public class Module<T> { ...@@ -139,8 +132,11 @@ public class Module<T> {
private static void loadNative(String name) { private static void loadNative(String name) {
// Can only initialize native once per lifetime of Chrome. // Can only initialize native once per lifetime of Chrome.
if (sInitializedModules.contains(name)) return; if (sInitializedModules.contains(name)) return;
// TODO(crbug.com/870055, crbug.com/986960): Automatically determine if module has native // TODO(crbug.com/870055): Use |libraries| instead of whitelist to load
// code or resources instead of whitelisting. // native libraries.
String[] libraries = loadModuleDescriptor(name).getLibraries();
// TODO(crbug.com/986960): Automatically determine if module has native
// resources instead of whitelisting.
boolean loadLibrary = false; boolean loadLibrary = false;
boolean loadResources = false; boolean loadResources = false;
if ("test_dummy".equals(name)) { if ("test_dummy".equals(name)) {
...@@ -156,6 +152,47 @@ public class Module<T> { ...@@ -156,6 +152,47 @@ public class Module<T> {
sInitializedModules.add(name); sInitializedModules.add(name);
} }
/**
* Loads the {@link ModuleDescriptor} for a module.
*
* For bundles, uses reflection to load the descriptor from inside the
* module. For APKs, returns an empty descriptor since APKs won't have
* descriptors packaged into them.
*
* @param name The module's name.
* @return The module's {@link ModuleDescriptor}.
*/
private static ModuleDescriptor loadModuleDescriptor(String name) {
if (!BuildConfig.IS_BUNDLE) {
return new ModuleDescriptor() {
@Override
public String[] getLibraries() {
return new String[0];
}
};
}
return (ModuleDescriptor) instantiateReflectively(
"org.chromium.components.module_installer.builder.ModuleDescriptor_" + name);
}
/**
* Instantiates an object via reflection.
*
* Ignores strict mode violations since accessing code in a module may cause its DEX file to be
* loaded and on some devices that can cause such a violation.
*
* @param className The object's class name.
* @return The object.
*/
private static Object instantiateReflectively(String className) {
try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
return Class.forName(className).newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@NativeMethods @NativeMethods
interface Natives { interface Natives {
void loadNative(String name, boolean loadLibrary, boolean loadResources); void loadNative(String name, boolean loadLibrary, boolean loadResources);
......
// Copyright 2019 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.components.module_installer.builder;
/**
* Provides information about a dynamic feature module.
*/
public interface ModuleDescriptor {
/**
* Returns the list of native library names this module requires at runtime.
*/
String[] getLibraries();
}
# Copyright 2019 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")
import("//build/config/python.gni")
# Writes an implementation of
# |org.chromium.components.module_installer.builder.ModuleDescriptor| for a
# particular module to Java. The module loader backend expects such an
# implementation for each module to automate module setup on first access.
# Instantiations of this template can be depended on like |android_library|
# targets.
#
# Supports the following variables:
# module_name: Name of the module.
# shared_libraries: (Optional) List of shared_library targets the module
# requires at runtime. Will consider all transitively depended on
# shared_libraries.
template("module_desc_java") {
_target_name = target_name
_libraries = "${target_gen_dir}/${_target_name}.libraries"
_rebased_libraries = rebase_path(_libraries, root_out_dir)
generated_file("${_target_name}__libraries") {
if (defined(invoker.shared_libraries)) {
deps = invoker.shared_libraries
}
outputs = [
_libraries,
]
data_keys = [ "shared_libraries" ]
walk_keys = [ "shared_libraries_barrier" ]
rebase = root_build_dir
output_conversion = "json"
}
_srcjar = "$target_gen_dir/${_target_name}__srcjar.srcjar"
action_with_pydeps("${_target_name}__srcjar") {
script = "//components/module_installer/android/module_desc_java.py"
deps = [
":${_target_name}__libraries",
]
inputs = [
_libraries,
]
outputs = [
_srcjar,
]
args = [
"--module",
invoker.module_name,
"--libraries",
"@FileArg($_rebased_libraries)",
"--output",
rebase_path(_srcjar, root_out_dir),
]
}
android_library(_target_name) {
deps = [
"//base:base_java",
"//components/module_installer/android:module_installer_java",
]
srcjar_deps = [ ":${_target_name}__srcjar" ]
}
}
#!/usr/bin/env python
#
# Copyright 2019 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.
"""Writes Java module descriptor to srcjar file."""
import argparse
import os
import sys
import zipfile
sys.path.append(
os.path.join(
os.path.dirname(__file__), '..', '..', '..', 'build', 'android', 'gyp'))
from util import build_utils
_TEMPLATE = '''\
// This file is autogenerated by
// components/module_installer/android/module_desc_java.py
// Please do not change its content.
package org.chromium.components.module_installer.builder;
import org.chromium.base.annotations.UsedByReflection;
@UsedByReflection("Module.java")
public class ModuleDescriptor_{MODULE} implements ModuleDescriptor {{
private static final String[] LIBRARIES = {{{LIBRARIES}}};
@Override
public String[] getLibraries() {{
return LIBRARIES;
}}
}}
'''
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--module', required=True, help='The module name.')
parser.add_argument(
'--libraries', required=True, help='GN list of native library paths.')
parser.add_argument(
'--output', required=True, help='Path to the generated srcjar file.')
options = parser.parse_args(build_utils.ExpandFileArgs(sys.argv[1:]))
options.libraries = build_utils.ParseGnList(options.libraries)
libraries = []
for path in options.libraries:
path = path.strip()
filename = os.path.split(path)[1]
assert filename.startswith('lib')
assert filename.endswith('.so')
# Remove lib prefix and .so suffix.
libraries += [filename[3:-3]]
format_dict = {
'MODULE': options.module,
'LIBRARIES': ','.join(['"%s"' % l for l in libraries]),
}
with build_utils.AtomicOutput(options.output) as f:
with zipfile.ZipFile(f.name, 'w') as srcjar_file:
build_utils.AddToZipHermetic(
srcjar_file,
'org/chromium/components/module_installer/builder/'
'ModuleDescriptor_%s.java' % options.module,
data=_TEMPLATE.format(**format_dict))
if __name__ == '__main__':
sys.exit(main())
# Generated by running:
# build/print_python_deps.py --root components/module_installer/android --output components/module_installer/android/module_desc_java.pydeps components/module_installer/android/module_desc_java.py
../../../build/android/gyp/util/__init__.py
../../../build/android/gyp/util/build_utils.py
../../../build/android/gyp/util/md5_check.py
../../../build/gn_helpers.py
module_desc_java.py
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment