Skip to content

Commit

Permalink
Embed ICU data inside libflutter.so on Android (flutter#7588)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jason-simmons authored Jan 30, 2019
1 parent c92df42 commit 050dcaa
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 22 deletions.
2 changes: 1 addition & 1 deletion DEPS
Original file line number Diff line number Diff line change
Expand Up @@ -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
#
Expand Down
1 change: 1 addition & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions common/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
17 changes: 17 additions & 0 deletions fml/icu_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -22,6 +23,10 @@ class ICUContext {
valid_ = SetupMapping(icu_data_path) && SetupICU();
}

ICUContext(std::unique_ptr<Mapping> mapping) : mapping_(std::move(mapping)) {
valid_ = SetupICU();
}

~ICUContext() = default;

bool SetupMapping(const std::string& icu_data_path) {
Expand Down Expand Up @@ -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> 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> mapping) {
std::call_once(g_icu_init_flag, [mapping = std::move(mapping)]() mutable {
InitializeICUFromMappingOnce(std::move(mapping));
});
}

} // namespace icu
} // namespace fml
3 changes: 3 additions & 0 deletions fml/icu_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
#include <string>

#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> mapping);

} // namespace icu
} // namespace fml

Expand Down
2 changes: 2 additions & 0 deletions shell/common/shell.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
}
Expand Down
20 changes: 20 additions & 0 deletions shell/common/switches.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <sstream>
#include <string>

#include "flutter/fml/native_library.h"
#include "flutter/fml/paths.h"
#include "flutter/fml/string_view.h"
#include "flutter/shell/version/version.h"
Expand Down Expand Up @@ -121,6 +122,17 @@ static bool GetSwitchValue(const fml::CommandLine& command_line,
return false;
}

std::unique_ptr<fml::Mapping> GetSymbolMapping(std::string symbol_prefix) {
fml::RefPtr<fml::NativeLibrary> proc_library =
fml::NativeLibrary::CreateForCurrentProcess();
const uint8_t* mapping =
proc_library->ResolveSymbol((symbol_prefix + "_start").c_str());
const intptr_t size = reinterpret_cast<intptr_t>(
proc_library->ResolveSymbol((symbol_prefix + "_size").c_str()));
FML_CHECK(mapping && size) << "Unable to resolve symbols: " << symbol_prefix;
return std::make_unique<fml::NonOwnedMapping>(mapping, size);
}

blink::Settings SettingsFromCommandLine(const fml::CommandLine& command_line) {
blink::Settings settings = {};

Expand Down Expand Up @@ -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));
Expand Down
4 changes: 4 additions & 0 deletions shell/common/switches.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Expand Down
28 changes: 17 additions & 11 deletions shell/platform/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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") {
Expand Down Expand Up @@ -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,
]
}

Expand All @@ -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 = [
Expand All @@ -234,16 +243,13 @@ 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",
"$android_app_abi",
]

deps = [
":flutter_shell_assets",
":flutter_shell_java",
":flutter_shell_native",
]
Expand Down
14 changes: 14 additions & 0 deletions shell/platform/android/android_exports.lst
Original file line number Diff line number Diff line change
@@ -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:
*;
};
11 changes: 1 addition & 10 deletions shell/platform/android/io/flutter/view/FlutterMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -85,7 +81,6 @@ private static String fromFlutterAssets(String filePath) {
private static boolean sIsPrecompiledAsBlobs;
private static boolean sIsPrecompiledAsSharedLibrary;
private static Settings sSettings;
private static String sIcuDataPath;

private static final class ImmutableSetBuilder<T> {
static <T> ImmutableSetBuilder<T> newInstance() {
Expand Down Expand Up @@ -188,7 +183,7 @@ public static void ensureInitializationComplete(Context applicationContext, Stri
sResourceExtractor.waitForCompletion();

List<String> 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);
}
Expand Down Expand Up @@ -284,10 +279,6 @@ private static void initResources(Context applicationContext) {

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))
Expand Down
49 changes: 49 additions & 0 deletions sky/tools/objcopy.py
Original file line number Diff line number Diff line change
@@ -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())

0 comments on commit 050dcaa

Please sign in to comment.