From 3ac69ae22330baa0e9d63f431ed65a964ee01f74 Mon Sep 17 00:00:00 2001 From: Eric Seidel Date: Tue, 16 Jan 2024 13:22:15 -0800 Subject: [PATCH 01/13] refactor: squash all of our changes down to a single commit Our merge commits caused trouble missing tags. --- BUILD.gn | 27 ++- DEPS | 12 +- build/archives/BUILD.gn | 13 +- lib/snapshot/BUILD.gn | 27 ++- runtime/dart_snapshot.cc | 105 +++++++-- shell/common/BUILD.gn | 2 + shell/common/shell.cc | 10 + shell/common/shorebird/BUILD.gn | 55 +++++ shell/common/shorebird/shorebird.cc | 206 ++++++++++++++++++ shell/common/shorebird/shorebird.h | 17 ++ .../common/shorebird/snapshots_data_handle.cc | 144 ++++++++++++ .../common/shorebird/snapshots_data_handle.h | 47 ++++ .../snapshots_data_handle_unittests.cc | 177 +++++++++++++++ shell/platform/android/BUILD.gn | 44 +++- shell/platform/android/android_exports.lst | 8 + shell/platform/android/flutter_main.cc | 21 +- shell/platform/android/flutter_main.h | 3 + .../flutter/embedding/engine/FlutterJNI.java | 48 +++- shell/platform/darwin/ios/BUILD.gn | 13 ++ .../framework/Source/FlutterDartProject.mm | 34 +++ .../framework/Source/FlutterViewController.mm | 4 + sky/tools/create_full_ios_framework.py | 26 +++ testing/run_tests.py | 1 + 23 files changed, 1005 insertions(+), 39 deletions(-) create mode 100644 shell/common/shorebird/BUILD.gn create mode 100644 shell/common/shorebird/shorebird.cc create mode 100644 shell/common/shorebird/shorebird.h create mode 100644 shell/common/shorebird/snapshots_data_handle.cc create mode 100644 shell/common/shorebird/snapshots_data_handle.h create mode 100644 shell/common/shorebird/snapshots_data_handle_unittests.cc diff --git a/BUILD.gn b/BUILD.gn index 409c5f912a570..e9d5ee6d72a66 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -92,14 +92,10 @@ group("flutter") { # path_ops "//flutter/tools/path_ops", - ] - if (host_os == "linux") { - public_deps += [ - # Built alongside gen_snapshot for 64 bit targets - "//third_party/dart/runtime/bin:analyze_snapshot", - ] - } + # Built alongside gen_snapshot arm64 targets. + "//third_party/dart/runtime/bin:analyze_snapshot", + ] if (full_dart_sdk) { public_deps += [ "//flutter/web_sdk" ] @@ -165,6 +161,7 @@ group("unittests") { "//flutter/runtime:no_dart_plugin_registrant_unittests", "//flutter/runtime:runtime_unittests", "//flutter/shell/common:shell_unittests", + "//flutter/shell/common/shorebird:shorebird_unittests", "//flutter/shell/platform/embedder:embedder_a11y_unittests", "//flutter/shell/platform/embedder:embedder_proctable_unittests", "//flutter/shell/platform/embedder:embedder_unittests", @@ -287,3 +284,19 @@ if (host_os == "win") { outputs = [ "$root_build_dir/gen_snapshot/gen_snapshot.exe" ] } } + +# A top-level target for analyze_snapshot, modeled after the gen_snapshot +# target above. +if (host_os == "win") { + _analyze_snapshot_target = + "//third_party/dart/runtime/bin:analyze_snapshot($host_toolchain)" + + copy("analyze_snapshot") { + deps = [ _analyze_snapshot_target ] + + analyze_snapshot_out_dir = + get_label_info(_analyze_snapshot_target, "root_out_dir") + sources = [ "$analyze_snapshot_out_dir/analyze_snapshot.exe" ] + outputs = [ "$root_build_dir/analyze_snapshot/analyze_snapshot.exe" ] + } +} diff --git a/DEPS b/DEPS index 0386902e6ee43..8de3ce8e59ed1 100644 --- a/DEPS +++ b/DEPS @@ -20,6 +20,11 @@ vars = { 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', 'skia_revision': '795ed944ff5bde5916d193824589d3bacfa61a7d', + 'dart_sdk_revision': '33a70656a13a065e939e22eda44de21bee326077', + 'dart_sdk_git': 'git@github.com:shorebirdtech/dart-sdk.git', + 'updater_git': 'https://github.com/shorebirdtech/updater.git', + 'updater_rev': 'c6f8b2933e1110750ad40669194c39df1f76eb98', + # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. 'canvaskit_cipd_instance': '61aeJQ9laGfEFF_Vlc_u0MCkqB6xb2hAYHRBxKH-Uw4C', @@ -262,7 +267,7 @@ allowed_hosts = [ ] deps = { - 'src': 'https://github.com/flutter/buildroot.git' + '@' + 'f27d99b4428dea312646130d60c33a2d38fa9dc6', + 'src': 'https://github.com/shorebirdtech/buildroot.git' + '@' + '2c3c95dc8cdb71fa176fe2e7886ef2022cbdc3bb', # Fuchsia compatibility # @@ -329,7 +334,7 @@ deps = { Var('fuchsia_git') + '/protobuf-gn' + '@' + Var('dart_protobuf_gn_rev'), 'src/third_party/dart': - Var('dart_git') + '/sdk.git' + '@' + Var('dart_revision'), + Var('dart_sdk_git') + '@' + Var('dart_sdk_revision'), # WARNING: Unused Dart dependencies in the list below till "WARNING:" marker are removed automatically - see create_updated_flutter_deps.py. @@ -595,6 +600,9 @@ deps = { 'src/third_party/ocmock': Var('ocmock_git') + '@' + Var('ocmock_rev'), + 'src/third_party/updater': + Var('updater_git') + '@' + Var('updater_rev'), + 'src/third_party/libjpeg-turbo': Var('fuchsia_git') + '/third_party/libjpeg-turbo' + '@' + '0fb821f3b2e570b2783a94ccd9a2fb1f4916ae9f', diff --git a/build/archives/BUILD.gn b/build/archives/BUILD.gn index 002bbeabada90..1f9f1a4fd8eb6 100644 --- a/build/archives/BUILD.gn +++ b/build/archives/BUILD.gn @@ -297,14 +297,25 @@ if (is_mac) { } if (host_os == "win") { + # This rule archives both gen_snapshot *and* analyze_snapshot. The name is + # misleading. We (shorebird) have updated this rule to include + # analyze_snapshot but did not update the name because it is referenced + # elsewhere in the tooling. zip_bundle("archive_win_gen_snapshot") { - deps = [ "//flutter:gen_snapshot" ] + deps = [ + "//flutter:analyze_snapshot", + "//flutter:gen_snapshot", + ] output = "$full_target_platform_name-$flutter_runtime_mode/windows-x64.zip" files = [ { source = "$root_out_dir/gen_snapshot/gen_snapshot.exe" destination = "gen_snapshot.exe" }, + { + source = "$root_out_dir/analyze_snapshot/analyze_snapshot.exe" + destination = "analyze_snapshot.exe" + }, ] } } diff --git a/lib/snapshot/BUILD.gn b/lib/snapshot/BUILD.gn index eacbee21539f9..89caaef71e537 100644 --- a/lib/snapshot/BUILD.gn +++ b/lib/snapshot/BUILD.gn @@ -33,11 +33,14 @@ group("generate_snapshot_bins") { deps += [ ":create_macos_gen_snapshots" ] } else if (host_os == "mac" && (target_cpu == "arm" || target_cpu == "arm64")) { - deps += [ ":create_arm_gen_snapshot" ] + deps += [ + ":create_arm_analyze_snapshot", + ":create_arm_gen_snapshot", + ] } # Build analyze_snapshot for 64-bit target CPUs. - if (host_os == "linux" && (target_cpu == "x64" || target_cpu == "arm64")) { + if (target_cpu == "arm64") { deps += [ "//third_party/dart/runtime/bin:analyze_snapshot($host_toolchain)" ] } @@ -270,6 +273,26 @@ if (host_os == "mac" && target_os != "mac" && deps = [ "//third_party/dart/runtime/bin:gen_snapshot($host_toolchain)" ] visibility = [ ":*" ] } + + copy("create_arm_analyze_snapshot") { + # The toolchain-specific output directory. For cross-compiles, this is a + # clang-x64 or clang-arm64 subdirectory of the top-level build directory. + host_output_dir = get_label_info( + "//third_party/dart/runtime/bin:analyze_snapshot($host_toolchain)", + "root_out_dir") + + # Determine suffixed output gen_snapshot name. + target_cpu_suffix = target_cpu + if (target_cpu == "arm") { + target_cpu_suffix = "armv7" + } + + sources = [ "${host_output_dir}/analyze_snapshot" ] + outputs = [ "${host_output_dir}/analyze_snapshot_${target_cpu_suffix}" ] + deps = + [ "//third_party/dart/runtime/bin:analyze_snapshot($host_toolchain)" ] + visibility = [ ":*" ] + } } # Creates a `gen_snapshot` binary suffixed with the target CPU architecture. diff --git a/runtime/dart_snapshot.cc b/runtime/dart_snapshot.cc index 5943524aa9f80..312fb7c64f457 100644 --- a/runtime/dart_snapshot.cc +++ b/runtime/dart_snapshot.cc @@ -6,6 +6,7 @@ #include +#include #include "flutter/fml/native_library.h" #include "flutter/fml/paths.h" #include "flutter/fml/trace_event.h" @@ -56,33 +57,93 @@ static std::shared_ptr SearchMapping( const std::vector& native_library_path, const char* native_library_symbol_name, bool is_executable) { - // Ask the embedder. There is no fallback as we expect the embedders (via - // their embedding APIs) to just specify the mappings directly. - if (embedder_mapping_callback) { - // Note that mapping will be nullptr if the mapping callback returns an - // invalid mapping. If all the other methods for resolving the data also - // fail, the engine will stop with accompanying error logs. - if (auto mapping = embedder_mapping_callback()) { - return mapping; +#if FML_OS_IOS + // Detect when we're trying to load a Shorebird patch. + auto patch_path = native_library_path.front(); + bool is_patch = patch_path.find(".vmcode") != std::string::npos; + if (is_patch) { + // We use this terrible hack to load in the patch and then extract the + // symbols from it when the path is not App.framework/App but rather + // foo.vmcode, etc. We read the symbols into static variables, but then I + // believe we need to hold onto the ELF itself, otherwise the symbols + // become invalid. + // "leaked_elf" is meant to indicate that we're not freeing the ELF. + static Dart_LoadedElf* leaked_elf = nullptr; + // The VM Snapshot is identical for all binaries produced by a given version + // of Dart. Our linker checks this and will fail to link if ever the VM + // snapshot changes. + const uint8_t* ignored_vm_data = nullptr; + const uint8_t* ignored_vm_instrs = nullptr; + static const uint8_t* isolate_data = nullptr; + static const uint8_t* isolate_instrs = nullptr; + if (leaked_elf == nullptr) { + const char* error = nullptr; + // vmcode files are elf files prefixed with a shorebird linker header. + auto elf_mapping = GetFileMapping(patch_path, false /* executable */); + int elf_file_offset = Shorebird_ReadLinkHeader(elf_mapping->GetMapping(), + elf_mapping->GetSize()); + + leaked_elf = Dart_LoadELF(patch_path.c_str(), elf_file_offset, &error, + &ignored_vm_data, &ignored_vm_instrs, + &isolate_data, &isolate_instrs, + /* load as read-only, not rx */ false); + if (leaked_elf != nullptr) { + FML_LOG(INFO) << "Loaded ELF"; + } else { + FML_LOG(FATAL) << "Failed to load ELF at " << patch_path + << " error: " << error; + abort(); + } } - } - // Attempt to open file at path specified. - if (!file_path.empty()) { - if (auto file_mapping = GetFileMapping(file_path, is_executable)) { - return file_mapping; + FML_LOG(INFO) << "Loading symbol from ELF " << native_library_symbol_name; + + if (native_library_symbol_name == DartSnapshot::kIsolateDataSymbol) { + return std::make_unique(isolate_data, 0, + nullptr, true); + } else if (native_library_symbol_name == + DartSnapshot::kIsolateInstructionsSymbol) { + return std::make_unique(isolate_instrs, 0, + nullptr, true); + } + // Fall through to normal lookups for VM data and instructions. + // This fallthrough depends on the fact that NativeLibrary below can't + // read the ELF out of our .vmcode files. + } else { + // Only try to open the file if we're not loading a patch. +#endif + + // Ask the embedder. There is no fallback as we expect the embedders (via + // their embedding APIs) to just specify the mappings directly. + if (embedder_mapping_callback) { + // Note that mapping will be nullptr if the mapping callback returns an + // invalid mapping. If all the other methods for resolving the data also + // fail, the engine will stop with accompanying error logs. + if (auto mapping = embedder_mapping_callback()) { + return mapping; + } } - } - // Look in application specified native library if specified. - for (const std::string& path : native_library_path) { - auto native_library = fml::NativeLibrary::Create(path.c_str()); - auto symbol_mapping = std::make_unique( - native_library, native_library_symbol_name); - if (symbol_mapping->GetMapping() != nullptr) { - return symbol_mapping; + // Attempt to open file at path specified. + if (!file_path.empty()) { + if (auto file_mapping = GetFileMapping(file_path, is_executable)) { + return file_mapping; + } } - } + + // Look in application specified native library if specified. + for (const std::string& path : native_library_path) { + auto native_library = fml::NativeLibrary::Create(path.c_str()); + auto symbol_mapping = std::make_unique( + native_library, native_library_symbol_name); + if (symbol_mapping->GetMapping() != nullptr) { + return symbol_mapping; + } + } + +#if FML_OS_IOS + } // !is_patch +#endif // Look inside the currently loaded process. { diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index c5a4db91e4333..5f2b1f33023ad 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -148,6 +148,8 @@ source_set("common") { "//third_party/skia", ] + include_dirs = [ "//flutter/updater" ] + if (impeller_supports_rendering) { sources += [ "snapshot_controller_impeller.cc", diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 5164d1a1db176..f021b71a98bf7 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -42,6 +42,8 @@ #include "third_party/skia/include/utils/SkBase64.h" #include "third_party/tonic/common/log.h" +#include "third_party/updater/library/include/updater.h" + namespace flutter { constexpr char kSkiaChannel[] = "flutter/skia"; @@ -425,6 +427,14 @@ Shell::Shell(DartVMRef vm, volatile_path_tracker_(std::move(volatile_path_tracker)), weak_factory_gpu_(nullptr), weak_factory_(this) { +// FIXME: This is probably the wrong place to hook into. +#if FML_OS_ANDROID || FML_OS_IOS + if (!vm_) { + shorebird_report_launch_failure(); + } else { + shorebird_report_launch_success(); + } +#endif FML_CHECK(!settings.enable_software_rendering || !settings.enable_impeller) << "Software rendering is incompatible with Impeller."; FML_CHECK(vm_) << "Must have access to VM to create a shell."; diff --git a/shell/common/shorebird/BUILD.gn b/shell/common/shorebird/BUILD.gn new file mode 100644 index 0000000000000..87df986a13502 --- /dev/null +++ b/shell/common/shorebird/BUILD.gn @@ -0,0 +1,55 @@ +import("//flutter/common/config.gni") +import("//flutter/testing/testing.gni") + +source_set("snapshots_data_handle") { + sources = [ + "snapshots_data_handle.cc", + "snapshots_data_handle.h", + ] + + deps = [ + "//flutter/fml", + "//flutter/runtime", + "//flutter/runtime:libdart", + "//flutter/shell/common", + ] +} + +source_set("shorebird") { + sources = [ + "shorebird.cc", + "shorebird.h", + ] + + deps = [ + ":snapshots_data_handle", + "//flutter/fml", + "//flutter/runtime", + "//flutter/runtime:libdart", + "//flutter/shell/common", + ] + + include_dirs = [ "//flutter/updater" ] +} + +if (enable_unittests) { + test_fixtures("shorebird_fixtures") { + fixtures = [] + } + + executable("shorebird_unittests") { + testonly = true + + sources = [ "snapshots_data_handle_unittests.cc" ] + + # This only includes snapshots_data_handle and not shorebird because + # shorebird fails to link due to a missing updater lib. + deps = [ + ":shorebird_fixtures", + ":snapshots_data_handle", + "//flutter/runtime", + "//flutter/testing", + "//flutter/testing:fixture_test", + ] + } +} diff --git a/shell/common/shorebird/shorebird.cc b/shell/common/shorebird/shorebird.cc new file mode 100644 index 0000000000000..d56a0b7ae3574 --- /dev/null +++ b/shell/common/shorebird/shorebird.cc @@ -0,0 +1,206 @@ + +#include "flutter/shell/common/shorebird/shorebird.h" + +#include +#include +#include +#include +#include + +#include "flutter/fml/command_line.h" +#include "flutter/fml/file.h" +#include "flutter/fml/macros.h" +#include "flutter/fml/mapping.h" +#include "flutter/fml/message_loop.h" +#include "flutter/fml/native_library.h" +#include "flutter/fml/paths.h" +#include "flutter/fml/size.h" +#include "flutter/lib/ui/plugins/callback_cache.h" +#include "flutter/runtime/dart_snapshot.h" +#include "flutter/runtime/dart_vm.h" +#include "flutter/shell/common/shell.h" +#include "flutter/shell/common/shorebird/snapshots_data_handle.h" +#include "flutter/shell/common/switches.h" +#include "fml/logging.h" +#include "third_party/dart/runtime/include/dart_tools_api.h" + +#include "third_party/updater/library/include/updater.h" + +// Namespaced to avoid Google style warnings. +namespace flutter { + +// Old Android versions (e.g. the v16 ndk Flutter uses) don't always include a +// getauxval symbol, but the Rust ring crate assumes it exists: +// https://github.com/briansmith/ring/blob/fa25bf3a7403c9fe6458cb87bd8427be41225ca2/src/cpu/arm.rs#L22 +// It uses it to determine if the CPU supports AES instructions. +// Making this a weak symbol allows the linker to use a real version instead +// if it can find one. +// BoringSSL just reads from procfs instead, which is what we would do if +// we needed to implement this ourselves. Implementation looks straightforward: +// https://lwn.net/Articles/519085/ +// https://github.com/google/boringssl/blob/6ab4f0ae7f2db96d240eb61a5a8b4724e5a09b2f/crypto/cpu_arm_linux.c +#if defined(__ANDROID__) && defined(__arm__) +extern "C" __attribute__((weak)) unsigned long getauxval(unsigned long type) { + return 0; +} +#endif + +// TODO(eseidel): I believe we need to leak these or we'll sometimes crash +// when using the base snapshot in mixed mode. This likely will not play +// nicely with multi-engine support and will need to be refactored. +static fml::RefPtr vm_snapshot; +static fml::RefPtr isolate_snapshot; + +void SetBaseSnapshot(Settings& settings) { + // These mappings happen to be to static data in the App.framework, but + // we still need to seem to hold onto the DartSnapshot objects to keep + // the mappings alive. + vm_snapshot = DartSnapshot::VMSnapshotFromSettings(settings); + isolate_snapshot = DartSnapshot::IsolateSnapshotFromSettings(settings); + Shorebird_SetBaseSnapshots(isolate_snapshot->GetDataMapping(), + isolate_snapshot->GetInstructionsMapping(), + vm_snapshot->GetDataMapping(), + vm_snapshot->GetInstructionsMapping()); +} + +class FileCallbacksImpl { + public: + static void* Open(); + static uintptr_t Read(void* file, uint8_t* buffer, uintptr_t length); + static int64_t Seek(void* file, int64_t offset, int32_t whence); + static void Close(void* file); +}; + +FileCallbacks ShorebirdFileCallbacks() { + return { + .open = FileCallbacksImpl::Open, + .read = FileCallbacksImpl::Read, + .seek = FileCallbacksImpl::Seek, + .close = FileCallbacksImpl::Close, + }; +} + +void ConfigureShorebird(std::string code_cache_path, + std::string app_storage_path, + Settings& settings, + const std::string& shorebird_yaml, + const std::string& version, + const std::string& version_code) { + // If you are crashing here, you probably are running Shorebird in a Debug + // config, where the AOT snapshot won't be linked into the process, and thus + // lookups will fail. Change your Scheme to Release to fix: + // https://github.com/flutter/flutter/wiki/Debugging-the-engine#debugging-ios-builds-with-xcode + FML_CHECK(DartSnapshot::VMSnapshotFromSettings(settings)) + << "XCode Scheme must be set to Release to use Shorebird"; + + auto shorebird_updater_dir_name = "shorebird_updater"; + + auto code_cache_dir = fml::paths::JoinPaths( + {std::move(code_cache_path), shorebird_updater_dir_name}); + auto app_storage_dir = fml::paths::JoinPaths( + {std::move(app_storage_path), shorebird_updater_dir_name}); + + fml::CreateDirectory(fml::paths::GetCachesDirectory(), + {shorebird_updater_dir_name}, + fml::FilePermission::kReadWrite); + + // Using a block to make AppParameters lifetime explicit. + { + AppParameters app_parameters; + // Combine version and version_code into a single string. + // We could also pass these separately through to the updater if needed. + auto release_version = version + "+" + version_code; + app_parameters.release_version = release_version.c_str(); + app_parameters.code_cache_dir = code_cache_dir.c_str(); + app_parameters.app_storage_dir = app_storage_dir.c_str(); + + // https://stackoverflow.com/questions/26032039/convert-vectorstring-into-char-c + std::vector c_paths{}; + for (const auto& string : settings.application_library_path) { + c_paths.push_back(string.c_str()); + } + // Do not modify application_library_path or c_strings will invalidate. + + app_parameters.original_libapp_paths = c_paths.data(); + app_parameters.original_libapp_paths_size = c_paths.size(); + + // shorebird_init copies from app_parameters and shorebirdYaml. + shorebird_init(&app_parameters, ShorebirdFileCallbacks(), + shorebird_yaml.c_str()); + } + + // We've decided not to support synchronous updates on launch for now. + // It's a terrible user experience (having the app hang on launch) and + // instead we will provide examples of how to build a custom update UI + // within Dart, including updating as part of login, etc. + // https://github.com/shorebirdtech/shorebird/issues/950 + + // We only set the base snapshot on iOS for now. +#if FML_OS_IOS + SetBaseSnapshot(settings); +#endif + + char* c_active_path = shorebird_next_boot_patch_path(); + if (c_active_path != NULL) { + std::string active_path = c_active_path; + shorebird_free_string(c_active_path); + FML_LOG(INFO) << "Shorebird updater: active path: " << active_path; + size_t c_patch_number = shorebird_next_boot_patch_number(); + // FIXME: use a constant for this. + // 0 means no patch is available. + if (c_patch_number != 0) { + FML_LOG(INFO) << "Shorebird updater: active patch number: " + << c_patch_number; + } + +#if FML_OS_IOS + // On iOS we add the patch to the front of the list instead of clearing + // the list, to allow dart_shapshot.cc to still find the base snapshot + // for the vm isolate. + settings.application_library_path.insert( + settings.application_library_path.begin(), active_path); +#else + settings.application_library_path.clear(); + settings.application_library_path.emplace_back(active_path); +#endif + + // Once start_update_thread is called, the next_boot_patch* functions may + // change their return values if the shorebird_report_launch_failed + // function is called. + shorebird_report_launch_start(); + + } else { + FML_LOG(INFO) << "Shorebird updater: no active patch."; + } + + if (shorebird_should_auto_update()) { + FML_LOG(INFO) << "Starting Shorebird update"; + shorebird_start_update_thread(); + } else { + FML_LOG(INFO) + << "Shorebird auto_update disabled, not checking for updates."; + } +} + +void* FileCallbacksImpl::Open() { + return SnapshotsDataHandle::createForSnapshots(*vm_snapshot, + *isolate_snapshot) + .release(); +} + +uintptr_t FileCallbacksImpl::Read(void* file, + uint8_t* buffer, + uintptr_t length) { + return reinterpret_cast(file)->Read(buffer, length); +} + +int64_t FileCallbacksImpl::Seek(void* file, int64_t offset, int32_t whence) { + // Currently we only support blob handles. + return reinterpret_cast(file)->Seek(offset, whence); +} + +void FileCallbacksImpl::Close(void* file) { + delete reinterpret_cast(file); +} + +} // namespace flutter \ No newline at end of file diff --git a/shell/common/shorebird/shorebird.h b/shell/common/shorebird/shorebird.h new file mode 100644 index 0000000000000..3c3ae81fafa31 --- /dev/null +++ b/shell/common/shorebird/shorebird.h @@ -0,0 +1,17 @@ +#ifndef SHELL_COMMON_SHOREBIRD_H_ +#define SHELL_COMMON_SHOREBIRD_H_ + +#include "flutter/common/settings.h" + +namespace flutter { + +void ConfigureShorebird(std::string code_cache_path, + std::string app_storage_path, + flutter::Settings& settings, + const std::string& shorebird_yaml, + const std::string& version, + const std::string& version_code); + +} // namespace flutter + +#endif // SHELL_COMMON_SHOREBIRD_H_ diff --git a/shell/common/shorebird/snapshots_data_handle.cc b/shell/common/shorebird/snapshots_data_handle.cc new file mode 100644 index 0000000000000..0c6c5a45450a1 --- /dev/null +++ b/shell/common/shorebird/snapshots_data_handle.cc @@ -0,0 +1,144 @@ +#include "flutter/shell/common/shorebird/snapshots_data_handle.h" + +#include "third_party/dart/runtime/include/dart_native_api.h" + +namespace flutter { + +static std::unique_ptr DataMapping(const DartSnapshot& snapshot) { + auto ptr = snapshot.GetDataMapping(); + return std::make_unique(ptr, + Dart_SnapshotDataSize(ptr)); +} + +static std::unique_ptr InstructionsMapping( + const DartSnapshot& snapshot) { + auto ptr = snapshot.GetInstructionsMapping(); + return std::make_unique(ptr, + Dart_SnapshotInstrSize(ptr)); +} + +// The size of the snapshot data is the sum of the sizes of the blobs. +size_t SnapshotsDataHandle::FullSize() const { + size_t size = 0; + for (const auto& blob : blobs_) { + size += blob->GetSize(); + } + return size; +} + +// The offset into the snapshots data blobs as though they were a single +// contiguous buffer. +size_t SnapshotsDataHandle::AbsoluteOffsetForIndex(BlobsIndex index) { + if (index.blob >= blobs_.size()) { + FML_LOG(WARNING) << "Blob index " << index.blob + << " is larger than the number of blobs (" << blobs_.size() + << "). Returning full size (" << FullSize() << ")"; + return FullSize(); + } + if (index.offset > blobs_[index.blob]->GetSize()) { + FML_LOG(WARNING) << "Offset for blob " << index.blob << " (" << index.offset + << ") is larger than the blob size (" + << blobs_[index.blob]->GetSize() + << "). Returning index start of next blob"; + return AbsoluteOffsetForIndex({index.blob + 1, 0}); + } + size_t offset = 0; + for (size_t i = 0; i < index.blob; i++) { + offset += blobs_[i]->GetSize(); + } + offset += index.offset; + return offset; +} + +BlobsIndex SnapshotsDataHandle::IndexForAbsoluteOffset(int64_t offset, + BlobsIndex start_index) { + size_t start_offset = AbsoluteOffsetForIndex(start_index); + if (offset < 0) { + if ((size_t)abs(offset) > start_offset) { + FML_LOG(WARNING) + << "Offset is before the beginning of SnapshotsData. Returning 0, 0"; + return {0, 0}; + } + } else if (offset + start_offset >= FullSize()) { + FML_LOG(WARNING) << "Target offset is past the end of SnapshotsData (" + << offset + start_offset << ", blobs size:" << FullSize() + << "). Returning last blob index and offset"; + return {blobs_.size(), blobs_.back()->GetSize()}; + } + + size_t dest_offset = start_offset + offset; + BlobsIndex index = {0, 0}; + for (const auto& blob : blobs_) { + if (dest_offset < blob->GetSize()) { + // The remaining offset is within this blob. + index.offset = dest_offset; + break; + } + + index.blob++; + dest_offset -= blob->GetSize(); + } + return index; +} + +std::unique_ptr SnapshotsDataHandle::createForSnapshots( + const DartSnapshot& vm_snapshot, + const DartSnapshot& isolate_snapshot) { + // This needs to match the order in which the blobs are written out in + // analyze_snapshot --dump_blobs + std::vector> blobs; + blobs.push_back(DataMapping(vm_snapshot)); + blobs.push_back(DataMapping(isolate_snapshot)); + blobs.push_back(InstructionsMapping(vm_snapshot)); + blobs.push_back(InstructionsMapping(isolate_snapshot)); + return std::make_unique(std::move(blobs)); +} + +uintptr_t SnapshotsDataHandle::Read(uint8_t* buffer, uintptr_t length) { + uintptr_t bytes_read = 0; + // Copy current blob from current offset and possibly into the next blob + // until we have read length bytes. + while (bytes_read < length) { + if (current_index_.blob >= blobs_.size()) { + // We have read all blobs. + break; + } + intptr_t remaining_blob_length = + blobs_[current_index_.blob]->GetSize() - current_index_.offset; + if (remaining_blob_length <= 0) { + // We have read all bytes in this blob. + current_index_.blob++; + current_index_.offset = 0; + continue; + } + intptr_t bytes_to_read = fmin(length - bytes_read, remaining_blob_length); + memcpy(buffer + bytes_read, + blobs_[current_index_.blob]->GetMapping() + current_index_.offset, + bytes_to_read); + bytes_read += bytes_to_read; + current_index_.offset += bytes_to_read; + } + + return bytes_read; +} + +int64_t SnapshotsDataHandle::Seek(int64_t offset, int32_t whence) { + BlobsIndex start_index; + switch (whence) { + case SEEK_CUR: + start_index = current_index_; + break; + case SEEK_SET: + start_index = {0, 0}; + break; + case SEEK_END: + start_index = {blobs_.size(), blobs_.back()->GetSize()}; + break; + default: + FML_CHECK(false) << "Unrecognized whence value in Seek: " << whence; + } + current_index_ = IndexForAbsoluteOffset(offset, start_index); + return current_index_.offset; +} + +} // namespace flutter \ No newline at end of file diff --git a/shell/common/shorebird/snapshots_data_handle.h b/shell/common/shorebird/snapshots_data_handle.h new file mode 100644 index 0000000000000..40552236aadc3 --- /dev/null +++ b/shell/common/shorebird/snapshots_data_handle.h @@ -0,0 +1,47 @@ +#ifndef SHELL_COMMON_SHOREBIRD_SNAPSHOTS_DATA_HANDLE_H_ +#define SHELL_COMMON_SHOREBIRD_SNAPSHOTS_DATA_HANDLE_H_ + +#include "flutter/fml/file.h" +#include "flutter/runtime/dart_snapshot.h" +#include "third_party/dart/runtime/include/dart_tools_api.h" + +namespace flutter { + +// An offset into an indexed collection of buffers. blob is the index of the +// buffer, and offset is the offset into that buffer. +struct BlobsIndex { + size_t blob; + size_t offset; +}; + +// Implements a POSIX file I/O interface which allows us to provide the four +// data blobs of a Dart snapshot (vm_data, vm_instructions, isolate_data, +// isolate_instructions) to Rust as though it were a single piece of memory. +class SnapshotsDataHandle { + public: + // This would ideally be private, but we need to be able to call it from the + // static createForSnapshots method. + explicit SnapshotsDataHandle(std::vector> blobs) + : blobs_(std::move(blobs)) {} + + static std::unique_ptr createForSnapshots( + const DartSnapshot& vm_snapshot, + const DartSnapshot& isolate_snapshot); + + uintptr_t Read(uint8_t* buffer, uintptr_t length); + int64_t Seek(int64_t offset, int32_t whence); + + // The sum of all the blobs' sizes. + size_t FullSize() const; + + private: + size_t AbsoluteOffsetForIndex(BlobsIndex index); + BlobsIndex IndexForAbsoluteOffset(int64_t offset, BlobsIndex startIndex); + + BlobsIndex current_index_; + std::vector> blobs_; +}; + +} // namespace flutter + +#endif // SHELL_COMMON_SHOREBIRD_SNAPSHOTS_DATA_HANDLE_H_ diff --git a/shell/common/shorebird/snapshots_data_handle_unittests.cc b/shell/common/shorebird/snapshots_data_handle_unittests.cc new file mode 100644 index 0000000000000..2acf44a7b8973 --- /dev/null +++ b/shell/common/shorebird/snapshots_data_handle_unittests.cc @@ -0,0 +1,177 @@ +#include +#include +#include + +#include "flutter/shell/common/shorebird/snapshots_data_handle.h" + +#include "flutter/fml/mapping.h" +#include "flutter/runtime/dart_snapshot.h" +#include "flutter/testing/testing.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "testing/fixture_test.h" + +namespace flutter { +namespace testing { + +std::unique_ptr MakeHandle( + std::vector& blobs) { + // Map the strings into non-owned mappings: + std::vector> mappings = {}; + for (auto& blob : blobs) { + std::unique_ptr mapping = + std::make_unique( + reinterpret_cast(blob.data()), blob.size()); + mappings.push_back(std::move(mapping)); + } + auto handle = + std::make_unique(std::move(mappings)); + return handle; +} + +TEST(SnapshotsDataHandle, Read) { + std::vector blobs = {"abc", "def", "ghi", "jkl"}; + std::unique_ptr blobs_handle = MakeHandle(blobs); + + const size_t buffer_size = 12; + uint8_t buffer[buffer_size]; + std::fill(buffer, buffer + buffer_size, 0); + blobs_handle->Read(buffer, 6); + + EXPECT_EQ(buffer[0], 'a'); + EXPECT_EQ(buffer[1], 'b'); + EXPECT_EQ(buffer[2], 'c'); + EXPECT_EQ(buffer[3], 'd'); + EXPECT_EQ(buffer[4], 'e'); + EXPECT_EQ(buffer[5], 'f'); + + // Only the first 6 bytes should have been read. + EXPECT_EQ(buffer[6], 0); +} + +TEST(SnapshotsDataHandle, ReadAfterSeekWithPositiveOffset) { + std::vector blobs = {"abc", "def", "ghi", "jkl"}; + std::unique_ptr blobs_handle = MakeHandle(blobs); + + const size_t buffer_size = 20; + uint8_t buffer[buffer_size]; + std::fill(buffer, buffer + buffer_size, 0); + + blobs_handle->Seek(4, SEEK_CUR); + blobs_handle->Read(buffer, 6); + + EXPECT_EQ(buffer[0], 'e'); + EXPECT_EQ(buffer[1], 'f'); + EXPECT_EQ(buffer[2], 'g'); + EXPECT_EQ(buffer[3], 'h'); + EXPECT_EQ(buffer[4], 'i'); + EXPECT_EQ(buffer[5], 'j'); + + // Only the first 6 bytes should have been read. + EXPECT_EQ(buffer[6], 0); +} + +TEST(SnapshotsDataHandle, ReadAfterSeekWithNegativeOffset) { + std::vector blobs = {"abc", "def", "ghi", "jkl"}; + std::unique_ptr blobs_handle = MakeHandle(blobs); + + const size_t buffer_size = 20; + uint8_t buffer[buffer_size]; + std::fill(buffer, buffer + buffer_size, 0); + + blobs_handle->Read(buffer, 5); + EXPECT_EQ(buffer[0], 'a'); + EXPECT_EQ(buffer[1], 'b'); + EXPECT_EQ(buffer[2], 'c'); + EXPECT_EQ(buffer[3], 'd'); + EXPECT_EQ(buffer[4], 'e'); + EXPECT_EQ(buffer[5], 0); + + // Reset buffer + std::fill(buffer, buffer + buffer_size, 0); + + // Read 5, seeked back 4, should start reading at offset 1 ('b') + blobs_handle->Seek(-4, SEEK_CUR); + blobs_handle->Read(buffer, 6); + + EXPECT_EQ(buffer[0], 'b'); + EXPECT_EQ(buffer[1], 'c'); + EXPECT_EQ(buffer[2], 'd'); + EXPECT_EQ(buffer[3], 'e'); + EXPECT_EQ(buffer[4], 'f'); + EXPECT_EQ(buffer[5], 'g'); + EXPECT_EQ(buffer[6], 0); +} + +TEST(SnapshotsDataHandle, SeekPastEnd) { + std::vector blobs = {"abc", "def", "ghi", "jkl"}; + std::unique_ptr blobs_handle = MakeHandle(blobs); + + const size_t buffer_size = 20; + uint8_t buffer[buffer_size]; + std::fill(buffer, buffer + buffer_size, 0); + + // Seek 1 past the end + blobs_handle->Seek(blobs_handle->FullSize() + 1, SEEK_CUR); + + // Seek back 2 bytes and read 2 bytes + blobs_handle->Seek(-2, SEEK_CUR); + blobs_handle->Read(buffer, 2); + + EXPECT_EQ(buffer[0], 'k'); + EXPECT_EQ(buffer[1], 'l'); +} + +TEST(SnapshotsDataHandle, SeekBeforeBeginning) { + std::vector blobs = {"abc", "def", "ghi", "jkl"}; + std::unique_ptr blobs_handle = MakeHandle(blobs); + + const size_t buffer_size = 20; + uint8_t buffer[buffer_size]; + std::fill(buffer, buffer + buffer_size, 0); + + // Seek before the start of the blobs and read the first 2 bytes. + blobs_handle->Seek(-2, SEEK_CUR); + blobs_handle->Read(buffer, 2); + + EXPECT_EQ(buffer[0], 'a'); + EXPECT_EQ(buffer[1], 'b'); +} + +TEST(SnapshotsDataHandle, SeekFromBeginning) { + std::vector blobs = {"abc", "def", "ghi", "jkl"}; + std::unique_ptr blobs_handle = MakeHandle(blobs); + + const size_t buffer_size = 20; + uint8_t buffer[buffer_size]; + std::fill(buffer, buffer + buffer_size, 0); + + // Seek 10 bytes from current (the beginning) + blobs_handle->Seek(10, SEEK_CUR); + + // Seek 2 bytes from the beginning and read 2 bytes + blobs_handle->Seek(2, SEEK_SET); + blobs_handle->Read(buffer, 2); + + EXPECT_EQ(buffer[0], 'c'); + EXPECT_EQ(buffer[1], 'd'); +} + +TEST(SnapshotsDataHandle, SeekFromEnd) { + std::vector blobs = {"abc", "def", "ghi", "jkl"}; + std::unique_ptr blobs_handle = MakeHandle(blobs); + + const size_t buffer_size = 20; + uint8_t buffer[buffer_size]; + std::fill(buffer, buffer + buffer_size, 0); + + // Seek 2 bytes from the end and read 2 bytes + blobs_handle->Seek(-2, SEEK_END); + blobs_handle->Read(buffer, 2); + + EXPECT_EQ(buffer[0], 'k'); + EXPECT_EQ(buffer[1], 'l'); +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index d9f2774afb903..1adb615536f08 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -137,6 +137,7 @@ source_set("flutter_shell_native_src") { "//flutter/runtime", "//flutter/runtime:libdart", "//flutter/shell/common", + "//flutter/shell/common/shorebird", "//flutter/shell/platform/android/context", "//flutter/shell/platform/android/external_view_embedder", "//flutter/shell/platform/android/jni", @@ -149,6 +150,8 @@ source_set("flutter_shell_native_src") { public_configs = [ "//flutter:config" ] + include_dirs = [ "//flutter/updater" ] + defines = [] libs = [ @@ -156,6 +159,23 @@ source_set("flutter_shell_native_src") { "EGL", "GLESv2", ] + if (target_cpu == "arm") { + libs += [ "//third_party/updater/target/armv7-linux-androideabi/release/libupdater.a" ] + } else if (target_cpu == "arm64") { + libs += [ + "//third_party/updater/target/aarch64-linux-android/release/libupdater.a", + ] + } else if (target_cpu == "x64") { + libs += [ + "//third_party/updater/target/x86_64-linux-android/release/libupdater.a", + ] + } else if (target_cpu == "x86") { + libs += [ + "//third_party/updater/target/i686-linux-android/release/libupdater.a", + ] + } else { + assert(false, "Unsupported target_cpu") + } } action("gen_android_build_config_java") { @@ -681,6 +701,9 @@ if (target_cpu != "x86") { # TODO(godofredoc): Remove gen_snapshot and rename new_gen_snapshot when v2 migration is complete. # BUG: https://github.com/flutter/flutter/issues/105351 + # This has a somewhat misleading name. We (shorebird) have updated this target + # to include analyze_snapshot as well as gen_snapshot. Because this target is + # used elsewhere in the toolchain, we did not rename it. zip_bundle("new_gen_snapshot") { gen_snapshot_bin = "gen_snapshot" gen_snapshot_out_dir = get_label_info( @@ -688,6 +711,14 @@ if (target_cpu != "x86") { "root_out_dir") gen_snapshot_path = rebase_path("$gen_snapshot_out_dir/$gen_snapshot_bin") + analyze_snapshot_bin = "analyze_snapshot" + analyze_snapshot_out_dir = + get_label_info( + "//third_party/dart/runtime/bin:analyze_snapshot($host_toolchain)", + "root_out_dir") + analyze_snapshot_path = + rebase_path("$analyze_snapshot_out_dir/$analyze_snapshot_bin") + if (host_os == "linux") { output = "$android_zip_archive_dir/$full_platform_name-$flutter_runtime_mode/linux-x64.zip" } else if (host_os == "mac") { @@ -696,6 +727,8 @@ if (target_cpu != "x86") { output = "$android_zip_archive_dir/$full_platform_name-$flutter_runtime_mode/windows-x64.zip" gen_snapshot_bin = "gen_snapshot.exe" gen_snapshot_path = rebase_path("$root_out_dir/$gen_snapshot_bin") + analyze_snapshot_bin = "analyze_snapshot.exe" + analyze_snapshot_path = rebase_path("$root_out_dir/$analyze_snapshot_bin") } files = [ @@ -703,9 +736,16 @@ if (target_cpu != "x86") { source = gen_snapshot_path destination = gen_snapshot_bin }, + { + source = analyze_snapshot_path + destination = analyze_snapshot_bin + }, ] - deps = [ "//third_party/dart/runtime/bin:gen_snapshot($host_toolchain)" ] + deps = [ + "//third_party/dart/runtime/bin:analyze_snapshot($host_toolchain)", + "//third_party/dart/runtime/bin:gen_snapshot($host_toolchain)", + ] if (host_os == "mac") { deps += [ ":android_entitlement_config" ] @@ -719,7 +759,7 @@ if (target_cpu != "x86") { } } -if (host_os == "linux" && (target_cpu == "x64" || target_cpu == "arm64")) { +if (target_cpu == "arm64") { zip_bundle("analyze_snapshot") { deps = [ "//third_party/dart/runtime/bin:analyze_snapshot($host_toolchain)" ] diff --git a/shell/platform/android/android_exports.lst b/shell/platform/android/android_exports.lst index 3bea8e22e3814..12f3cb4751ce8 100644 --- a/shell/platform/android/android_exports.lst +++ b/shell/platform/android/android_exports.lst @@ -9,6 +9,14 @@ JNI_OnLoad; _binary_icudtl_dat_start; _binary_icudtl_dat_size; + shorebird_init; + shorebird_active_path; + shorebird_active_patch_number; + shorebird_free_string; + shorebird_check_for_update; + shorebird_update; + shorebird_next_boot_patch_number; + shorebird_current_boot_patch_number; local: *; }; diff --git a/shell/platform/android/flutter_main.cc b/shell/platform/android/flutter_main.cc index 782b373c21d24..3f35e001f3d70 100644 --- a/shell/platform/android/flutter_main.cc +++ b/shell/platform/android/flutter_main.cc @@ -23,10 +23,13 @@ #include "flutter/lib/ui/plugins/callback_cache.h" #include "flutter/runtime/dart_vm.h" #include "flutter/shell/common/shell.h" +#include "flutter/shell/common/shorebird/shorebird.h" #include "flutter/shell/common/switches.h" #include "third_party/dart/runtime/include/dart_tools_api.h" #include "third_party/skia/include/core/SkFontMgr.h" +#include "third_party/updater/library/include/updater.h" + namespace flutter { extern "C" { @@ -82,6 +85,9 @@ void FlutterMain::Init(JNIEnv* env, jstring kernelPath, jstring appStoragePath, jstring engineCachesPath, + jstring shorebirdYaml, + jstring version, + jstring versionCode, jlong initTimeMillis) { std::vector args; args.push_back("flutter"); @@ -117,8 +123,18 @@ void FlutterMain::Init(JNIEnv* env, flutter::DartCallbackCache::SetCachePath( fml::jni::JavaStringToString(env, appStoragePath)); - fml::paths::InitializeAndroidCachesPath( - fml::jni::JavaStringToString(env, engineCachesPath)); + auto code_cache_path = fml::jni::JavaStringToString(env, engineCachesPath); + auto app_storage_path = fml::jni::JavaStringToString(env, appStoragePath); + fml::paths::InitializeAndroidCachesPath(code_cache_path); + +#if FLUTTER_RELEASE + std::string shorebird_yaml = fml::jni::JavaStringToString(env, shorebirdYaml); + std::string version_string = fml::jni::JavaStringToString(env, version); + std::string version_code_string = + fml::jni::JavaStringToString(env, versionCode); + ConfigureShorebird(code_cache_path, app_storage_path, settings, + shorebird_yaml, version_string, version_code_string); +#endif flutter::DartCallbackCache::LoadCacheFromDisk(); @@ -206,6 +222,7 @@ bool FlutterMain::Register(JNIEnv* env) { { .name = "nativeInit", .signature = "(Landroid/content/Context;[Ljava/lang/String;Ljava/" + "lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/" "lang/String;Ljava/lang/String;Ljava/lang/String;J)V", .fnPtr = reinterpret_cast(&Init), }, diff --git a/shell/platform/android/flutter_main.h b/shell/platform/android/flutter_main.h index bc78efce2d46a..ce95ffd61ef78 100644 --- a/shell/platform/android/flutter_main.h +++ b/shell/platform/android/flutter_main.h @@ -36,6 +36,9 @@ class FlutterMain { jstring kernelPath, jstring appStoragePath, jstring engineCachesPath, + jstring shorebirdYaml, + jstring version, + jstring versionCode, jlong initTimeMillis); void SetupDartVMServiceUriCallback(JNIEnv* env); diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 1d66eeff9dc45..aff0ff629caad 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -5,6 +5,8 @@ package io.flutter.embedding.engine; import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.ColorSpace; @@ -37,7 +39,10 @@ import io.flutter.view.AccessibilityBridge; import io.flutter.view.FlutterCallbackInformation; import io.flutter.view.TextureRegistry; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.lang.ref.WeakReference; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -174,6 +179,9 @@ private static native void nativeInit( @Nullable String bundlePath, @NonNull String appStoragePath, @NonNull String engineCachesPath, + @Nullable String shorebirdYaml, + @Nullable String version, + @Nullable String versionCode, long initTimeMillis); /** @@ -199,8 +207,46 @@ public void init( Log.w(TAG, "FlutterJNI.init called more than once"); } + String version = null; + String versionCode = null; + try { + PackageInfo packageInfo = + context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + version = packageInfo.versionName; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + versionCode = String.valueOf(packageInfo.getLongVersionCode()); + } else { + versionCode = String.valueOf(packageInfo.versionCode); + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Failed to read app version. Shorebird updater can't run.", e); + } + + String shorebirdYaml = null; + try { + InputStream yaml = context.getAssets().open("flutter_assets/shorebird.yaml"); + BufferedReader r = new BufferedReader(new InputStreamReader(yaml)); + StringBuilder total = new StringBuilder(); + for (String line; (line = r.readLine()) != null; ) { + total.append(line).append('\n'); + } + shorebirdYaml = total.toString(); + Log.d(TAG, "shorebird.yaml: " + shorebirdYaml); + } catch (IOException e) { + Log.e(TAG, "Failed to load shorebird.yaml", e); + Log.e(TAG, "Did you remember to include shorebird.yaml in your pubspec.yaml's assets?"); + } + FlutterJNI.nativeInit( - context, args, bundlePath, appStoragePath, engineCachesPath, initTimeMillis); + context, + args, + bundlePath, + appStoragePath, + engineCachesPath, + shorebirdYaml, + version, + versionCode, + initTimeMillis); FlutterJNI.initCalled = true; } diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 5a65050fe0e11..1d3c980136e32 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -188,12 +188,14 @@ source_set("flutter_framework_source") { "//flutter/runtime", "//flutter/runtime:libdart", "//flutter/shell/common", + "//flutter/shell/common/shorebird", "//flutter/shell/platform/common:common_cpp_input", "//flutter/shell/platform/darwin/common", "//flutter/shell/platform/darwin/common:framework_common", "//flutter/shell/platform/embedder:embedder_as_internal_library", "//flutter/shell/profiling:profiling", "//flutter/third_party/spring_animation", + "//third_party/dart/runtime/bin:elf_loader", "//third_party/skia", ] @@ -202,6 +204,17 @@ source_set("flutter_framework_source") { "//flutter:config", ] + if (target_cpu == "arm64") { + libs = [ + "//third_party/updater/target/aarch64-apple-ios/release/libupdater.a", + ] + } else if (target_cpu == "x64") { + libs = + [ "//third_party/updater/target/x86_64-apple-ios/release/libupdater.a" ] + } else { + assert(false, "Unsupported target_cpu") + } + frameworks = [ "AudioToolbox.framework", "CoreMedia.framework", diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm index 70c1ac7a4742d..417b4c514a66f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm @@ -16,13 +16,16 @@ #include "flutter/common/task_runners.h" #include "flutter/fml/mapping.h" #include "flutter/fml/message_loop.h" +#include "flutter/fml/paths.h" #include "flutter/fml/platform/darwin/scoped_nsobject.h" #include "flutter/runtime/dart_vm.h" #include "flutter/shell/common/shell.h" +#include "flutter/shell/common/shorebird/shorebird.h" #include "flutter/shell/common/switches.h" #import "flutter/shell/platform/darwin/common/command_line.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" +#include "third_party/updater/library/include/updater.h" FLUTTER_ASSERT_NOT_ARC extern "C" { @@ -103,10 +106,12 @@ static BOOL DoesHardwareSupportWideGamut() { } if (flutter::DartVM::IsRunningPrecompiledCode()) { + NSLog(@"SANITY CHECK: Running precompiled code."); if (hasExplicitBundle) { NSString* executablePath = bundle.executablePath; if ([[NSFileManager defaultManager] fileExistsAtPath:executablePath]) { settings.application_library_path.push_back(executablePath.UTF8String); + NSLog(@"Using precompiled library from %@", executablePath); } } @@ -118,6 +123,7 @@ static BOOL DoesHardwareSupportWideGamut() { NSString* executablePath = [NSBundle bundleWithPath:libraryPath].executablePath; if (executablePath.length > 0) { settings.application_library_path.push_back(executablePath.UTF8String); + NSLog(@"Using library from %@", libraryPath); } } } @@ -132,6 +138,7 @@ static BOOL DoesHardwareSupportWideGamut() { [NSBundle bundleWithPath:applicationFrameworkPath].executablePath; if (executablePath.length > 0) { settings.application_library_path.push_back(executablePath.UTF8String); + NSLog(@"Using App.framework from %@", applicationFrameworkPath); } } } @@ -163,6 +170,33 @@ static BOOL DoesHardwareSupportWideGamut() { } } + NSString* assetsPath = [NSString stringWithUTF8String:settings.assets_path.c_str()]; + NSLog(@"ASSET PATH %@", assetsPath); + + // FIXME: This may not be the correct path (e.g., should it include the organization id?) + // See + // https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW13 + // /private/var/mobile/Containers/Data/Application/264477BF-6E38-47C9-AAD9-532BB842F197/Library/Application + // Support/shorebird/shorebird_updater + std::string cache_path = + fml::paths::JoinPaths({getenv("HOME"), "Library/Application Support/shorebird"}); + NSURL* shorebirdYamlPath = [NSURL URLWithString:@"shorebird.yaml" + relativeToURL:[NSURL fileURLWithPath:assetsPath]]; + NSString* appVersion = [mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + NSString* appBuildNumber = [mainBundle objectForInfoDictionaryKey:@"CFBundleVersion"]; + NSString* shorebirdYamlContents = [NSString stringWithContentsOfURL:shorebirdYamlPath + encoding:NSUTF8StringEncoding + error:nil]; + if (shorebirdYamlContents != nil) { + // Note: we intentionally pass cache_path twice. We provide two different directories + // to ConfigureShorebird because Android differentiates between data that persists + // between releases and data that does not. iOS does not make this distinction. + flutter::ConfigureShorebird(cache_path, cache_path, settings, shorebirdYamlContents.UTF8String, + appVersion.UTF8String, appBuildNumber.UTF8String); + } else { + NSLog(@"Failed to find shorebird.yaml, not starting updater."); + } + // Domain network configuration // Disabled in https://github.com/flutter/flutter/issues/72723. // Re-enable in https://github.com/flutter/flutter/issues/54448. diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index c3223766d4c01..ef5fc2b63a266 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -33,6 +33,8 @@ #import "flutter/shell/platform/embedder/embedder.h" #import "flutter/third_party/spring_animation/spring_animation.h" +#import + static constexpr int kMicrosecondsPerSecond = 1000 * 1000; static constexpr CGFloat kScrollViewContentSize = 2.0; @@ -231,6 +233,8 @@ - (void)sharedSetupWithProject:(nullable FlutterDartProject*)project if (!project) { project = [[[FlutterDartProject alloc] init] autorelease]; } + FML_LOG(INFO) << "CPU::Id(): " << dart::CPU::Id(); + FlutterView.forceSoftwareRendering = project.settings.enable_software_rendering; _weakFactory = std::make_unique>(self); auto engine = fml::scoped_nsobject{[[FlutterEngine alloc] diff --git a/sky/tools/create_full_ios_framework.py b/sky/tools/create_full_ios_framework.py index 9aac99671348f..d6138bf254f2c 100644 --- a/sky/tools/create_full_ios_framework.py +++ b/sky/tools/create_full_ios_framework.py @@ -115,6 +115,7 @@ def main(): ) generate_gen_snapshot(args, dst, x64_out_dir, arm64_out_dir) + generate_analyze_snapshot(args, dst, x64_out_dir, arm64_out_dir) zip_archive(dst) return 0 @@ -229,6 +230,7 @@ def zip_archive(dst): 'zip', '-r', 'artifacts.zip', + 'analyze_snapshot_arm64', 'gen_snapshot_arm64', 'Flutter.xcframework', 'entitlements.txt', @@ -282,5 +284,29 @@ def _generate_gen_snapshot(directory, destination): ]) +def generate_analyze_snapshot(args, dst, x64_out_dir, arm64_out_dir): + if x64_out_dir: + _generate_analyze_snapshot( + x64_out_dir, os.path.join(dst, 'analyze_snapshot_x64') + ) + + if arm64_out_dir: + _generate_analyze_snapshot( + os.path.join(arm64_out_dir, args.clang_dir), + os.path.join(dst, 'analyze_snapshot_arm64') + ) + + +def _generate_analyze_snapshot(directory, destination): + analyze_snapshot_dir = os.path.join(directory, 'analyze_snapshot') + if not os.path.isfile(analyze_snapshot_dir): + print('Cannot find analyze_snapshot at %s' % analyze_snapshot_dir) + sys.exit(1) + + subprocess.check_call([ + 'xcrun', 'bitcode_strip', '-r', analyze_snapshot_dir, '-o', destination + ]) + + if __name__ == '__main__': sys.exit(main()) diff --git a/testing/run_tests.py b/testing/run_tests.py index 64a17f4efdc09..27f2fa0dca6c5 100755 --- a/testing/run_tests.py +++ b/testing/run_tests.py @@ -419,6 +419,7 @@ def make_test(name, flags=None, extra_env=None): make_test('platform_view_android_delegate_unittests'), # https://github.com/flutter/flutter/issues/36295 make_test('shell_unittests'), + make_test('shorebird_unittests'), ] if is_windows(): From 515dbf635445323ba3223612e024d4d034b49ad1 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Tue, 16 Jan 2024 16:34:45 -0500 Subject: [PATCH 02/13] bump updater rev --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 8de3ce8e59ed1..a2b18c4f10ea4 100644 --- a/DEPS +++ b/DEPS @@ -23,7 +23,7 @@ vars = { 'dart_sdk_revision': '33a70656a13a065e939e22eda44de21bee326077', 'dart_sdk_git': 'git@github.com:shorebirdtech/dart-sdk.git', 'updater_git': 'https://github.com/shorebirdtech/updater.git', - 'updater_rev': 'c6f8b2933e1110750ad40669194c39df1f76eb98', + 'updater_rev': 'ed013eb257b63e4a70c8bcb656bbb5ecd8dff4b5', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From 51492944c59f15542156f2e44e6127e6063b3259 Mon Sep 17 00:00:00 2001 From: Eric Seidel Date: Tue, 16 Jan 2024 19:02:06 -0800 Subject: [PATCH 03/13] chore: roll dart to 77aa67d56717d3862d39ec1630b27d831acfaf0a --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index a2b18c4f10ea4..efcae4be7c0ca 100644 --- a/DEPS +++ b/DEPS @@ -20,7 +20,7 @@ vars = { 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', 'skia_revision': '795ed944ff5bde5916d193824589d3bacfa61a7d', - 'dart_sdk_revision': '33a70656a13a065e939e22eda44de21bee326077', + 'dart_sdk_revision': '77aa67d56717d3862d39ec1630b27d831acfaf0a', 'dart_sdk_git': 'git@github.com:shorebirdtech/dart-sdk.git', 'updater_git': 'https://github.com/shorebirdtech/updater.git', 'updater_rev': 'ed013eb257b63e4a70c8bcb656bbb5ecd8dff4b5', From 67efabc86a4d85f0e17045dfc57f736b7b91463c Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Wed, 17 Jan 2024 11:48:59 -0500 Subject: [PATCH 04/13] fix: initialize SnapshotsDataHandle current_index_ (#75) * fix: initialize SnapshotsDataHandle current_index_ * set current_index_ in a different place --- shell/common/shorebird/snapshots_data_handle.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/common/shorebird/snapshots_data_handle.h b/shell/common/shorebird/snapshots_data_handle.h index 40552236aadc3..3667f029c6c29 100644 --- a/shell/common/shorebird/snapshots_data_handle.h +++ b/shell/common/shorebird/snapshots_data_handle.h @@ -38,7 +38,7 @@ class SnapshotsDataHandle { size_t AbsoluteOffsetForIndex(BlobsIndex index); BlobsIndex IndexForAbsoluteOffset(int64_t offset, BlobsIndex startIndex); - BlobsIndex current_index_; + BlobsIndex current_index_ = {0, 0}; std::vector> blobs_; }; From eddcb8d55561ad26a3c6825a26a2127f22d96331 Mon Sep 17 00:00:00 2001 From: Eric Seidel Date: Thu, 18 Jan 2024 09:41:48 -0800 Subject: [PATCH 05/13] chore: roll dart to c3ce7094705ca64d573cea48966214f15f91479f --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index efcae4be7c0ca..4b2db3b3cd41b 100644 --- a/DEPS +++ b/DEPS @@ -20,7 +20,7 @@ vars = { 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', 'skia_revision': '795ed944ff5bde5916d193824589d3bacfa61a7d', - 'dart_sdk_revision': '77aa67d56717d3862d39ec1630b27d831acfaf0a', + 'dart_sdk_revision': 'c3ce7094705ca64d573cea48966214f15f91479f', 'dart_sdk_git': 'git@github.com:shorebirdtech/dart-sdk.git', 'updater_git': 'https://github.com/shorebirdtech/updater.git', 'updater_rev': 'ed013eb257b63e4a70c8bcb656bbb5ecd8dff4b5', From bd214d443f86bd80c4523bb9b745de89f6b2dade Mon Sep 17 00:00:00 2001 From: Eric Seidel Date: Thu, 18 Jan 2024 11:00:22 -0800 Subject: [PATCH 06/13] chore: roll dart to feb840571776e4387b1f6cf558d106e6fc4f739a --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 4b2db3b3cd41b..700a408864897 100644 --- a/DEPS +++ b/DEPS @@ -20,7 +20,7 @@ vars = { 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', 'skia_revision': '795ed944ff5bde5916d193824589d3bacfa61a7d', - 'dart_sdk_revision': 'c3ce7094705ca64d573cea48966214f15f91479f', + 'dart_sdk_revision': 'feb840571776e4387b1f6cf558d106e6fc4f739a', 'dart_sdk_git': 'git@github.com:shorebirdtech/dart-sdk.git', 'updater_git': 'https://github.com/shorebirdtech/updater.git', 'updater_rev': 'ed013eb257b63e4a70c8bcb656bbb5ecd8dff4b5', From 1a3e6860e86e76a14e0cdb28b181657308fff130 Mon Sep 17 00:00:00 2001 From: Eric Seidel Date: Tue, 23 Jan 2024 13:13:24 -0800 Subject: [PATCH 07/13] chore: roll Dart to 18b474166026f68316962a1fea4ff9280a315b83 --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 700a408864897..95f32d8cd4a07 100644 --- a/DEPS +++ b/DEPS @@ -20,7 +20,7 @@ vars = { 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', 'skia_revision': '795ed944ff5bde5916d193824589d3bacfa61a7d', - 'dart_sdk_revision': 'feb840571776e4387b1f6cf558d106e6fc4f739a', + 'dart_sdk_revision': '18b474166026f68316962a1fea4ff9280a315b83', 'dart_sdk_git': 'git@github.com:shorebirdtech/dart-sdk.git', 'updater_git': 'https://github.com/shorebirdtech/updater.git', 'updater_rev': 'ed013eb257b63e4a70c8bcb656bbb5ecd8dff4b5', From 1271d699bb40bd62f4d398ea9b57706e7273c4e4 Mon Sep 17 00:00:00 2001 From: Eric Seidel Date: Tue, 23 Jan 2024 13:26:19 -0800 Subject: [PATCH 08/13] chore: roll Dart to 00f223f1b9f8c8c434a6e35d09845096daac0087 --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 95f32d8cd4a07..8608dd837da7f 100644 --- a/DEPS +++ b/DEPS @@ -20,7 +20,7 @@ vars = { 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', 'skia_revision': '795ed944ff5bde5916d193824589d3bacfa61a7d', - 'dart_sdk_revision': '18b474166026f68316962a1fea4ff9280a315b83', + 'dart_sdk_revision': '00f223f1b9f8c8c434a6e35d09845096daac0087', 'dart_sdk_git': 'git@github.com:shorebirdtech/dart-sdk.git', 'updater_git': 'https://github.com/shorebirdtech/updater.git', 'updater_rev': 'ed013eb257b63e4a70c8bcb656bbb5ecd8dff4b5', From a212cfb62d2c8f722a98d4b8ebc764f920877855 Mon Sep 17 00:00:00 2001 From: Eric Seidel Date: Tue, 23 Jan 2024 17:55:45 -0800 Subject: [PATCH 09/13] chore: roll Dart to 273ead4b4d55cbf06b7447ef951b164324aa04fb --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 8608dd837da7f..db83ce472376d 100644 --- a/DEPS +++ b/DEPS @@ -20,7 +20,7 @@ vars = { 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', 'skia_revision': '795ed944ff5bde5916d193824589d3bacfa61a7d', - 'dart_sdk_revision': '00f223f1b9f8c8c434a6e35d09845096daac0087', + 'dart_sdk_revision': '273ead4b4d55cbf06b7447ef951b164324aa04fb', 'dart_sdk_git': 'git@github.com:shorebirdtech/dart-sdk.git', 'updater_git': 'https://github.com/shorebirdtech/updater.git', 'updater_rev': 'ed013eb257b63e4a70c8bcb656bbb5ecd8dff4b5', From 2a30285baf8d7f65a64fa65f401af4fa884bd779 Mon Sep 17 00:00:00 2001 From: Eric Seidel Date: Tue, 30 Jan 2024 14:51:51 -0800 Subject: [PATCH 10/13] Roll dart to e743e66a09fde2a7922ac56519155d2a562a5cc9 --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index db83ce472376d..50aee8681e5d8 100644 --- a/DEPS +++ b/DEPS @@ -20,7 +20,7 @@ vars = { 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', 'skia_revision': '795ed944ff5bde5916d193824589d3bacfa61a7d', - 'dart_sdk_revision': '273ead4b4d55cbf06b7447ef951b164324aa04fb', + 'dart_sdk_revision': 'e743e66a09fde2a7922ac56519155d2a562a5cc9', 'dart_sdk_git': 'git@github.com:shorebirdtech/dart-sdk.git', 'updater_git': 'https://github.com/shorebirdtech/updater.git', 'updater_rev': 'ed013eb257b63e4a70c8bcb656bbb5ecd8dff4b5', From b73e6f744e2aa0750bbd9ee760f5360813368282 Mon Sep 17 00:00:00 2001 From: Eric Seidel Date: Tue, 30 Jan 2024 15:08:37 -0800 Subject: [PATCH 11/13] chore: roll dart to 86be823f26b7b6a680be43e8a6f96eac3af32e56 --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 50aee8681e5d8..5f09e7af096da 100644 --- a/DEPS +++ b/DEPS @@ -20,7 +20,7 @@ vars = { 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', 'skia_revision': '795ed944ff5bde5916d193824589d3bacfa61a7d', - 'dart_sdk_revision': 'e743e66a09fde2a7922ac56519155d2a562a5cc9', + 'dart_sdk_revision': '86be823f26b7b6a680be43e8a6f96eac3af32e56', 'dart_sdk_git': 'git@github.com:shorebirdtech/dart-sdk.git', 'updater_git': 'https://github.com/shorebirdtech/updater.git', 'updater_rev': 'ed013eb257b63e4a70c8bcb656bbb5ecd8dff4b5', From 30a4aa12f119ec0bf39774746d7d35bd41eaeffe Mon Sep 17 00:00:00 2001 From: Eric Seidel Date: Tue, 30 Jan 2024 15:56:31 -0800 Subject: [PATCH 12/13] chore: roll dart to a043f468303266c8bc000714a7b6ac9241ce4e1a --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 5f09e7af096da..de582aa238bf6 100644 --- a/DEPS +++ b/DEPS @@ -20,7 +20,7 @@ vars = { 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', 'skia_revision': '795ed944ff5bde5916d193824589d3bacfa61a7d', - 'dart_sdk_revision': '86be823f26b7b6a680be43e8a6f96eac3af32e56', + 'dart_sdk_revision': 'a043f468303266c8bc000714a7b6ac9241ce4e1a', 'dart_sdk_git': 'git@github.com:shorebirdtech/dart-sdk.git', 'updater_git': 'https://github.com/shorebirdtech/updater.git', 'updater_rev': 'ed013eb257b63e4a70c8bcb656bbb5ecd8dff4b5', From cfeb6729b279b4eaea0fbd4d26a2bb39d6da4604 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Wed, 31 Jan 2024 15:52:11 -0600 Subject: [PATCH 13/13] chore: roll dart to 041c44649d5d43de8fa01a8d17539d84be69634d --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index de582aa238bf6..6c9da05e4325f 100644 --- a/DEPS +++ b/DEPS @@ -20,7 +20,7 @@ vars = { 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', 'skia_revision': '795ed944ff5bde5916d193824589d3bacfa61a7d', - 'dart_sdk_revision': 'a043f468303266c8bc000714a7b6ac9241ce4e1a', + 'dart_sdk_revision': '041c44649d5d43de8fa01a8d17539d84be69634d', 'dart_sdk_git': 'git@github.com:shorebirdtech/dart-sdk.git', 'updater_git': 'https://github.com/shorebirdtech/updater.git', 'updater_rev': 'ed013eb257b63e4a70c8bcb656bbb5ecd8dff4b5',