From 050dcaad6046c0997db6bb6d56926ff67a35fb40 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Wed, 30 Jan 2019 11:56:17 -0800 Subject: [PATCH] Embed ICU data inside libflutter.so on Android (#7588) Prior to this the Android embedder code would extract the icudtl.dat asset out of the APK and write it to local disk during the first startup of the app. This change will make that work unnecessary and eliminate the risk of ICU failures due to errors in the extraction process. --- DEPS | 2 +- ci/licenses_golden/licenses_flutter | 1 + common/settings.h | 1 + fml/icu_util.cc | 17 +++++++ fml/icu_util.h | 3 ++ shell/common/shell.cc | 2 + shell/common/switches.cc | 20 ++++++++ shell/common/switches.h | 4 ++ shell/platform/android/BUILD.gn | 28 ++++++----- shell/platform/android/android_exports.lst | 14 ++++++ .../android/io/flutter/view/FlutterMain.java | 11 +---- sky/tools/objcopy.py | 49 +++++++++++++++++++ 12 files changed, 130 insertions(+), 22 deletions(-) create mode 100644 shell/platform/android/android_exports.lst create mode 100644 sky/tools/objcopy.py diff --git a/DEPS b/DEPS index 11be5753661..1beaadf2c53 100644 --- a/DEPS +++ b/DEPS @@ -116,7 +116,7 @@ allowed_hosts = [ ] deps = { - 'src': 'https://github.com/flutter/buildroot.git' + '@' + '19317704cfb7be72c7d81953f634674ea8bee5ea', + 'src': 'https://github.com/flutter/buildroot.git' + '@' + '13ca742ec8b3d7761877197d74b003d3e646d805', # Fuchsia compatibility # diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 828ff5bd94d..4df1c866548 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -391,6 +391,7 @@ FILE: ../../../flutter/shell/platform/android/android_context_gl.cc FILE: ../../../flutter/shell/platform/android/android_context_gl.h FILE: ../../../flutter/shell/platform/android/android_environment_gl.cc FILE: ../../../flutter/shell/platform/android/android_environment_gl.h +FILE: ../../../flutter/shell/platform/android/android_exports.lst FILE: ../../../flutter/shell/platform/android/android_external_texture_gl.cc FILE: ../../../flutter/shell/platform/android/android_external_texture_gl.h FILE: ../../../flutter/shell/platform/android/android_native_window.cc diff --git a/common/settings.h b/common/settings.h index d131695f4b5..d51052f3c1e 100644 --- a/common/settings.h +++ b/common/settings.h @@ -105,6 +105,7 @@ struct Settings { bool verbose_logging = false; std::string log_tag = "flutter"; std::string icu_data_path; + MappingCallback icu_mapper; // Assets settings fml::UniqueFD::element_type assets_dir = diff --git a/fml/icu_util.cc b/fml/icu_util.cc index a432fcf9899..fa0cc02b9c2 100644 --- a/fml/icu_util.cc +++ b/fml/icu_util.cc @@ -10,6 +10,7 @@ #include "flutter/fml/build_config.h" #include "flutter/fml/logging.h" #include "flutter/fml/mapping.h" +#include "flutter/fml/native_library.h" #include "flutter/fml/paths.h" #include "third_party/icu/source/common/unicode/udata.h" @@ -22,6 +23,10 @@ class ICUContext { valid_ = SetupMapping(icu_data_path) && SetupICU(); } + ICUContext(std::unique_ptr mapping) : mapping_(std::move(mapping)) { + valid_ = SetupICU(); + } + ~ICUContext() = default; bool SetupMapping(const std::string& icu_data_path) { @@ -99,5 +104,17 @@ void InitializeICU(const std::string& icu_data_path) { [&icu_data_path]() { InitializeICUOnce(icu_data_path); }); } +void InitializeICUFromMappingOnce(std::unique_ptr mapping) { + static ICUContext* context = new ICUContext(std::move(mapping)); + FML_CHECK(context->IsValid()) + << "Unable to initialize the ICU context from a mapping."; +} + +void InitializeICUFromMapping(std::unique_ptr mapping) { + std::call_once(g_icu_init_flag, [mapping = std::move(mapping)]() mutable { + InitializeICUFromMappingOnce(std::move(mapping)); + }); +} + } // namespace icu } // namespace fml diff --git a/fml/icu_util.h b/fml/icu_util.h index edde00b8137..22b7a906efc 100644 --- a/fml/icu_util.h +++ b/fml/icu_util.h @@ -8,12 +8,15 @@ #include #include "flutter/fml/macros.h" +#include "flutter/fml/mapping.h" namespace fml { namespace icu { void InitializeICU(const std::string& icu_data_path = ""); +void InitializeICUFromMapping(std::unique_ptr mapping); + } // namespace icu } // namespace fml diff --git a/shell/common/shell.cc b/shell/common/shell.cc index c517946f2c5..2a00a44605b 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -189,6 +189,8 @@ static void PerformInitializationTasks(const blink::Settings& settings) { if (settings.icu_data_path.size() != 0) { fml::icu::InitializeICU(settings.icu_data_path); + } else if (settings.icu_mapper) { + fml::icu::InitializeICUFromMapping(settings.icu_mapper()); } else { FML_DLOG(WARNING) << "Skipping ICU initialization in the shell."; } diff --git a/shell/common/switches.cc b/shell/common/switches.cc index 295118bfec4..5b4f1abe2d5 100644 --- a/shell/common/switches.cc +++ b/shell/common/switches.cc @@ -9,6 +9,7 @@ #include #include +#include "flutter/fml/native_library.h" #include "flutter/fml/paths.h" #include "flutter/fml/string_view.h" #include "flutter/shell/version/version.h" @@ -121,6 +122,17 @@ static bool GetSwitchValue(const fml::CommandLine& command_line, return false; } +std::unique_ptr GetSymbolMapping(std::string symbol_prefix) { + fml::RefPtr proc_library = + fml::NativeLibrary::CreateForCurrentProcess(); + const uint8_t* mapping = + proc_library->ResolveSymbol((symbol_prefix + "_start").c_str()); + const intptr_t size = reinterpret_cast( + proc_library->ResolveSymbol((symbol_prefix + "_size").c_str())); + FML_CHECK(mapping && size) << "Unable to resolve symbols: " << symbol_prefix; + return std::make_unique(mapping, size); +} + blink::Settings SettingsFromCommandLine(const fml::CommandLine& command_line) { blink::Settings settings = {}; @@ -213,6 +225,14 @@ blink::Settings SettingsFromCommandLine(const fml::CommandLine& command_line) { command_line.GetOptionValue(FlagForSwitch(Switch::ICUDataFilePath), &settings.icu_data_path); + if (command_line.HasOption(FlagForSwitch(Switch::ICUSymbolPrefix))) { + std::string icu_symbol_prefix; + command_line.GetOptionValue(FlagForSwitch(Switch::ICUSymbolPrefix), + &icu_symbol_prefix); + settings.icu_mapper = [icu_symbol_prefix] { + return GetSymbolMapping(icu_symbol_prefix); + }; + } settings.use_test_fonts = command_line.HasOption(FlagForSwitch(Switch::UseTestFonts)); diff --git a/shell/common/switches.h b/shell/common/switches.h index b278e32d6f3..c20589dea62 100644 --- a/shell/common/switches.h +++ b/shell/common/switches.h @@ -48,6 +48,10 @@ DEF_SWITCH(AotIsolateSnapshotInstructions, "read and executable. AotSnapshotPath must be present.") DEF_SWITCH(CacheDirPath, "cache-dir-path", "Path to the cache directory.") DEF_SWITCH(ICUDataFilePath, "icu-data-file-path", "Path to the ICU data file.") +DEF_SWITCH(ICUSymbolPrefix, + "icu-symbol-prefix", + "Prefix for the symbols representing ICU data linked into the " + "Flutter library.") DEF_SWITCH(DartFlags, "dart-flags", "Flags passed directly to the Dart VM without being interpreted " diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 0e4ec9a9582..e9d2a961f81 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -49,10 +49,12 @@ shared_library("flutter_shell_native") { "platform_view_android_jni.h", "vsync_waiter_android.cc", "vsync_waiter_android.h", + "$root_build_dir/flutter_icu/icudtl.o", ] deps = [ ":android_gpu_configuration", + ":icudtl_object", "$flutter_root/assets", "$flutter_root/common", "$flutter_root/flow", @@ -90,6 +92,8 @@ shared_library("flutter_shell_native") { "EGL", "GLESv2", ] + + ldflags = ["-Wl,--version-script=" + rebase_path("android_exports.lst")] } java_library("flutter_shell_java") { @@ -200,19 +204,25 @@ java_prebuilt("android_arch_lifecycle_viewmodel") { jar_path = "//third_party/android_support/android_arch_lifecycle_viewmodel.jar" } -copy("flutter_shell_assets") { - visibility = [ ":*" ] +action("icudtl_object") { + script = "$flutter_root/sky/tools/objcopy.py" - sources = [ - "//third_party/icu/flutter/icudtl.dat", + icudtl_input = "//third_party/icu/flutter/icudtl.dat" + icudtl_output = "$root_build_dir/flutter_icu/icudtl.o" + + inputs = [ + "$icudtl_input", ] outputs = [ - "$root_build_dir/flutter_shell_assets/{{source_file_part}}", + "$icudtl_output", ] - deps = [ - "//third_party/icu:icudata", + args = [ + "--objcopy", rebase_path(android_objcopy), + "--input", rebase_path(icudtl_input), + "--output", rebase_path(icudtl_output), + "--arch", current_cpu, ] } @@ -222,7 +232,6 @@ action("android") { inputs = [ "$root_build_dir/flutter_java.jar", "$root_build_dir/lib.stripped/libflutter.so", - "$root_build_dir/flutter_shell_assets/icudtl.dat", ] outputs = [ @@ -234,8 +243,6 @@ action("android") { rebase_path("flutter.jar", root_build_dir, root_build_dir), "--dist_jar", rebase_path("flutter_java.jar", root_build_dir, root_build_dir), - "--asset_dir", - rebase_path("flutter_shell_assets", root_build_dir, root_build_dir), "--native_lib", rebase_path("lib.stripped/libflutter.so", root_build_dir, root_build_dir), "--android_abi", @@ -243,7 +250,6 @@ action("android") { ] deps = [ - ":flutter_shell_assets", ":flutter_shell_java", ":flutter_shell_native", ] diff --git a/shell/platform/android/android_exports.lst b/shell/platform/android/android_exports.lst new file mode 100644 index 00000000000..3bea8e22e38 --- /dev/null +++ b/shell/platform/android/android_exports.lst @@ -0,0 +1,14 @@ +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Linker script that exports the minimal symbols required for libflutter.so + +{ + global: + JNI_OnLoad; + _binary_icudtl_dat_start; + _binary_icudtl_dat_size; + local: + *; +}; diff --git a/shell/platform/android/io/flutter/view/FlutterMain.java b/shell/platform/android/io/flutter/view/FlutterMain.java index 9b0edbeed0b..c59e9ab1fb2 100644 --- a/shell/platform/android/io/flutter/view/FlutterMain.java +++ b/shell/platform/android/io/flutter/view/FlutterMain.java @@ -62,10 +62,6 @@ public class FlutterMain { private static final String DEFAULT_KERNEL_BLOB = "kernel_blob.bin"; private static final String DEFAULT_FLUTTER_ASSETS_DIR = "flutter_assets"; - // Assets that are shared among all Flutter apps within an APK. - private static final String SHARED_ASSET_DIR = "flutter_shared"; - private static final String SHARED_ASSET_ICU_DATA = "icudtl.dat"; - private static String fromFlutterAssets(String filePath) { return sFlutterAssetsDir + File.separator + filePath; } @@ -85,7 +81,6 @@ public class FlutterMain { private static boolean sIsPrecompiledAsBlobs; private static boolean sIsPrecompiledAsSharedLibrary; private static Settings sSettings; - private static String sIcuDataPath; private static final class ImmutableSetBuilder { static ImmutableSetBuilder newInstance() { @@ -188,7 +183,7 @@ public class FlutterMain { sResourceExtractor.waitForCompletion(); List shellArgs = new ArrayList<>(); - shellArgs.add("--icu-data-file-path=" + sIcuDataPath); + shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat"); if (args != null) { Collections.addAll(shellArgs, args); } @@ -284,10 +279,6 @@ public class FlutterMain { sResourceExtractor = new ResourceExtractor(context); - String icuAssetPath = SHARED_ASSET_DIR + File.separator + SHARED_ASSET_ICU_DATA; - sResourceExtractor.addResource(icuAssetPath); - sIcuDataPath = PathUtils.getDataDirectory(applicationContext) + File.separator + icuAssetPath; - sResourceExtractor .addResource(fromFlutterAssets(sFlx)) .addResource(fromFlutterAssets(sAotVmSnapshotData)) diff --git a/sky/tools/objcopy.py b/sky/tools/objcopy.py new file mode 100644 index 00000000000..b8da36d43b8 --- /dev/null +++ b/sky/tools/objcopy.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +# Copyright 2013 The Flutter 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 subprocess +import sys + +# BFD architecture names recognized by objcopy. +BFD_ARCH = { + 'arm': 'arm', + 'arm64': 'aarch64', + 'x86': 'i386', + 'x64': 'i386:x86-64', +} + +# BFD target names recognized by objcopy. +BFD_TARGET = { + 'arm': 'elf32-littlearm', + 'arm64': 'elf64-littleaarch64', + 'x86': 'elf32-i386', + 'x64': 'elf64-x86-64', +} + +def main(): + parser = argparse.ArgumentParser(description='Convert a data file to an object file') + parser.add_argument('--objcopy', type=str, required=True) + parser.add_argument('--input', type=str, required=True) + parser.add_argument('--output', type=str, required=True) + parser.add_argument('--arch', type=str, required=True) + + args = parser.parse_args() + + input_dir, input_file = os.path.split(args.input) + output_path = os.path.abspath(args.output) + + subprocess.check_call([ + args.objcopy, + '-I', 'binary', + '-O', BFD_TARGET[args.arch], + '-B', BFD_ARCH[args.arch], + input_file, + output_path, + ], cwd=input_dir) + +if __name__ == '__main__': + sys.exit(main())