From 4815a63341be48b5177c3fbc498124115452242a Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Tue, 6 Aug 2024 14:29:01 -0700 Subject: [PATCH 01/23] chore: squash all our changes to a single commit See shorebird/dev-3.24.5 for prior commit history. --- BUILD.gn | 27 +- DEPS | 339 +++++++++--------- build/archives/BUILD.gn | 18 +- lib/snapshot/BUILD.gn | 26 +- runtime/dart_snapshot.cc | 105 ++++-- shell/common/BUILD.gn | 13 + shell/common/shell.cc | 10 + shell/common/shorebird/BUILD.gn | 56 +++ shell/common/shorebird/shorebird.cc | 223 ++++++++++++ shell/common/shorebird/shorebird.h | 21 ++ .../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 | 10 + 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 | 14 + .../framework/Source/FlutterDartProject.mm | 33 ++ .../framework/Source/FlutterViewController.mm | 4 + shell/platform/darwin/macos/BUILD.gn | 1 + .../macos/framework/Source/FlutterEngine.mm | 23 ++ shell/platform/embedder/BUILD.gn | 1 + shell/platform/embedder/embedder.cc | 10 + shell/platform/embedder/embedder.h | 39 ++ shell/platform/windows/platform_handler.cc | 8 +- shell/testing/BUILD.gn | 1 + sky/tools/create_full_ios_framework.py | 300 ++++++++++++++++ testing/run_tests.py | 1 + 30 files changed, 1563 insertions(+), 204 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 create mode 100644 sky/tools/create_full_ios_framework.py diff --git a/BUILD.gn b/BUILD.gn index bb2ab9ba0f534..ea94699b54971 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -112,14 +112,10 @@ group("flutter") { # path_ops "//flutter/tools/path_ops", - ] - if (host_os == "linux") { - public_deps += [ - # Built alongside gen_snapshot for 64 bit targets - "$dart_src/runtime/bin:analyze_snapshot", - ] - } + # Built alongside gen_snapshot arm64 targets. + "$dart_src/runtime/bin:analyze_snapshot", + ] if (full_dart_sdk) { public_deps += [ "//flutter/web_sdk" ] @@ -193,6 +189,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", @@ -319,3 +316,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 = + "$dart_src/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 5cc1fadc6b9eb..034e9d75a9ee0 100644 --- a/DEPS +++ b/DEPS @@ -15,6 +15,10 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'skia_revision': '6062afaa505bf7e6c727a20cafe4c7bee0f02df8', + "dart_sdk_revision": "4f9b5084a041a0e7cc26295da1da9109df43ac4c", + "dart_sdk_git": "git@github.com:shorebirdtech/dart-sdk.git", + "updater_git": "https://github.com/shorebirdtech/updater.git", + "updater_rev": "1e4efce65f28d54c7a806ea05defd2ca3eb94595", # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. @@ -258,22 +262,20 @@ vars = { 'fuchsia_gn_sdk_version': 'tHRCseOuPnZ5H4a7kb4Zl6YQ2rhEDWIzcEX3G9NPFhkC', } -gclient_gn_args_file = 'src/flutter/third_party/dart/build/config/gclient_args.gni' -gclient_gn_args = [ - 'checkout_llvm' -] +gclient_gn_args_file = "src/flutter/third_party/dart/build/config/gclient_args.gni" +gclient_gn_args = ["checkout_llvm"] # Only these hosts are allowed for dependencies in this DEPS file. # If you need to add a new host, contact chrome infrastructure team. allowed_hosts = [ - 'boringssl.googlesource.com', - 'chrome-infra-packages.appspot.com', - 'chromium.googlesource.com', - 'dart.googlesource.com', - 'flutter.googlesource.com', - 'llvm.googlesource.com', - 'skia.googlesource.com', - 'swiftshader.googlesource.com', + "boringssl.googlesource.com", + "chrome-infra-packages.appspot.com", + "chromium.googlesource.com", + "dart.googlesource.com", + "flutter.googlesource.com", + "llvm.googlesource.com", + "skia.googlesource.com", + "swiftshader.googlesource.com", ] deps = { @@ -339,7 +341,7 @@ deps = { # Var('flutter_git') + '/third_party/protobuf-gn' + '@' + Var('dart_protobuf_gn_rev'), 'src/flutter/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. @@ -632,6 +634,9 @@ deps = { 'src/flutter/third_party/ocmock': Var('flutter_git') + '/third_party/ocmock' + '@' + Var('ocmock_rev'), + 'src/third_party/updater': + Var('updater_git') + '@' + Var('updater_rev'), + 'src/flutter/third_party/libjpeg-turbo/src': Var('flutter_git') + '/third_party/libjpeg-turbo' + '@' + '0fb821f3b2e570b2783a94ccd9a2fb1f4916ae9f', @@ -1018,157 +1023,165 @@ deps = { } recursedeps = [ - 'src/flutter/third_party/vulkan-deps', + "src/flutter/third_party/vulkan-deps", ] hooks = [ - { - # Generate the Dart SDK's .dart_tool/package_confg.json file. - 'name': 'Generate .dart_tool/package_confg.json', - 'pattern': '.', - 'action': ['python3', 'src/flutter/third_party/dart/tools/generate_package_config.py'], - }, - { - # Generate the sdk/version file. - 'name': 'Generate sdk/version', - 'pattern': '.', - 'action': ['python3', 'src/flutter/third_party/dart/tools/generate_sdk_version_file.py'], - }, - { - # Update the Windows toolchain if necessary. - 'name': 'win_toolchain', - 'condition': 'download_windows_deps', - 'pattern': '.', - 'action': ['python3', 'src/build/vs_toolchain.py', 'update'], - }, - { - 'name': 'dia_dll', - 'pattern': '.', - 'condition': 'download_windows_deps', - 'action': [ - 'python3', - 'src/flutter/tools/dia_dll.py', - ], - }, - { - 'name': 'linux_sysroot_x64', - 'pattern': '.', - 'condition': 'download_linux_deps', - 'action': [ - 'python3', - 'src/build/linux/sysroot_scripts/install-sysroot.py', - '--arch=x64'], - }, - { - 'name': 'linux_sysroot_arm64', - 'pattern': '.', - 'condition': 'download_linux_deps', - 'action': [ - 'python3', - 'src/build/linux/sysroot_scripts/install-sysroot.py', - '--arch=arm64'], - }, - { - 'name': 'pub get --offline', - 'pattern': '.', - 'action': [ - 'python3', - 'src/flutter/tools/pub_get_offline.py', - ] - }, - { - 'name': 'Download Fuchsia SDK', - 'pattern': '.', - 'condition': 'download_fuchsia_deps and download_fuchsia_sdk', - 'action': [ - 'python3', - 'src/flutter/tools/download_fuchsia_sdk.py', - '--fail-loudly', - '--verbose', - '--host-os', - Var('host_os'), - '--fuchsia-sdk-path', - Var('fuchsia_sdk_path'), - ] - }, - { - 'name': 'Activate Emscripten SDK', - 'pattern': '.', - 'condition': 'download_emsdk', - 'action': [ - 'python3', - 'src/flutter/tools/activate_emsdk.py', - ] - }, - { - 'name': 'Setup githooks', - 'pattern': '.', - 'condition': 'setup_githooks', - 'action': [ - 'python3', - 'src/flutter/tools/githooks/setup.py', - ] - }, - { - 'name': 'impeller-cmake-example submodules', - 'pattern': '.', - 'condition': 'download_impeller_cmake_example', - 'action': [ - 'python3', - 'src/flutter/ci/impeller_cmake_build_test.py', - '--path', - 'flutter/third_party/impeller-cmake-example', - '--setup', - ] - }, - { - 'name': 'Download Fuchsia system images', - 'pattern': '.', - 'condition': 'run_fuchsia_emu', - 'action': [ - 'env', - 'DOWNLOAD_FUCHSIA_SDK={download_fuchsia_sdk}', - 'FUCHSIA_SDK_PATH={fuchsia_sdk_path}', - 'python3', - 'src/flutter/tools/fuchsia/with_envs.py', - 'src/flutter/tools/fuchsia/test_scripts/update_product_bundles.py', - 'terminal.x64,terminal.qemu-arm64', - ] - }, - # The following two scripts check if they are running in the LUCI - # environment, and do nothing if so. This is because Xcode is not yet - # installed in CI when these hooks are run. - { - 'name': 'Find the iOS device SDKs', - 'pattern': '.', - 'condition': 'host_os == "mac"', - 'action': [ - 'python3', - 'src/build/config/ios/ios_sdk.py', - # This cleans up entries under flutter/prebuilts for this script and the - # following script. - '--as-gclient-hook' - ] - }, - { - 'name': 'Find the macOS SDK', - 'pattern': '.', - 'condition': 'host_os == "mac"', - 'action': [ - 'python3', - 'src/build/mac/find_sdk.py', - '--as-gclient-hook', - Var('mac_sdk_min') - ] - }, - { - 'name': 'Generate Fuchsia GN build rules', - 'pattern': '.', - 'condition': 'download_fuchsia_deps', - 'action': [ - 'python3', - 'src/flutter/tools/fuchsia/with_envs.py', - 'src/flutter/tools/fuchsia/test_scripts/gen_build_defs.py', - ], - }, + { + # Generate the Dart SDK's .dart_tool/package_confg.json file. + "name": "Generate .dart_tool/package_confg.json", + "pattern": ".", + "action": [ + "python3", + "src/flutter/third_party/dart/tools/generate_package_config.py", + ], + }, + { + # Generate the sdk/version file. + "name": "Generate sdk/version", + "pattern": ".", + "action": [ + "python3", + "src/flutter/third_party/dart/tools/generate_sdk_version_file.py", + ], + }, + { + # Update the Windows toolchain if necessary. + "name": "win_toolchain", + "condition": "download_windows_deps", + "pattern": ".", + "action": ["python3", "src/build/vs_toolchain.py", "update"], + }, + { + "name": "dia_dll", + "pattern": ".", + "condition": "download_windows_deps", + "action": [ + "python3", + "src/flutter/tools/dia_dll.py", + ], + }, + { + "name": "linux_sysroot_x64", + "pattern": ".", + "condition": "download_linux_deps", + "action": [ + "python3", + "src/build/linux/sysroot_scripts/install-sysroot.py", + "--arch=x64", + ], + }, + { + "name": "linux_sysroot_arm64", + "pattern": ".", + "condition": "download_linux_deps", + "action": [ + "python3", + "src/build/linux/sysroot_scripts/install-sysroot.py", + "--arch=arm64", + ], + }, + { + "name": "pub get --offline", + "pattern": ".", + "action": [ + "python3", + "src/flutter/tools/pub_get_offline.py", + ], + }, + { + "name": "Download Fuchsia SDK", + "pattern": ".", + "condition": "download_fuchsia_deps and download_fuchsia_sdk", + "action": [ + "python3", + "src/flutter/tools/download_fuchsia_sdk.py", + "--fail-loudly", + "--verbose", + "--host-os", + Var("host_os"), + "--fuchsia-sdk-path", + Var("fuchsia_sdk_path"), + ], + }, + { + "name": "Activate Emscripten SDK", + "pattern": ".", + "condition": "download_emsdk", + "action": [ + "python3", + "src/flutter/tools/activate_emsdk.py", + ], + }, + { + "name": "Setup githooks", + "pattern": ".", + "condition": "setup_githooks", + "action": [ + "python3", + "src/flutter/tools/githooks/setup.py", + ], + }, + { + "name": "impeller-cmake-example submodules", + "pattern": ".", + "condition": "download_impeller_cmake_example", + "action": [ + "python3", + "src/flutter/ci/impeller_cmake_build_test.py", + "--path", + "flutter/third_party/impeller-cmake-example", + "--setup", + ], + }, + { + "name": "Download Fuchsia system images", + "pattern": ".", + "condition": "run_fuchsia_emu", + "action": [ + "env", + "DOWNLOAD_FUCHSIA_SDK={download_fuchsia_sdk}", + "FUCHSIA_SDK_PATH={fuchsia_sdk_path}", + "python3", + "src/flutter/tools/fuchsia/with_envs.py", + "src/flutter/tools/fuchsia/test_scripts/update_product_bundles.py", + "terminal.x64,terminal.qemu-arm64", + ], + }, + # The following two scripts check if they are running in the LUCI + # environment, and do nothing if so. This is because Xcode is not yet + # installed in CI when these hooks are run. + { + "name": "Find the iOS device SDKs", + "pattern": ".", + "condition": 'host_os == "mac"', + "action": [ + "python3", + "src/build/config/ios/ios_sdk.py", + # This cleans up entries under flutter/prebuilts for this script and the + # following script. + "--as-gclient-hook", + ], + }, + { + "name": "Find the macOS SDK", + "pattern": ".", + "condition": 'host_os == "mac"', + "action": [ + "python3", + "src/build/mac/find_sdk.py", + "--as-gclient-hook", + Var("mac_sdk_min"), + ], + }, + { + "name": "Generate Fuchsia GN build rules", + "pattern": ".", + "condition": "download_fuchsia_deps", + "action": [ + "python3", + "src/flutter/tools/fuchsia/with_envs.py", + "src/flutter/tools/fuchsia/test_scripts/gen_build_defs.py", + ], + }, ] diff --git a/build/archives/BUILD.gn b/build/archives/BUILD.gn index 97d9483a23aba..8082f7d199f06 100644 --- a/build/archives/BUILD.gn +++ b/build/archives/BUILD.gn @@ -45,6 +45,7 @@ generated_file("artifacts_entitlement_config") { if (build_engine_artifacts) { zip_bundle("artifacts") { deps = [ + "$dart_src/runtime/bin:analyze_snapshot", "$dart_src/runtime/bin:gen_snapshot", "//flutter/flutter_frontend_server:frontend_server", "//flutter/impeller/compiler:impellerc", @@ -139,6 +140,10 @@ if (build_engine_artifacts) { if (host_os == "mac") { deps += [ ":artifacts_entitlement_config" ] files += [ + { + source = "$root_out_dir/analyze_snapshot$exe" + destination = "analyze_snapshot$exe" + }, { source = "$target_gen_dir/entitlements.txt" destination = "entitlements.txt" @@ -308,14 +313,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 73611e45063ab..2e84d775c9578 100644 --- a/lib/snapshot/BUILD.gn +++ b/lib/snapshot/BUILD.gn @@ -37,11 +37,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 += [ "$dart_src/runtime/bin:analyze_snapshot($host_toolchain)" ] } } @@ -176,6 +179,25 @@ if (host_os == "mac" && target_os != "mac" && deps = [ "$dart_src/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( + "$dart_src/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 = [ "$dart_src/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..1a62b3406ef3e 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 || FML_OS_MACOSX + // 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 || FML_OS_MACOSX + } // !is_patch +#endif // Look inside the currently loaded process. { diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index 8e2d9f2174b3a..5e4b4d1f2c6f8 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -152,6 +152,8 @@ source_set("common") { "//flutter/skia", ] + include_dirs = [ "//flutter/updater" ] + if (impeller_supports_rendering) { sources += [ "snapshot_controller_impeller.cc", @@ -160,6 +162,17 @@ source_set("common") { deps += [ "//flutter/impeller" ] } + + # Needed to compile flutter_tester for macOS. + if (host_os == "mac" && target_os == "mac") { + if (target_cpu == "arm64") { + libs = [ "//third_party/updater/target/aarch64-apple-darwin/release/libupdater.a" ] + } else if (target_cpu == "x64") { + libs = [ + "//third_party/updater/target/x86_64-apple-darwin/release/libupdater.a", + ] + } + } } # These are in their own source_set to avoid a dependency cycle with //common/graphics diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 453845b3b7836..0878c020de259 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -43,6 +43,8 @@ #include "third_party/skia/include/core/SkGraphics.h" #include "third_party/tonic/common/log.h" +#include "third_party/updater/library/include/updater.h" + namespace flutter { constexpr char kSkiaChannel[] = "flutter/skia"; @@ -441,6 +443,14 @@ Shell::Shell(DartVMRef vm, is_gpu_disabled_sync_switch_(new fml::SyncSwitch(is_gpu_disabled)), weak_factory_gpu_(nullptr), weak_factory_(this) { + // FIXME: This is probably the wrong place to hook into. +#if FML_OS_ANDROID || FML_OS_IOS || FML_OS_MACOSX + 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."; if (!settings.enable_impeller && settings.warn_on_impeller_opt_out) { diff --git a/shell/common/shorebird/BUILD.gn b/shell/common/shorebird/BUILD.gn new file mode 100644 index 0000000000000..4e31c785af768 --- /dev/null +++ b/shell/common/shorebird/BUILD.gn @@ -0,0 +1,56 @@ +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", + "//flutter/shell/platform/embedder:embedder_headers", + ] + + 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..a77b85414e306 --- /dev/null +++ b/shell/common/shorebird/shorebird.cc @@ -0,0 +1,223 @@ + +#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(const ShorebirdFlutterProjectArgs& args, + flutter::Settings& settings) { + // cache_path is used for both code_cache and app_storage, as we don't persist + // any data between releases. args.app_path is appended to + // the settings.application_library_path vector at this function's call site. + ConfigureShorebird(args.cache_path, args.cache_path, settings, + args.shorebird_yaml_contents, args.app_version, + args.app_build_number); +} + +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); + + bool init_result; + // 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. + init_result = 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 || FML_OS_MACOSX + 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; + +#if FML_OS_IOS || FML_OS_MACOSX + // 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 + } else { + FML_LOG(INFO) << "Shorebird updater: no active patch."; + } + + // We are careful only to report a launch start in the case where it's the + // first time we've configured shorebird this process. Otherwise we could end + // up in a case where we report a launch start, but never a completion (e.g. + // from package:flutter_work_manager which sometimes creates a FlutterEngine + // (and thus configures shorebird) but never runs it. The proper fix for this + // is probably to move the launch_start() call to be later in the lifecycle + // (when the snapshot is loaded and run, rather than when FlutterEngine is + // initialized). This "hack" will still have a problem where FlutterEngine is + // initialized but never run before the app is quit, could still cause us to + // suddenly mark-bad a patch that was never actually attempted to launch. + if (!init_result) { + return; + } + + // 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(); + + 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..1c6cb8bcd4443 --- /dev/null +++ b/shell/common/shorebird/shorebird.h @@ -0,0 +1,21 @@ +#ifndef FLUTTER_SHELL_COMMON_SHOREBIRD_SHOREBIRD_H_ +#define FLUTTER_SHELL_COMMON_SHOREBIRD_SHOREBIRD_H_ + +#include "flutter/common/settings.h" +#include "shell/platform/embedder/embedder.h" + +namespace flutter { + +void ConfigureShorebird(const ShorebirdFlutterProjectArgs& args, + flutter::Settings& settings); + +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 // FLUTTER_SHELL_COMMON_SHOREBIRD_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..7a7ec5274d002 --- /dev/null +++ b/shell/common/shorebird/snapshots_data_handle.h @@ -0,0 +1,47 @@ +#ifndef FLUTTER_SHELL_COMMON_SHOREBIRD_SNAPSHOTS_DATA_HANDLE_H_ +#define FLUTTER_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_ = {0, 0}; + std::vector> blobs_; +}; + +} // namespace flutter + +#endif // FLUTTER_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 6f062431cf747..bde83714ec43b 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -159,6 +159,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", @@ -172,6 +173,8 @@ source_set("flutter_shell_native_src") { public_configs = [ "//flutter:config" ] + include_dirs = [ "//flutter/updater" ] + defines = [] libs = [ @@ -179,6 +182,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") { @@ -726,6 +746,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 = @@ -733,6 +756,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( + "$dart_src/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") { @@ -741,6 +772,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 = [ @@ -748,9 +781,16 @@ if (target_cpu != "x86") { source = gen_snapshot_path destination = gen_snapshot_bin }, + { + source = analyze_snapshot_path + destination = analyze_snapshot_bin + }, ] - deps = [ "$dart_src/runtime/bin:gen_snapshot($host_toolchain)" ] + deps = [ + "$dart_src/runtime/bin:analyze_snapshot($host_toolchain)", + "$dart_src/runtime/bin:gen_snapshot($host_toolchain)", + ] if (host_os == "mac") { deps += [ ":android_entitlement_config" ] @@ -764,7 +804,7 @@ if (target_cpu != "x86") { } } -if (host_os == "linux" && (target_cpu == "x64" || target_cpu == "arm64")) { +if (target_cpu == "arm64") { zip_bundle("analyze_snapshot") { deps = [ "$dart_src/runtime/bin:analyze_snapshot($host_toolchain)" ] diff --git a/shell/platform/android/android_exports.lst b/shell/platform/android/android_exports.lst index 198bff773dd74..90333c576066c 100644 --- a/shell/platform/android/android_exports.lst +++ b/shell/platform/android/android_exports.lst @@ -11,6 +11,16 @@ _binary_icudtl_dat_size; InternalFlutterGpu*; kInternalFlutterGpu*; + shorebird_init; + shorebird_active_path; + shorebird_active_patch_number; + shorebird_free_string; + shorebird_free_update_result; + shorebird_check_for_downloadable_update; + shorebird_update; + shorebird_update_with_result; + 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 184a2e98d4bd9..61e949b737356 100644 --- a/shell/platform/android/flutter_main.cc +++ b/shell/platform/android/flutter_main.cc @@ -21,6 +21,7 @@ #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 "flutter/shell/platform/android/android_context_vk_impeller.h" #include "flutter/shell/platform/android/context/android_context.h" @@ -30,6 +31,8 @@ #include "third_party/dart/runtime/include/dart_tools_api.h" #include "txt/platform.h" +#include "third_party/updater/library/include/updater.h" + namespace flutter { constexpr int kMinimumAndroidApiLevelForVulkan = 29; @@ -72,6 +75,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"); @@ -120,8 +126,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(); @@ -211,6 +227,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 c572b458fbc29..1d9f0ab9d6fb3 100644 --- a/shell/platform/android/flutter_main.h +++ b/shell/platform/android/flutter_main.h @@ -39,6 +39,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 389e866829f09..cf54bf600e676 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -7,6 +7,8 @@ import static io.flutter.Build.API_LEVELS; 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; @@ -40,7 +42,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; @@ -177,6 +182,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); /** @@ -202,8 +210,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 >= API_LEVELS.API_28) { + 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 ab58295121949..30d8eafdaafb5 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -52,6 +52,7 @@ source_set("flutter_framework_source_arc") { deps = [ ":flutter_framework_source", "//flutter/fml", + "//flutter/shell/common/shorebird", "//flutter/shell/platform/common:common_cpp_input", "//flutter/shell/platform/darwin/common:framework_common", "//flutter/third_party/icu", @@ -202,9 +203,11 @@ source_set("flutter_framework_source") { } deps += [ + "$dart_src/runtime/bin:elf_loader", "//flutter/fml", "//flutter/runtime", "//flutter/shell/common", + "//flutter/shell/common/shorebird", "//flutter/shell/platform/darwin/common", "//flutter/shell/platform/darwin/common:framework_common", "//flutter/shell/platform/embedder:embedder_as_internal_library", @@ -217,6 +220,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 9c64bfef3a364..2a8f3365777de 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm @@ -12,6 +12,8 @@ #include #include "flutter/common/constants.h" +#include "flutter/fml/paths.h" +#include "flutter/shell/common/shorebird/shorebird.h" #include "flutter/shell/common/switches.h" #import "flutter/shell/platform/darwin/common/command_line.h" @@ -96,10 +98,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); } } @@ -111,6 +115,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); } } } @@ -125,6 +130,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); } } } @@ -156,6 +162,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 ebf48bdd1076a..101de7adbc865 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -34,6 +34,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; @@ -238,6 +240,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/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index 3ce4bfc1f0a77..2975a38fb8d08 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -127,6 +127,7 @@ source_set("flutter_framework_source") { "//flutter/shell/platform/common:common_cpp_enums", "//flutter/shell/platform/common:common_cpp_input", "//flutter/shell/platform/common:common_cpp_switches", + "//flutter/shell/platform/darwin/common", "//flutter/shell/platform/darwin/common:availability_version_check", "//flutter/shell/platform/darwin/common:framework_common", "//flutter/shell/platform/darwin/graphics:graphics", diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 2c79ee079d4b9..c870f0d3f55ea 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -10,6 +10,7 @@ #include #include "flutter/common/constants.h" +#include "flutter/fml/paths.h" #include "flutter/shell/platform/common/app_lifecycle_state.h" #include "flutter/shell/platform/common/engine_switches.h" #include "flutter/shell/platform/embedder/embedder.h" @@ -679,6 +680,28 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint { [engine onVSync:baton]; }; + NSString* bundlePath = + [[NSBundle bundleWithURL:[NSBundle.mainBundle.privateFrameworksURL + URLByAppendingPathComponent:@"App.framework"]] bundlePath]; + bundlePath = [bundlePath stringByAppendingString:@"/App"]; + flutterArguments.shorebird_args.app_path = bundlePath.UTF8String; + NSString* assetsPath = _project.assetsPath; + NSURL* shorebirdYamlPath = [NSURL URLWithString:@"shorebird.yaml" + relativeToURL:[NSURL fileURLWithPath:assetsPath]]; + NSString* shorebirdYamlContents = [NSString stringWithContentsOfURL:shorebirdYamlPath + encoding:NSUTF8StringEncoding + error:nil]; + NSString* appVersion = + [NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + NSString* appBuildNumber = [NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleVersion"]; + flutterArguments.shorebird_args.app_version = appVersion.UTF8String; + flutterArguments.shorebird_args.app_build_number = appBuildNumber.UTF8String; + + std::string cache_path = + fml::paths::JoinPaths({getenv("HOME"), "Library", "Application Support", "shorebird"}); + flutterArguments.shorebird_args.cache_path = cache_path.c_str(); + flutterArguments.shorebird_args.shorebird_yaml_contents = shorebirdYamlContents.UTF8String; + FlutterRendererConfig rendererConfig = [_renderer createRendererConfig]; FlutterEngineResult result = _embedderAPI.Initialize( FLUTTER_ENGINE_VERSION, &rendererConfig, &flutterArguments, (__bridge void*)(self), &_engine); diff --git a/shell/platform/embedder/BUILD.gn b/shell/platform/embedder/BUILD.gn index 38c7e2db98e87..c620e74761979 100644 --- a/shell/platform/embedder/BUILD.gn +++ b/shell/platform/embedder/BUILD.gn @@ -110,6 +110,7 @@ template("embedder_source_set") { "//flutter/lib/ui", "//flutter/runtime:libdart", "//flutter/shell/common", + "//flutter/shell/common/shorebird", "//flutter/skia", "//flutter/third_party/tonic", ] diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 2adf56c592e0a..6f6ffc2b24df9 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -50,6 +50,7 @@ extern const intptr_t kPlatformStrongDillSize; #include "flutter/fml/paths.h" #include "flutter/fml/trace_event.h" #include "flutter/shell/common/rasterizer.h" +#include "flutter/shell/common/shorebird/shorebird.h" #include "flutter/shell/common/switches.h" #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/embedder/embedder_engine.h" @@ -2305,6 +2306,15 @@ FlutterEngineResult FlutterEngineInitialize(size_t version, "Could not infer the Flutter project to run from given arguments."); } + // Begin shorebird + if (args->shorebird_args.shorebird_yaml_contents) { + settings.application_library_path.push_back(args->shorebird_args.app_path); + flutter::ConfigureShorebird(args->shorebird_args, settings); + } else { + FML_LOG(INFO) << "[shorebird] No shorebird YAML contents provided."; + } + // End shorebird + // Create the engine but don't launch the shell or run the root isolate. auto embedder_engine = std::make_unique( std::move(thread_host), // diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 9e9b875a2cc36..482a4cad90f6c 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -2204,6 +2204,42 @@ typedef void (*FlutterLogMessageCallback)(const char* /* tag */, /// FlutterEngine instance in AOT mode. typedef struct _FlutterEngineAOTData* FlutterEngineAOTData; +typedef struct { + /// The version of the app (e.g., 1.0.0). + /// + /// The string can be collected after the call to `FlutterEngineInitialize` + /// returns. The string must be NULL terminated. + const char* app_version; + + /// The build number of the app (e.g., 1). + /// + /// The string can be collected after the call to `FlutterEngineInitialize` + /// returns. The string must be NULL terminated. + const char* app_build_number; + + /// The text contents of the shorebird.yaml file bundled with the compiled + /// app. Note that this is _not_ the same as the shorebird.yaml that exists in + /// the user's project. + /// + /// The string can be collected after the call to `FlutterEngineInitialize` + /// returns. The string must be NULL terminated. + const char* shorebird_yaml_contents; + + /// The path to the directory where Shorebird will store patches and state + /// data. + /// + /// The string can be collected after the call to `FlutterEngineInitialize` + /// returns. The string must be NULL terminated. + const char* cache_path; + + /// The path to the executable file. This is a Mach-O executable file on + /// macOS. + /// + /// The string can be collected after the call to `FlutterEngineInitialize` + /// returns. The string must be NULL terminated. + const char* app_path; +} ShorebirdFlutterProjectArgs; + typedef struct { /// The size of this struct. Must be sizeof(FlutterProjectArgs). size_t struct_size; @@ -2503,6 +2539,9 @@ typedef struct { /// being registered on the framework side. The callback is invoked from /// a task posted to the platform thread. FlutterChannelUpdateCallback channel_update_callback; + + /// Data used to initialize Shorebird as part of engine initialization. + ShorebirdFlutterProjectArgs shorebird_args; } FlutterProjectArgs; #ifndef FLUTTER_ENGINE_NO_PROTOTYPES diff --git a/shell/platform/windows/platform_handler.cc b/shell/platform/windows/platform_handler.cc index 197de59345351..48ebf0cf0411b 100644 --- a/shell/platform/windows/platform_handler.cc +++ b/shell/platform/windows/platform_handler.cc @@ -60,7 +60,7 @@ class ScopedGlobalMemory { memory_ = ::GlobalAlloc(flags, bytes); if (!memory_) { FML_LOG(ERROR) << "Unable to allocate global memory: " - << ::GetLastError(); + << static_cast(::GetLastError()); } } @@ -68,7 +68,7 @@ class ScopedGlobalMemory { if (memory_) { if (::GlobalFree(memory_) != nullptr) { FML_LOG(ERROR) << "Failed to free global allocation: " - << ::GetLastError(); + << static_cast(::GetLastError()); } } } @@ -175,12 +175,12 @@ std::variant ScopedClipboard::GetString() { HANDLE data = ::GetClipboardData(CF_UNICODETEXT); if (data == nullptr) { - return ::GetLastError(); + return static_cast(::GetLastError()); } ScopedGlobalLock locked_data(data); if (!locked_data.get()) { - return ::GetLastError(); + return static_cast(::GetLastError()); } return static_cast(locked_data.get()); } diff --git a/shell/testing/BUILD.gn b/shell/testing/BUILD.gn index 28a19c75d1bb9..ce47535e8db12 100644 --- a/shell/testing/BUILD.gn +++ b/shell/testing/BUILD.gn @@ -38,6 +38,7 @@ executable("testing") { deps = [ "$dart_src/runtime:libdart_jit", "$dart_src/runtime/bin:dart_io_api", + "$dart_src/runtime/bin:elf_loader", "//flutter/assets", "//flutter/common", "//flutter/flow", diff --git a/sky/tools/create_full_ios_framework.py b/sky/tools/create_full_ios_framework.py new file mode 100644 index 0000000000000..2738b8693302f --- /dev/null +++ b/sky/tools/create_full_ios_framework.py @@ -0,0 +1,300 @@ +#!/usr/bin/env python3 +# +# 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. + +# Generates and zip the ios flutter framework including the architecture +# dependent snapshot. + +import argparse +import os +import platform +import shutil +import subprocess +import sys + +from create_xcframework import create_xcframework # pylint: disable=import-error + +ARCH_SUBPATH = 'mac-arm64' if platform.processor() == 'arm' else 'mac-x64' +DSYMUTIL = os.path.join( + os.path.dirname(__file__), '..', '..', 'buildtools', ARCH_SUBPATH, 'clang', 'bin', 'dsymutil' +) + +buildroot_dir = os.path.abspath(os.path.join(os.path.realpath(__file__), '..', '..', '..', '..')) + + +def main(): + parser = argparse.ArgumentParser( + description=( + 'Creates Flutter.framework, Flutter.xcframework and ' + 'copies architecture-dependent gen_snapshot binaries to output dir' + ) + ) + + parser.add_argument('--dst', type=str, required=True) + parser.add_argument('--clang-dir', type=str, default='clang_x64') + parser.add_argument('--x64-out-dir', type=str) + parser.add_argument('--arm64-out-dir', type=str, required=True) + parser.add_argument('--simulator-x64-out-dir', type=str, required=True) + parser.add_argument('--simulator-arm64-out-dir', type=str, required=False) + parser.add_argument('--strip', action='store_true', default=False) + parser.add_argument('--dsym', action='store_true', default=False) + + args = parser.parse_args() + + dst = (args.dst if os.path.isabs(args.dst) else os.path.join(buildroot_dir, args.dst)) + + arm64_out_dir = ( + args.arm64_out_dir + if os.path.isabs(args.arm64_out_dir) else os.path.join(buildroot_dir, args.arm64_out_dir) + ) + + x64_out_dir = None + if args.x64_out_dir: + x64_out_dir = ( + args.x64_out_dir + if os.path.isabs(args.x64_out_dir) else os.path.join(buildroot_dir, args.x64_out_dir) + ) + + simulator_x64_out_dir = None + if args.simulator_x64_out_dir: + simulator_x64_out_dir = ( + args.simulator_x64_out_dir if os.path.isabs(args.simulator_x64_out_dir) else + os.path.join(buildroot_dir, args.simulator_x64_out_dir) + ) + + framework = os.path.join(dst, 'Flutter.framework') + simulator_framework = os.path.join(dst, 'sim', 'Flutter.framework') + arm64_framework = os.path.join(arm64_out_dir, 'Flutter.framework') + simulator_x64_framework = os.path.join(simulator_x64_out_dir, 'Flutter.framework') + + simulator_arm64_out_dir = None + if args.simulator_arm64_out_dir: + simulator_arm64_out_dir = ( + args.simulator_arm64_out_dir if os.path.isabs(args.simulator_arm64_out_dir) else + os.path.join(buildroot_dir, args.simulator_arm64_out_dir) + ) + + if args.simulator_arm64_out_dir is not None: + simulator_arm64_framework = os.path.join(simulator_arm64_out_dir, 'Flutter.framework') + + if not os.path.isdir(arm64_framework): + print('Cannot find iOS arm64 Framework at %s' % arm64_framework) + return 1 + + if not os.path.isdir(simulator_x64_framework): + print('Cannot find iOS x64 simulator Framework at %s' % simulator_framework) + return 1 + + if not os.path.isfile(DSYMUTIL): + print('Cannot find dsymutil at %s' % DSYMUTIL) + return 1 + + create_framework( + args, dst, framework, arm64_framework, simulator_framework, simulator_x64_framework, + simulator_arm64_framework + ) + + extension_safe_dst = os.path.join(dst, 'extension_safe') + create_extension_safe_framework( + args, extension_safe_dst, '%s_extension_safe' % arm64_out_dir, + '%s_extension_safe' % simulator_x64_out_dir, '%s_extension_safe' % simulator_arm64_out_dir + ) + + 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 + +def create_extension_safe_framework( # pylint: disable=too-many-arguments + args, dst, arm64_out_dir, simulator_x64_out_dir, simulator_arm64_out_dir +): + framework = os.path.join(dst, 'Flutter.framework') + simulator_framework = os.path.join(dst, 'sim', 'Flutter.framework') + arm64_framework = os.path.join(arm64_out_dir, 'Flutter.framework') + simulator_x64_framework = os.path.join(simulator_x64_out_dir, 'Flutter.framework') + simulator_arm64_framework = os.path.join(simulator_arm64_out_dir, 'Flutter.framework') + + if not os.path.isdir(arm64_framework): + print('Cannot find extension safe iOS arm64 Framework at %s' % arm64_framework) + return 1 + + if not os.path.isdir(simulator_x64_framework): + print('Cannot find extension safe iOS x64 simulator Framework at %s' % simulator_x64_framework) + return 1 + + create_framework( + args, dst, framework, arm64_framework, simulator_framework, simulator_x64_framework, + simulator_arm64_framework + ) + return 0 + +def create_framework( # pylint: disable=too-many-arguments + args, dst, framework, arm64_framework, simulator_framework, + simulator_x64_framework, simulator_arm64_framework +): + arm64_dylib = os.path.join(arm64_framework, 'Flutter') + simulator_x64_dylib = os.path.join(simulator_x64_framework, 'Flutter') + simulator_arm64_dylib = os.path.join(simulator_arm64_framework, 'Flutter') + if not os.path.isfile(arm64_dylib): + print('Cannot find iOS arm64 dylib at %s' % arm64_dylib) + return 1 + + if not os.path.isfile(simulator_x64_dylib): + print('Cannot find iOS simulator dylib at %s' % simulator_x64_dylib) + return 1 + + # Compute dsym output paths, if enabled. + framework_dsym = None + simulator_dsym = None + if args.dsym: + framework_dsym = framework + '.dSYM' + simulator_dsym = simulator_framework + '.dSYM' + + # Emit the framework for physical devices. + shutil.rmtree(framework, True) + shutil.copytree(arm64_framework, framework) + framework_binary = os.path.join(framework, 'Flutter') + process_framework(args, dst, framework_binary, framework_dsym) + + # Emit the framework for simulators. + if args.simulator_arm64_out_dir is not None: + shutil.rmtree(simulator_framework, True) + shutil.copytree(simulator_arm64_framework, simulator_framework) + + simulator_framework_binary = os.path.join(simulator_framework, 'Flutter') + + # Create the arm64/x64 simulator fat framework. + subprocess.check_call([ + 'lipo', simulator_x64_dylib, simulator_arm64_dylib, '-create', '-output', + simulator_framework_binary + ]) + process_framework(args, dst, simulator_framework_binary, simulator_dsym) + else: + simulator_framework = simulator_x64_framework + + # Create XCFramework from the arm-only fat framework and the arm64/x64 + # simulator frameworks, or just the x64 simulator framework if only that one + # exists. + xcframeworks = [simulator_framework, framework] + dsyms = [simulator_dsym, framework_dsym] if args.dsym else None + create_xcframework(location=dst, name='Flutter', frameworks=xcframeworks, dsyms=dsyms) + + # Add the x64 simulator into the fat framework. + subprocess.check_call([ + 'lipo', arm64_dylib, simulator_x64_dylib, '-create', '-output', framework_binary + ]) + + process_framework(args, dst, framework_binary, framework_dsym) + return 0 + + +def embed_codesign_configuration(config_path, contents): + with open(config_path, 'w') as file: + file.write('\n'.join(contents) + '\n') + + +def zip_archive(dst): + ios_file_with_entitlements = ['gen_snapshot_arm64'] + ios_file_without_entitlements = [ + 'Flutter.xcframework/ios-arm64/Flutter.framework/Flutter', + 'Flutter.xcframework/ios-arm64/dSYMs/Flutter.framework.dSYM/Contents/Resources/DWARF/Flutter', + 'Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter', + 'Flutter.xcframework/ios-arm64_x86_64-simulator/dSYMs/Flutter.framework.dSYM/Contents/Resources/DWARF/Flutter', # pylint: disable=line-too-long + 'extension_safe/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter', + 'extension_safe/Flutter.xcframework/ios-arm64/dSYMs/Flutter.framework.dSYM/Contents/Resources/DWARF/Flutter', # pylint: disable=line-too-long + 'extension_safe/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter', + 'extension_safe/Flutter.xcframework/ios-arm64_x86_64-simulator/dSYMs/Flutter.framework.dSYM/Contents/Resources/DWARF/Flutter' # pylint: disable=line-too-long + ] + embed_codesign_configuration(os.path.join(dst, 'entitlements.txt'), ios_file_with_entitlements) + + embed_codesign_configuration( + os.path.join(dst, 'without_entitlements.txt'), ios_file_without_entitlements + ) + + subprocess.check_call([ + 'zip', + '-r', + 'artifacts.zip', + 'analyze_snapshot_arm64', + 'gen_snapshot_arm64', + 'Flutter.xcframework', + 'entitlements.txt', + 'without_entitlements.txt', + 'extension_safe/Flutter.xcframework', + ], + cwd=dst) + + # Generate Flutter.dSYM.zip for manual symbolification. + # + # Historically, the framework dSYM was named Flutter.dSYM, so in order to + # remain backward-compatible with existing instructions in docs/Crashes.md + # and existing tooling such as dart-lang/dart_ci, we rename back to that name + # + # TODO(cbracken): remove these archives and the upload steps once we bundle + # dSYMs in app archives. https://github.com/flutter/flutter/issues/116493 + framework_dsym = os.path.join(dst, 'Flutter.framework.dSYM') + if os.path.exists(framework_dsym): + renamed_dsym = framework_dsym.replace('Flutter.framework.dSYM', 'Flutter.dSYM') + os.rename(framework_dsym, renamed_dsym) + subprocess.check_call(['zip', '-r', 'Flutter.dSYM.zip', 'Flutter.dSYM'], cwd=dst) + + extension_safe_dsym = os.path.join(dst, 'extension_safe', 'Flutter.framework.dSYM') + if os.path.exists(extension_safe_dsym): + renamed_dsym = extension_safe_dsym.replace('Flutter.framework.dSYM', 'Flutter.dSYM') + os.rename(extension_safe_dsym, renamed_dsym) + subprocess.check_call(['zip', '-r', 'extension_safe_Flutter.dSYM.zip', 'Flutter.dSYM'], cwd=dst) + + +def process_framework(args, dst, framework_binary, dsym): + if dsym: + subprocess.check_call([DSYMUTIL, '-o', dsym, framework_binary]) + + if args.strip: + # copy unstripped + unstripped_out = os.path.join(dst, 'Flutter.unstripped') + shutil.copyfile(framework_binary, unstripped_out) + subprocess.check_call(['strip', '-x', '-S', framework_binary]) + + +def generate_gen_snapshot(args, dst, x64_out_dir, arm64_out_dir): + if x64_out_dir: + _generate_gen_snapshot(x64_out_dir, os.path.join(dst, 'gen_snapshot_x64')) + + if arm64_out_dir: + _generate_gen_snapshot( + os.path.join(arm64_out_dir, args.clang_dir), os.path.join(dst, 'gen_snapshot_arm64') + ) + + +def _generate_gen_snapshot(directory, destination): + gen_snapshot_dir = os.path.join(directory, 'gen_snapshot') + if not os.path.isfile(gen_snapshot_dir): + print('Cannot find gen_snapshot at %s' % gen_snapshot_dir) + sys.exit(1) + + subprocess.check_call(['xcrun', 'bitcode_strip', '-r', gen_snapshot_dir, '-o', 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 d66e4e98dc87a..2725a22fb3b64 100755 --- a/testing/run_tests.py +++ b/testing/run_tests.py @@ -444,6 +444,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 faa0ddf04fc54aca73cc96b417fa1c5e85bb3398 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Thu, 12 Dec 2024 13:12:33 -0600 Subject: [PATCH 02/23] fix: remove flutter/fml/size include --- shell/common/shorebird/shorebird.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/shell/common/shorebird/shorebird.cc b/shell/common/shorebird/shorebird.cc index a77b85414e306..89d49d68fafa1 100644 --- a/shell/common/shorebird/shorebird.cc +++ b/shell/common/shorebird/shorebird.cc @@ -14,7 +14,6 @@ #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" From e140099f56833d94c9621094d02515b8db31bad6 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Thu, 12 Dec 2024 16:21:15 -0600 Subject: [PATCH 03/23] fix: use shorebird buildroot --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 034e9d75a9ee0..946e68c20dc8c 100644 --- a/DEPS +++ b/DEPS @@ -279,7 +279,7 @@ allowed_hosts = [ ] deps = { - 'src': 'https://github.com/flutter/buildroot.git' + '@' + 'f85c3be4bf808add6ba867b8ff7943fd235b7b5e', + 'src': 'https://github.com/shorebirdtech/buildroot.git' + '@' + '4c5c8abd2ab1ac7c2c029503c71450a87bf94307', 'src/flutter/third_party/depot_tools': Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '580b4ff3f5cd0dcaa2eacda28cefe0f45320e8f7', From d5c77fef9ae0c444ed623653e510c59d53513842 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Fri, 13 Dec 2024 11:58:59 -0500 Subject: [PATCH 04/23] fix: generate analyze snapshot for arm builds --- lib/snapshot/BUILD.gn | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/snapshot/BUILD.gn b/lib/snapshot/BUILD.gn index 2e84d775c9578..6cfd629183bb8 100644 --- a/lib/snapshot/BUILD.gn +++ b/lib/snapshot/BUILD.gn @@ -37,10 +37,11 @@ group("generate_snapshot_bins") { deps += [ ":create_macos_gen_snapshots" ] } else if (host_os == "mac" && (target_cpu == "arm" || target_cpu == "arm64")) { - deps += [ - ":create_arm_analyze_snapshot", - ":create_arm_gen_snapshot", - ] + deps += [ ":create_arm_gen_snapshot" ] + } + + if (host_os == "mac" && target_os != "mac" && (target_cpu == "arm" || target_cpu == "arm64")) { + deps += [ ":create_arm_analyze_snapshot" ] } # Build analyze_snapshot for 64-bit target CPUs. From b71e7e01b4417a240049b8492551b4b9c1b90478 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Fri, 13 Dec 2024 11:59:21 -0500 Subject: [PATCH 05/23] formatting --- lib/snapshot/BUILD.gn | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/snapshot/BUILD.gn b/lib/snapshot/BUILD.gn index 6cfd629183bb8..0f062ea17d7be 100644 --- a/lib/snapshot/BUILD.gn +++ b/lib/snapshot/BUILD.gn @@ -40,7 +40,8 @@ group("generate_snapshot_bins") { deps += [ ":create_arm_gen_snapshot" ] } - if (host_os == "mac" && target_os != "mac" && (target_cpu == "arm" || target_cpu == "arm64")) { + if (host_os == "mac" && target_os != "mac" && + (target_cpu == "arm" || target_cpu == "arm64")) { deps += [ ":create_arm_analyze_snapshot" ] } From 34abb61d79e7bd59122715a3aa66fe4dcaec055c Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Fri, 13 Dec 2024 14:16:48 -0500 Subject: [PATCH 06/23] fix: update how we generate analyze_snapshot (#92) * fix: update how we generate analyze_snapshot * formatting --- lib/snapshot/BUILD.gn | 92 +++++++++++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 25 deletions(-) diff --git a/lib/snapshot/BUILD.gn b/lib/snapshot/BUILD.gn index 0f062ea17d7be..2509e27cfdbd3 100644 --- a/lib/snapshot/BUILD.gn +++ b/lib/snapshot/BUILD.gn @@ -34,17 +34,15 @@ group("generate_snapshot_bins") { # For macOS target builds: needed for both target CPUs (arm64, x64). # For iOS, Android target builds: all AOT target CPUs are arm/arm64. if (host_os == "mac" && (target_os == "mac" || target_os == "ios")) { - deps += [ ":create_macos_gen_snapshots" ] + deps += [ + ":create_macos_analyze_snapshots", + ":create_macos_gen_snapshots", + ] } else if (host_os == "mac" && (target_cpu == "arm" || target_cpu == "arm64")) { deps += [ ":create_arm_gen_snapshot" ] } - if (host_os == "mac" && target_os != "mac" && - (target_cpu == "arm" || target_cpu == "arm64")) { - deps += [ ":create_arm_analyze_snapshot" ] - } - # Build analyze_snapshot for 64-bit target CPUs. if (target_cpu == "arm64") { deps += [ "$dart_src/runtime/bin:analyze_snapshot($host_toolchain)" ] @@ -181,25 +179,6 @@ if (host_os == "mac" && target_os != "mac" && deps = [ "$dart_src/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( - "$dart_src/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 = [ "$dart_src/runtime/bin:analyze_snapshot($host_toolchain)" ] - visibility = [ ":*" ] - } } # Creates a `gen_snapshot` binary suffixed with the target CPU architecture. @@ -258,6 +237,69 @@ if (host_os == "mac" && (target_os == "mac" || target_os == "ios")) { ":create_macos_gen_snapshot_x64_${target_cpu}", ] } + + # Added by shorebird. + # analyze_snapshot targets below were copied from the gen_snapshot targets + # above to allow us to include analyze_snapshot in the artifacts generated + # for create_ios_framework.py. + template("build_mac_analyze_snapshot") { + assert(defined(invoker.host_arch)) + host_cpu = invoker.host_arch + + build_toolchain = "//build/toolchain/mac:clang_$host_cpu" + analyze_snapshot_target_name = "analyze_snapshot" + + # At this point, the gen_snapshot equivalent changes + # gen_ snapshot_target_name to "gen_snapshot_host_targeting_host". There is + # no equivalent for analyze_snapshot, so we don't do that here. + # + # It's unclear whether we need to do so now, but we didn't previously, so + # we're not doing it now until we have a reason to. + + analyze_snapshot_target = + "$dart_src/runtime/bin:$analyze_snapshot_target_name($build_toolchain)" + + copy(target_name) { + # The toolchain-specific output directory. For cross-compiles, this is a + # clang-x64 or clang-arm64 subdirectory of the top-level build directory. + output_dir = get_label_info(analyze_snapshot_target, "root_out_dir") + + sources = [ "${output_dir}/${analyze_snapshot_target_name}" ] + outputs = [ + "${root_out_dir}/artifacts_$host_cpu/analyze_snapshot_${target_cpu}", + ] + deps = [ analyze_snapshot_target ] + } + } + + build_mac_analyze_snapshot( + "create_macos_analyze_snapshot_arm64_${target_cpu}") { + host_arch = "arm64" + } + + build_mac_analyze_snapshot( + "create_macos_analyze_snapshot_x64_${target_cpu}") { + host_arch = "x64" + } + + action("create_macos_analyze_snapshots") { + script = "//flutter/sky/tools/create_macos_binary.py" + outputs = [ "${root_out_dir}/analyze_snapshot_${target_cpu}" ] + args = [ + "--in-arm64", + rebase_path( + "${root_out_dir}/artifacts_arm64/analyze_snapshot_${target_cpu}"), + "--in-x64", + rebase_path( + "${root_out_dir}/artifacts_x64/analyze_snapshot_${target_cpu}"), + "--out", + rebase_path("${root_out_dir}/analyze_snapshot_${target_cpu}"), + ] + deps = [ + ":create_macos_analyze_snapshot_arm64_${target_cpu}", + ":create_macos_analyze_snapshot_x64_${target_cpu}", + ] + } } source_set("snapshot") { From c9e315f6154482293e821f3a4f73c2f114375f81 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Wed, 18 Dec 2024 14:11:05 -0800 Subject: [PATCH 07/23] chore: bump updater rev --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 946e68c20dc8c..7f462412fab76 100644 --- a/DEPS +++ b/DEPS @@ -18,7 +18,7 @@ vars = { "dart_sdk_revision": "4f9b5084a041a0e7cc26295da1da9109df43ac4c", "dart_sdk_git": "git@github.com:shorebirdtech/dart-sdk.git", "updater_git": "https://github.com/shorebirdtech/updater.git", - "updater_rev": "1e4efce65f28d54c7a806ea05defd2ca3eb94595", + "updater_rev": "38aadee1c5e0a072dd40ee8778bea2f67dd8ec46", # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From d1d4efb3e8fa98b23e0c838811ad5fc33bd2e685 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Thu, 19 Dec 2024 15:52:43 -0600 Subject: [PATCH 08/23] chore: roll updater to `54e1e2ce2f82e5fe004bd0730c00d4310ab637dc` (re-export shorebird_check_for_update) --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 7f462412fab76..eae0922c4b51c 100644 --- a/DEPS +++ b/DEPS @@ -18,7 +18,7 @@ vars = { "dart_sdk_revision": "4f9b5084a041a0e7cc26295da1da9109df43ac4c", "dart_sdk_git": "git@github.com:shorebirdtech/dart-sdk.git", "updater_git": "https://github.com/shorebirdtech/updater.git", - "updater_rev": "38aadee1c5e0a072dd40ee8778bea2f67dd8ec46", + "updater_rev": "54e1e2ce2f82e5fe004bd0730c00d4310ab637dc", # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From c2653ea336247e52658033a4759dde90ee1120bc Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Thu, 19 Dec 2024 17:08:03 -0600 Subject: [PATCH 09/23] chore: export `shorebird_check_for_update` for android --- shell/platform/android/android_exports.lst | 1 + 1 file changed, 1 insertion(+) diff --git a/shell/platform/android/android_exports.lst b/shell/platform/android/android_exports.lst index 90333c576066c..1f483306bbab0 100644 --- a/shell/platform/android/android_exports.lst +++ b/shell/platform/android/android_exports.lst @@ -17,6 +17,7 @@ shorebird_free_string; shorebird_free_update_result; shorebird_check_for_downloadable_update; + shorebird_check_for_update; shorebird_update; shorebird_update_with_result; shorebird_next_boot_patch_number; From 486ae289257f4907e076bffe65dfcd5782632176 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Fri, 3 Jan 2025 17:27:07 -0500 Subject: [PATCH 10/23] Windows build (#94) * chore: squash all our changes to a single commit See shorebird/dev-3.24.5 for prior commit history. * fix: remove flutter/fml/size include * fix: use shorebird buildroot * fix: generate analyze snapshot for arm builds * formatting * fix: update how we generate analyze_snapshot (#92) * fix: update how we generate analyze_snapshot * formatting * build windows * Windows Support * Format * Make patching work * Formatting * Formatting * Formatting * Improve windows * Formatting * Formatting * Formatting * Revert unneeded changes * Revert changes * Bump updater rev * Remove prints * Cleanup * fix deps * Use correct local appdata path * Formatting * consolidate fetching build number and release version * format * remove unnecessary pointer * Introduce ReleaseVersion struct * formatting * Formatting * More formatting * Comments * Formatting * More comments * Formatting * Remove print statements * Remove comments * Use in-out param for ConfigureShorebird * formatting * fix deps * Remove extra include of updater.lib * Cleanup * cleanup * Formatting * Use ERROR_SUCCESS * Do not use Error Success * Add comment * GetLocalAppDataPath feedback * Refactor --------- Co-authored-by: Felix Angelov --- DEPS | 2 +- common/config.gni | 4 + shell/common/shell.cc | 2 +- shell/common/shorebird/BUILD.gn | 9 ++ shell/common/shorebird/shorebird.cc | 95 ++++++++++++- shell/common/shorebird/shorebird.h | 31 ++++- shell/platform/embedder/embedder.cc | 7 +- shell/platform/windows/BUILD.gn | 2 + .../platform/windows/flutter_project_bundle.h | 4 + .../windows/flutter_windows_engine.cc | 131 +++++++++++++++++- 10 files changed, 278 insertions(+), 9 deletions(-) diff --git a/DEPS b/DEPS index eae0922c4b51c..9a84b0155444e 100644 --- a/DEPS +++ b/DEPS @@ -18,7 +18,7 @@ vars = { "dart_sdk_revision": "4f9b5084a041a0e7cc26295da1da9109df43ac4c", "dart_sdk_git": "git@github.com:shorebirdtech/dart-sdk.git", "updater_git": "https://github.com/shorebirdtech/updater.git", - "updater_rev": "54e1e2ce2f82e5fe004bd0730c00d4310ab637dc", + "updater_rev": "71b5ed65fab03fcf241edf0c74d027de9998da51", # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. diff --git a/common/config.gni b/common/config.gni index 0a5a95306c2fd..66d300d99670d 100644 --- a/common/config.gni +++ b/common/config.gni @@ -73,6 +73,10 @@ if (slimpeller) { feature_defines_list += [ "SLIMPELLER=1" ] } +if (is_ios || is_mac || is_android || is_win) { + feature_defines_list += [ "SHOREBIRD_PLATFORM_SUPPORTED=1" ] +} + if (is_ios || is_mac) { flutter_cflags_objc = [ "-Werror=overriding-method-mismatch", diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 0878c020de259..9e626a95b6db7 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -444,7 +444,7 @@ Shell::Shell(DartVMRef vm, weak_factory_gpu_(nullptr), weak_factory_(this) { // FIXME: This is probably the wrong place to hook into. -#if FML_OS_ANDROID || FML_OS_IOS || FML_OS_MACOSX +#if SHOREBIRD_PLATFORM_SUPPORTED if (!vm_) { shorebird_report_launch_failure(); } else { diff --git a/shell/common/shorebird/BUILD.gn b/shell/common/shorebird/BUILD.gn index 4e31c785af768..0a28f76bce43b 100644 --- a/shell/common/shorebird/BUILD.gn +++ b/shell/common/shorebird/BUILD.gn @@ -31,6 +31,15 @@ source_set("shorebird") { ] include_dirs = [ "//flutter/updater" ] + + if (host_os == "win" && target_os == "win") { + if (target_cpu == "x64") { + libs = [ + "userenv.lib", + "//third_party/updater/target/x86_64-pc-windows-msvc/release/updater.lib", + ] + } + } } if (enable_unittests) { diff --git a/shell/common/shorebird/shorebird.cc b/shell/common/shorebird/shorebird.cc index 89d49d68fafa1..77608a0e29563 100644 --- a/shell/common/shorebird/shorebird.cc +++ b/shell/common/shorebird/shorebird.cc @@ -21,6 +21,7 @@ #include "flutter/shell/common/shorebird/snapshots_data_handle.h" #include "flutter/shell/common/switches.h" #include "fml/logging.h" +#include "shell/platform/embedder/embedder.h" #include "third_party/dart/runtime/include/dart_tools_api.h" #include "third_party/updater/library/include/updater.h" @@ -79,8 +80,100 @@ FileCallbacks ShorebirdFileCallbacks() { }; } +// FIXME: consolidate this with the other ConfigureShorebird +bool ConfigureShorebird(const ShorebirdConfigArgs& args, + std::string& patch_path) { + patch_path = args.release_app_library_path; + auto shorebird_updater_dir_name = "shorebird_updater"; + + auto code_cache_dir = fml::paths::JoinPaths( + {std::move(args.code_cache_path), shorebird_updater_dir_name}); + auto app_storage_dir = fml::paths::JoinPaths( + {std::move(args.app_storage_path), shorebird_updater_dir_name}); + + fml::CreateDirectory(fml::paths::GetCachesDirectory(), + {shorebird_updater_dir_name}, + fml::FilePermission::kReadWrite); + + bool init_result; + // 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 = + args.release_version.version + "+" + args.release_version.build_number; + 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{}; + c_paths.push_back(args.release_app_library_path.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. + init_result = shorebird_init(&app_parameters, ShorebirdFileCallbacks(), + args.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. + // TODO: this won't compile as we don't have a settings object here. + // #if FML_OS_IOS || FML_OS_MACOSX + // SetBaseSnapshot(settings); + // #endif + + FML_LOG(INFO) << "Checking for active patch"; + char* c_active_path = shorebird_next_boot_patch_path(); + if (c_active_path != NULL) { + patch_path = c_active_path; + shorebird_free_string(c_active_path); + FML_LOG(INFO) << "Shorebird updater: patch path: " << patch_path; + } else { + FML_LOG(INFO) << "Shorebird updater: no active patch."; + } + + // We are careful only to report a launch start in the case where it's the + // first time we've configured shorebird this process. Otherwise we could end + // up in a case where we report a launch start, but never a completion (e.g. + // from package:flutter_work_manager which sometimes creates a FlutterEngine + // (and thus configures shorebird) but never runs it. The proper fix for this + // is probably to move the launch_start() call to be later in the lifecycle + // (when the snapshot is loaded and run, rather than when FlutterEngine is + // initialized). This "hack" will still have a problem where FlutterEngine is + // initialized but never run before the app is quit, could still cause us to + // suddenly mark-bad a patch that was never actually attempted to launch. + if (!init_result) { + return false; + } + + // 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(); + + 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."; + } + + return true; +} + void ConfigureShorebird(const ShorebirdFlutterProjectArgs& args, - flutter::Settings& settings) { + Settings& settings) { // cache_path is used for both code_cache and app_storage, as we don't persist // any data between releases. args.app_path is appended to // the settings.application_library_path vector at this function's call site. diff --git a/shell/common/shorebird/shorebird.h b/shell/common/shorebird/shorebird.h index 1c6cb8bcd4443..b4efa4c28321b 100644 --- a/shell/common/shorebird/shorebird.h +++ b/shell/common/shorebird/shorebird.h @@ -6,12 +6,39 @@ namespace flutter { +struct ReleaseVersion { + std::string version; + std::string build_number; +}; + +struct ShorebirdConfigArgs { + std::string code_cache_path; + std::string app_storage_path; + std::string release_app_library_path; + std::string shorebird_yaml; + ReleaseVersion release_version; + + ShorebirdConfigArgs(std::string code_cache_path, + std::string app_storage_path, + std::string release_app_library_path, + std::string shorebird_yaml, + ReleaseVersion release_version) + : code_cache_path(code_cache_path), + app_storage_path(app_storage_path), + release_app_library_path(release_app_library_path), + shorebird_yaml(shorebird_yaml), + release_version(release_version) {} +}; + +bool ConfigureShorebird(const ShorebirdConfigArgs& args, + std::string& patch_path); + void ConfigureShorebird(const ShorebirdFlutterProjectArgs& args, - flutter::Settings& settings); + Settings& settings); void ConfigureShorebird(std::string code_cache_path, std::string app_storage_path, - flutter::Settings& settings, + Settings& settings, const std::string& shorebird_yaml, const std::string& version, const std::string& version_code); diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 6f6ffc2b24df9..f1646d85eab08 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -2306,13 +2306,16 @@ FlutterEngineResult FlutterEngineInitialize(size_t version, "Could not infer the Flutter project to run from given arguments."); } + // FIXME: This is probably the wrong place to call ConfigureShorebird, as + // some platforms (i.e., Windows) need to to swap out the app path before + // this point. // Begin shorebird +#if FML_OS_MACOSX if (args->shorebird_args.shorebird_yaml_contents) { settings.application_library_path.push_back(args->shorebird_args.app_path); flutter::ConfigureShorebird(args->shorebird_args, settings); - } else { - FML_LOG(INFO) << "[shorebird] No shorebird YAML contents provided."; } +#endif // End shorebird // Create the engine but don't launch the shell or run the root isolate. diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index e80662e592a72..7389e5352f645 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -156,6 +156,7 @@ source_set("flutter_windows_source") { ":flutter_windows_headers", "//flutter/fml:fml", "//flutter/impeller/renderer/backend/gles", + "//flutter/shell/common/shorebird:shorebird", "//flutter/shell/platform/common:common_cpp", "//flutter/shell/platform/common:common_cpp_input", "//flutter/shell/platform/common:common_cpp_switches", @@ -169,6 +170,7 @@ source_set("flutter_windows_source") { "//flutter/third_party/angle:libEGL_static", "//flutter/third_party/angle:libGLESv2_static", "//flutter/third_party/rapidjson", + "//flutter/third_party/tonic", ] } diff --git a/shell/platform/windows/flutter_project_bundle.h b/shell/platform/windows/flutter_project_bundle.h index 09770b5c094fc..8e9126dbacedd 100644 --- a/shell/platform/windows/flutter_project_bundle.h +++ b/shell/platform/windows/flutter_project_bundle.h @@ -44,6 +44,10 @@ class FlutterProjectBundle { // Sets engine switches. void SetSwitches(const std::vector& switches); + void SetAotLibraryPath(const std::filesystem::path& aot_library_path) { + aot_library_path_ = aot_library_path; + } + // Attempts to load AOT data for this bundle. The returned data must be // retained until any engine instance it is passed to has been shut down. // diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc index d08591a10b520..c6cff1b63b53d 100644 --- a/shell/platform/windows/flutter_windows_engine.cc +++ b/shell/platform/windows/flutter_windows_engine.cc @@ -5,15 +5,20 @@ #include "flutter/shell/platform/windows/flutter_windows_engine.h" #include +#include +#include +#include #include #include #include +#include #include "flutter/fml/logging.h" #include "flutter/fml/paths.h" #include "flutter/fml/platform/win/wstring_conversion.h" #include "flutter/fml/synchronization/waitable_event.h" +#include "flutter/shell/common/shorebird/shorebird.h" #include "flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h" #include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_message_codec.h" #include "flutter/shell/platform/common/path_utils.h" @@ -26,6 +31,7 @@ #include "flutter/shell/platform/windows/system_utils.h" #include "flutter/shell/platform/windows/task_runner.h" #include "flutter/third_party/accessibility/ax/ax_node.h" +#include "third_party/tonic/filesystem/filesystem/file.h" // winbase.h defines GetCurrentTime as a macro. #undef GetCurrentTime @@ -240,13 +246,125 @@ bool FlutterWindowsEngine::Run() { return Run(""); } +int GetReleaseVersionAndBuildNumber(ReleaseVersion* release_version) { + char module_path[MAX_PATH]; + // Get the full path of the currently running executable. The return value is + // the size of the string that was copied to the buffer, with -1 indicating + // failure. + if (GetModuleFileNameA(NULL, module_path, MAX_PATH) == -1) { + return -1; + } + + // Get the size of the version information + DWORD handle = -1; + DWORD version_info_size = GetFileVersionInfoSizeA(module_path, &handle); + if (version_info_size == -1) { + return -1; + } + + // Allocate memory for version info + // std::vector version_data(version_info_size); + std::unique_ptr version_data(new char[version_info_size]); + if (!GetFileVersionInfoA(module_path, handle, version_info_size, + version_data.get())) { + return -1; + } + + // Get the version info structure + VS_FIXEDFILEINFO* file_info = nullptr; + UINT file_info_size = -1; + if (!VerQueryValueA(version_data.get(), "\\", + reinterpret_cast(&file_info), &file_info_size)) { + return -1; + } + + if (file_info) { + // Extract version numbers + DWORD major = HIWORD(file_info->dwFileVersionMS); + DWORD minor = LOWORD(file_info->dwFileVersionMS); + DWORD build = HIWORD(file_info->dwFileVersionLS); + + char version[49]; + snprintf(version, sizeof(version), "%lu.%lu.%lu", major, minor, build); + release_version->version = std::string(version); + release_version->build_number = + std::to_string(LOWORD(file_info->dwFileVersionLS)); + return kSuccess; + } + + return -1; +} + +bool GetLocalAppDataPath(std::string& outPath) { + PWSTR path = nullptr; + HRESULT result = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &path); + if (!SUCCEEDED(result)) { + return false; + } + + std::wstring widePath(path); + std::string localAppDataPath(widePath.begin(), widePath.end()); + // The calling process is responsible for freeing this resource + // https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath + CoTaskMemFree(path); + outPath = localAppDataPath; + return true; +} + +bool SetUpShorebird(std::string assets_path_string, std::string& patch_path) { + auto shorebird_yaml_path = + fml::paths::JoinPaths({assets_path_string, "shorebird.yaml"}); + std::string shorebird_yaml_contents(""); + if (!filesystem::ReadFileToString(shorebird_yaml_path, + &shorebird_yaml_contents)) { + FML_LOG(ERROR) << "Failed to read shorebird.yaml."; + return false; + } + + std::string code_cache_path; + if (!GetLocalAppDataPath(code_cache_path)) { + FML_LOG(ERROR) << "Failed to retrieve the local AppData directory."; + return false; + } + + auto executable_location = fml::paths::GetExecutableDirectoryPath().second; + auto app_path = + fml::paths::JoinPaths({executable_location, "data", "app.so"}); + ReleaseVersion release_version; + auto release_version_result = + GetReleaseVersionAndBuildNumber(&release_version); + if (release_version_result != kSuccess) { + FML_LOG(ERROR) + << "Failed to retrieve the release version and build number."; + return false; + } + + ShorebirdConfigArgs shorebird_args(code_cache_path, code_cache_path, app_path, + shorebird_yaml_contents, release_version); + return ConfigureShorebird(shorebird_args, patch_path); +} + bool FlutterWindowsEngine::Run(std::string_view entrypoint) { + std::string assets_path_string = project_->assets_path().u8string(); + std::string icu_path_string = project_->icu_path().u8string(); + if (!project_->HasValidPaths()) { FML_LOG(ERROR) << "Missing or unresolvable paths to assets."; return false; } - std::string assets_path_string = project_->assets_path().u8string(); - std::string icu_path_string = project_->icu_path().u8string(); + + std::string patch_path; + auto setup_shorebird_result = SetUpShorebird(assets_path_string, patch_path); + if (setup_shorebird_result) { + // If we have a patch installed, we replace the default AOT library path + // with the patch path here. + FML_LOG(INFO) << "Setting project patch path: " << patch_path; + project_->SetAotLibraryPath(patch_path); + } else { + FML_LOG(ERROR) << "Failed to configure Shorebird."; + } + + // This loads AOT data from the project_'s aot_library_path_. if (embedder_api_.RunsAOTCompiledDartCode()) { aot_data_ = project_->LoadAotData(embedder_api_); if (!aot_data_) { @@ -370,6 +488,15 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) { host->root_isolate_create_callback_(); } }; + // Copied from shell\platform\darwin\macos\framework\Source\FlutterEngine.mm + // Writes log messages to stdout. + args.log_message_callback = [](const char* tag, const char* message, + void* user_data) { + if (tag && tag[0]) { + std::cout << tag << ": "; + } + std::cout << message << std::endl; + }; args.channel_update_callback = [](const FlutterChannelUpdate* update, void* user_data) { auto host = static_cast(user_data); From f2ba0d8937f08f821546890bb94a73587300fd6c Mon Sep 17 00:00:00 2001 From: "BOLTMAN-WINDOWS\\bryan" Date: Fri, 3 Jan 2025 23:09:16 -0500 Subject: [PATCH 11/23] fix: fix flutter_tester build --- shell/common/BUILD.gn | 10 ++++++++++ shell/common/shorebird/BUILD.gn | 9 --------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index 5e4b4d1f2c6f8..5242d66c726fc 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -173,6 +173,16 @@ source_set("common") { ] } } + + # Needed to compile flutter_tester for Windows. + if (host_os == "win" && target_os == "win") { + if (target_cpu == "x64") { + libs = [ + "userenv.lib", + "//third_party/updater/target/x86_64-pc-windows-msvc/release/updater.lib", + ] + } + } } # These are in their own source_set to avoid a dependency cycle with //common/graphics diff --git a/shell/common/shorebird/BUILD.gn b/shell/common/shorebird/BUILD.gn index 0a28f76bce43b..4e31c785af768 100644 --- a/shell/common/shorebird/BUILD.gn +++ b/shell/common/shorebird/BUILD.gn @@ -31,15 +31,6 @@ source_set("shorebird") { ] include_dirs = [ "//flutter/updater" ] - - if (host_os == "win" && target_os == "win") { - if (target_cpu == "x64") { - libs = [ - "userenv.lib", - "//third_party/updater/target/x86_64-pc-windows-msvc/release/updater.lib", - ] - } - } } if (enable_unittests) { From 7c60fd1b439abc20de74f5406950b41e47a366f0 Mon Sep 17 00:00:00 2001 From: "BOLTMAN-WINDOWS\\bryan" Date: Fri, 3 Jan 2025 23:10:27 -0500 Subject: [PATCH 12/23] chore: formatting --- shell/common/BUILD.gn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index 5242d66c726fc..aee1fcc550f8f 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -173,7 +173,7 @@ source_set("common") { ] } } - + # Needed to compile flutter_tester for Windows. if (host_os == "win" && target_os == "win") { if (target_cpu == "x64") { From d49c3f3c08e3b32eb984bfb6ba4efc74b935ec34 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Tue, 14 Jan 2025 14:41:32 -0600 Subject: [PATCH 13/23] chore: roll Dart to 3.6.1 --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 9a84b0155444e..fcaa3a57e2877 100644 --- a/DEPS +++ b/DEPS @@ -15,7 +15,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', 'skia_revision': '6062afaa505bf7e6c727a20cafe4c7bee0f02df8', - "dart_sdk_revision": "4f9b5084a041a0e7cc26295da1da9109df43ac4c", + "dart_sdk_revision": "5c6f8122c1ee85f833ea3b69b3b85dc07303e8d2", "dart_sdk_git": "git@github.com:shorebirdtech/dart-sdk.git", "updater_git": "https://github.com/shorebirdtech/updater.git", "updater_rev": "71b5ed65fab03fcf241edf0c74d027de9998da51", From 994db673af7ce8519f91b894b3a959b1632f2b61 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Fri, 24 Jan 2025 14:42:26 -0500 Subject: [PATCH 14/23] feat: use windows method for loading patches on intel macs (#96) * feat: support intel macs * formatting * formatting * don't use linker on macos * cleanup * formatting * bump updater rev * SHOREBIRD_USE_LINKER -> SHOREBIRD_USE_INTERPRETER --- DEPS | 2 +- common/config.gni | 4 + runtime/dart_snapshot.cc | 4 +- shell/common/shorebird/shorebird.cc | 20 +---- shell/common/shorebird/shorebird.h | 3 - shell/platform/darwin/macos/BUILD.gn | 1 + .../macos/framework/Source/FlutterEngine.mm | 79 ++++++++++++------- shell/platform/embedder/BUILD.gn | 1 - shell/platform/embedder/embedder.cc | 13 --- shell/platform/embedder/embedder.h | 39 --------- 10 files changed, 61 insertions(+), 105 deletions(-) diff --git a/DEPS b/DEPS index fcaa3a57e2877..0c1599c9eeed0 100644 --- a/DEPS +++ b/DEPS @@ -18,7 +18,7 @@ vars = { "dart_sdk_revision": "5c6f8122c1ee85f833ea3b69b3b85dc07303e8d2", "dart_sdk_git": "git@github.com:shorebirdtech/dart-sdk.git", "updater_git": "https://github.com/shorebirdtech/updater.git", - "updater_rev": "71b5ed65fab03fcf241edf0c74d027de9998da51", + "updater_rev": "ba52a62b5d1b064cce363ef98c6165b1e2f78059", # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. diff --git a/common/config.gni b/common/config.gni index 66d300d99670d..351af4a41746d 100644 --- a/common/config.gni +++ b/common/config.gni @@ -77,6 +77,10 @@ if (is_ios || is_mac || is_android || is_win) { feature_defines_list += [ "SHOREBIRD_PLATFORM_SUPPORTED=1" ] } +if (is_ios) { + feature_defines_list += [ "SHOREBIRD_USE_INTERPRETER=1" ] +} + if (is_ios || is_mac) { flutter_cflags_objc = [ "-Werror=overriding-method-mismatch", diff --git a/runtime/dart_snapshot.cc b/runtime/dart_snapshot.cc index 1a62b3406ef3e..d735b299cad42 100644 --- a/runtime/dart_snapshot.cc +++ b/runtime/dart_snapshot.cc @@ -57,7 +57,7 @@ static std::shared_ptr SearchMapping( const std::vector& native_library_path, const char* native_library_symbol_name, bool is_executable) { -#if FML_OS_IOS || FML_OS_MACOSX +#if SHOREBIRD_USE_INTERPRETER // 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; @@ -141,7 +141,7 @@ static std::shared_ptr SearchMapping( } } -#if FML_OS_IOS || FML_OS_MACOSX +#if SHOREBIRD_USE_INTERPRETER } // !is_patch #endif diff --git a/shell/common/shorebird/shorebird.cc b/shell/common/shorebird/shorebird.cc index 77608a0e29563..602a0521ae7b0 100644 --- a/shell/common/shorebird/shorebird.cc +++ b/shell/common/shorebird/shorebird.cc @@ -126,12 +126,6 @@ bool ConfigureShorebird(const ShorebirdConfigArgs& args, // 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. - // TODO: this won't compile as we don't have a settings object here. - // #if FML_OS_IOS || FML_OS_MACOSX - // SetBaseSnapshot(settings); - // #endif - FML_LOG(INFO) << "Checking for active patch"; char* c_active_path = shorebird_next_boot_patch_path(); if (c_active_path != NULL) { @@ -172,16 +166,6 @@ bool ConfigureShorebird(const ShorebirdConfigArgs& args, return true; } -void ConfigureShorebird(const ShorebirdFlutterProjectArgs& args, - Settings& settings) { - // cache_path is used for both code_cache and app_storage, as we don't persist - // any data between releases. args.app_path is appended to - // the settings.application_library_path vector at this function's call site. - ConfigureShorebird(args.cache_path, args.cache_path, settings, - args.shorebird_yaml_contents, args.app_version, - args.app_build_number); -} - void ConfigureShorebird(std::string code_cache_path, std::string app_storage_path, Settings& settings, @@ -239,7 +223,7 @@ void ConfigureShorebird(std::string code_cache_path, // https://github.com/shorebirdtech/shorebird/issues/950 // We only set the base snapshot on iOS for now. -#if FML_OS_IOS || FML_OS_MACOSX +#if SHOREBIRD_USE_INTERPRETER SetBaseSnapshot(settings); #endif @@ -249,7 +233,7 @@ void ConfigureShorebird(std::string code_cache_path, shorebird_free_string(c_active_path); FML_LOG(INFO) << "Shorebird updater: active path: " << active_path; -#if FML_OS_IOS || FML_OS_MACOSX +#if SHOREBIRD_USE_INTERPRETER // 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. diff --git a/shell/common/shorebird/shorebird.h b/shell/common/shorebird/shorebird.h index b4efa4c28321b..3e9ccce9ee3f6 100644 --- a/shell/common/shorebird/shorebird.h +++ b/shell/common/shorebird/shorebird.h @@ -33,9 +33,6 @@ struct ShorebirdConfigArgs { bool ConfigureShorebird(const ShorebirdConfigArgs& args, std::string& patch_path); -void ConfigureShorebird(const ShorebirdFlutterProjectArgs& args, - Settings& settings); - void ConfigureShorebird(std::string code_cache_path, std::string app_storage_path, Settings& settings, diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index 2975a38fb8d08..b7ce1ca36ffe2 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -123,6 +123,7 @@ source_set("flutter_framework_source") { ":macos_gpu_configuration", "//flutter/flow:flow", "//flutter/fml", + "//flutter/shell/common/shorebird", "//flutter/shell/platform/common:common_cpp_accessibility", "//flutter/shell/platform/common:common_cpp_enums", "//flutter/shell/platform/common:common_cpp_input", diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index c870f0d3f55ea..adc9a0534a910 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -11,6 +11,7 @@ #include "flutter/common/constants.h" #include "flutter/fml/paths.h" +#include "flutter/shell/common/shorebird/shorebird.h" #include "flutter/shell/platform/common/app_lifecycle_state.h" #include "flutter/shell/platform/common/engine_switches.h" #include "flutter/shell/platform/embedder/embedder.h" @@ -585,6 +586,40 @@ - (void)dealloc { } } +- (BOOL)configureShorebird:(NSString**)patchPath { + NSLog(@"[shorebird] setting up non-linker shorebird"); + NSString* bundlePath = + [[NSBundle bundleWithURL:[NSBundle.mainBundle.privateFrameworksURL + URLByAppendingPathComponent:@"App.framework"]] bundlePath]; + bundlePath = [bundlePath stringByAppendingString:@"/App"]; + NSString* assetsPath = _project.assetsPath; + NSURL* shorebirdYamlPath = [NSURL URLWithString:@"shorebird.yaml" + relativeToURL:[NSURL fileURLWithPath:assetsPath]]; + NSString* shorebirdYamlContents = [NSString stringWithContentsOfURL:shorebirdYamlPath + encoding:NSUTF8StringEncoding + error:nil]; + NSString* appVersion = + [NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + NSString* appBuildNumber = [NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleVersion"]; + std::string cache_path = + fml::paths::JoinPaths({getenv("HOME"), "Library", "Application Support", "shorebird"}); + flutter::ReleaseVersion release_version = {appVersion.UTF8String, appBuildNumber.UTF8String}; + flutter::ShorebirdConfigArgs shorebird_args(cache_path, cache_path, bundlePath.UTF8String, + shorebirdYamlContents.UTF8String, release_version); + NSLog(@"[shorebird] calling ConfigureShorebird"); + std::string patch_path; + auto res = flutter::ConfigureShorebird(shorebird_args, patch_path); + if (!res) { + NSLog(@"[shorebird] ConfigureShorebird failed"); + return NO; + } + + NSLog(@"[shorebird] ConfigureShorebird success!"); + *patchPath = [NSString stringWithUTF8String:patch_path.c_str()]; + NSLog(@"[shorebird] patchPath: %@", *patchPath); + return YES; +} + - (BOOL)runWithEntrypoint:(NSString*)entrypoint { if (self.running) { return NO; @@ -663,7 +698,19 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint { .thread_priority_setter = SetThreadPriority}; flutterArguments.custom_task_runners = &custom_task_runners; - [self loadAOTData:_project.assetsPath]; + NSString* elfPath; + BOOL configureShorebirdRes = [self configureShorebird:&elfPath]; + if (!configureShorebirdRes) { + // No patch exists, or we failed to configure shorebird. This is a fallback. + // Upstream, this code lives in -(void)loadAOTData:. + // + // This is the location where the test fixture places the snapshot file. + // For applications built by Flutter tool, this is in "App.framework". + elfPath = [NSString pathWithComponents:@[ _project.assetsPath, @"app_elf_snapshot.so" ]]; + } + + [self loadAOTData:elfPath]; + if (_aotData) { flutterArguments.aot_data = _aotData; } @@ -680,29 +727,8 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint { [engine onVSync:baton]; }; - NSString* bundlePath = - [[NSBundle bundleWithURL:[NSBundle.mainBundle.privateFrameworksURL - URLByAppendingPathComponent:@"App.framework"]] bundlePath]; - bundlePath = [bundlePath stringByAppendingString:@"/App"]; - flutterArguments.shorebird_args.app_path = bundlePath.UTF8String; - NSString* assetsPath = _project.assetsPath; - NSURL* shorebirdYamlPath = [NSURL URLWithString:@"shorebird.yaml" - relativeToURL:[NSURL fileURLWithPath:assetsPath]]; - NSString* shorebirdYamlContents = [NSString stringWithContentsOfURL:shorebirdYamlPath - encoding:NSUTF8StringEncoding - error:nil]; - NSString* appVersion = - [NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; - NSString* appBuildNumber = [NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleVersion"]; - flutterArguments.shorebird_args.app_version = appVersion.UTF8String; - flutterArguments.shorebird_args.app_build_number = appBuildNumber.UTF8String; - - std::string cache_path = - fml::paths::JoinPaths({getenv("HOME"), "Library", "Application Support", "shorebird"}); - flutterArguments.shorebird_args.cache_path = cache_path.c_str(); - flutterArguments.shorebird_args.shorebird_yaml_contents = shorebirdYamlContents.UTF8String; - FlutterRendererConfig rendererConfig = [_renderer createRendererConfig]; + FlutterEngineResult result = _embedderAPI.Initialize( FLUTTER_ENGINE_VERSION, &rendererConfig, &flutterArguments, (__bridge void*)(self), &_engine); if (result != kSuccess) { @@ -732,7 +758,7 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint { return YES; } -- (void)loadAOTData:(NSString*)assetsDir { +- (void)loadAOTData:(NSString*)elfPath { if (!_embedderAPI.RunsAOTCompiledDartCode()) { return; } @@ -740,11 +766,8 @@ - (void)loadAOTData:(NSString*)assetsDir { BOOL isDirOut = false; // required for NSFileManager fileExistsAtPath. NSFileManager* fileManager = [NSFileManager defaultManager]; - // This is the location where the test fixture places the snapshot file. - // For applications built by Flutter tool, this is in "App.framework". - NSString* elfPath = [NSString pathWithComponents:@[ assetsDir, @"app_elf_snapshot.so" ]]; - if (![fileManager fileExistsAtPath:elfPath isDirectory:&isDirOut]) { + FML_LOG(INFO) << "in loadAOTData, elfPath does not exist: " << elfPath.UTF8String; return; } diff --git a/shell/platform/embedder/BUILD.gn b/shell/platform/embedder/BUILD.gn index c620e74761979..38c7e2db98e87 100644 --- a/shell/platform/embedder/BUILD.gn +++ b/shell/platform/embedder/BUILD.gn @@ -110,7 +110,6 @@ template("embedder_source_set") { "//flutter/lib/ui", "//flutter/runtime:libdart", "//flutter/shell/common", - "//flutter/shell/common/shorebird", "//flutter/skia", "//flutter/third_party/tonic", ] diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index f1646d85eab08..2adf56c592e0a 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -50,7 +50,6 @@ extern const intptr_t kPlatformStrongDillSize; #include "flutter/fml/paths.h" #include "flutter/fml/trace_event.h" #include "flutter/shell/common/rasterizer.h" -#include "flutter/shell/common/shorebird/shorebird.h" #include "flutter/shell/common/switches.h" #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/embedder/embedder_engine.h" @@ -2306,18 +2305,6 @@ FlutterEngineResult FlutterEngineInitialize(size_t version, "Could not infer the Flutter project to run from given arguments."); } - // FIXME: This is probably the wrong place to call ConfigureShorebird, as - // some platforms (i.e., Windows) need to to swap out the app path before - // this point. - // Begin shorebird -#if FML_OS_MACOSX - if (args->shorebird_args.shorebird_yaml_contents) { - settings.application_library_path.push_back(args->shorebird_args.app_path); - flutter::ConfigureShorebird(args->shorebird_args, settings); - } -#endif - // End shorebird - // Create the engine but don't launch the shell or run the root isolate. auto embedder_engine = std::make_unique( std::move(thread_host), // diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 482a4cad90f6c..9e9b875a2cc36 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -2204,42 +2204,6 @@ typedef void (*FlutterLogMessageCallback)(const char* /* tag */, /// FlutterEngine instance in AOT mode. typedef struct _FlutterEngineAOTData* FlutterEngineAOTData; -typedef struct { - /// The version of the app (e.g., 1.0.0). - /// - /// The string can be collected after the call to `FlutterEngineInitialize` - /// returns. The string must be NULL terminated. - const char* app_version; - - /// The build number of the app (e.g., 1). - /// - /// The string can be collected after the call to `FlutterEngineInitialize` - /// returns. The string must be NULL terminated. - const char* app_build_number; - - /// The text contents of the shorebird.yaml file bundled with the compiled - /// app. Note that this is _not_ the same as the shorebird.yaml that exists in - /// the user's project. - /// - /// The string can be collected after the call to `FlutterEngineInitialize` - /// returns. The string must be NULL terminated. - const char* shorebird_yaml_contents; - - /// The path to the directory where Shorebird will store patches and state - /// data. - /// - /// The string can be collected after the call to `FlutterEngineInitialize` - /// returns. The string must be NULL terminated. - const char* cache_path; - - /// The path to the executable file. This is a Mach-O executable file on - /// macOS. - /// - /// The string can be collected after the call to `FlutterEngineInitialize` - /// returns. The string must be NULL terminated. - const char* app_path; -} ShorebirdFlutterProjectArgs; - typedef struct { /// The size of this struct. Must be sizeof(FlutterProjectArgs). size_t struct_size; @@ -2539,9 +2503,6 @@ typedef struct { /// being registered on the framework side. The callback is invoked from /// a task posted to the platform thread. FlutterChannelUpdateCallback channel_update_callback; - - /// Data used to initialize Shorebird as part of engine initialization. - ShorebirdFlutterProjectArgs shorebird_args; } FlutterProjectArgs; #ifndef FLUTTER_ENGINE_NO_PROTOTYPES From 463b3ef7e217112932f6c907be04bf9c27896996 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Fri, 24 Jan 2025 14:56:21 -0500 Subject: [PATCH 15/23] bump updater rev --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 0c1599c9eeed0..0f201d212b463 100644 --- a/DEPS +++ b/DEPS @@ -18,7 +18,7 @@ vars = { "dart_sdk_revision": "5c6f8122c1ee85f833ea3b69b3b85dc07303e8d2", "dart_sdk_git": "git@github.com:shorebirdtech/dart-sdk.git", "updater_git": "https://github.com/shorebirdtech/updater.git", - "updater_rev": "ba52a62b5d1b064cce363ef98c6165b1e2f78059", + "updater_rev": "707346df33076fae5a85f9a89b5eb51c43fb5aec", # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From b21177429740fc747c193ec4017238a52ce65b8e Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Fri, 24 Jan 2025 15:00:27 -0500 Subject: [PATCH 16/23] bump updater rev --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 0f201d212b463..065fc3a7f8803 100644 --- a/DEPS +++ b/DEPS @@ -18,7 +18,7 @@ vars = { "dart_sdk_revision": "5c6f8122c1ee85f833ea3b69b3b85dc07303e8d2", "dart_sdk_git": "git@github.com:shorebirdtech/dart-sdk.git", "updater_git": "https://github.com/shorebirdtech/updater.git", - "updater_rev": "707346df33076fae5a85f9a89b5eb51c43fb5aec", + "updater_rev": "69468a0c9fdfad458fada32a41c3cebb1f705bb7", # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From 1998c99bb02606ec7b0b02769a7b241253cff4d0 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Tue, 28 Jan 2025 17:49:16 -0500 Subject: [PATCH 17/23] Add Shorebird support for x64 Linux targets (#97) * Add shorebird support to x64 Linux targets * formatting * read app id and version from files * Cleanup * Cleanup * add comment --- common/config.gni | 2 +- shell/common/BUILD.gn | 6 +++ shell/platform/linux/BUILD.gn | 2 + shell/platform/linux/fl_engine.cc | 80 +++++++++++++++++++++++++++++-- 4 files changed, 86 insertions(+), 4 deletions(-) diff --git a/common/config.gni b/common/config.gni index 351af4a41746d..8807af4fcc8ba 100644 --- a/common/config.gni +++ b/common/config.gni @@ -73,7 +73,7 @@ if (slimpeller) { feature_defines_list += [ "SLIMPELLER=1" ] } -if (is_ios || is_mac || is_android || is_win) { +if (is_android || is_ios || is_linux || is_mac || is_win) { feature_defines_list += [ "SHOREBIRD_PLATFORM_SUPPORTED=1" ] } diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index aee1fcc550f8f..d0494a9c51258 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -183,6 +183,12 @@ source_set("common") { ] } } + + if (host_os == "linux" && target_os == "linux") { + if (target_cpu == "x64") { + libs = [ "//third_party/updater/target/x86_64-unknown-linux-gnu/release/libupdater.a" ] + } + } } # These are in their own source_set to avoid a dependency cycle with //common/graphics diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index 36045a9fd4708..a95f6a3f0bf78 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -163,11 +163,13 @@ source_set("flutter_linux_sources") { deps = [ "//flutter/fml", + "//flutter/shell/common/shorebird", "//flutter/shell/platform/common:common_cpp_enums", "//flutter/shell/platform/common:common_cpp_input", "//flutter/shell/platform/common:common_cpp_switches", "//flutter/shell/platform/embedder:embedder_headers", "//flutter/third_party/rapidjson", + "//flutter/third_party/tonic", ] } diff --git a/shell/platform/linux/fl_engine.cc b/shell/platform/linux/fl_engine.cc index 4a8bee3561046..cfd61f6dce303 100644 --- a/shell/platform/linux/fl_engine.cc +++ b/shell/platform/linux/fl_engine.cc @@ -4,13 +4,19 @@ #include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h" +#include #include #include +#include +#include #include #include #include "flutter/common/constants.h" +#include "flutter/fml/logging.h" +#include "flutter/fml/paths.h" +#include "flutter/shell/common/shorebird/shorebird.h" #include "flutter/shell/platform/common/engine_switches.h" #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/linux/fl_binary_messenger_private.h" @@ -25,6 +31,8 @@ #include "flutter/shell/platform/linux/fl_texture_gl_private.h" #include "flutter/shell/platform/linux/fl_texture_registrar_private.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_plugin_registry.h" +#include "rapidjson/document.h" +#include "third_party/tonic/filesystem/filesystem/file.h" // Unique number associated with platform tasks. static constexpr size_t kPlatformTaskRunnerIdentifier = 1; @@ -506,6 +514,59 @@ FlRenderer* fl_engine_get_renderer(FlEngine* self) { return self->renderer; } +gboolean fl_set_up_shorebird(const char* assets_path, std::string& patch_path) { + auto shorebird_yaml_path = + fml::paths::JoinPaths({assets_path, "shorebird.yaml"}); + std::string shorebird_yaml_contents(""); + if (!filesystem::ReadFileToString(shorebird_yaml_path, + &shorebird_yaml_contents)) { + FML_LOG(ERROR) << "Failed to read shorebird.yaml."; + return false; + } + + // Read appid from shorebird.yaml + std::string appid = ""; + std::stringstream ss(shorebird_yaml_contents); + std::string line; + std::string appid_prefix = "appid:"; + while (std::getline(ss, line, '\n')) { + if (line.find(appid_prefix) != std::string::npos) { + appid = line.substr(line.find(appid_prefix) + appid_prefix.size()); + break; + } + } + + std::string code_cache_path = + fml::paths::JoinPaths({g_get_home_dir(), ".shorebird_cache", appid}); + auto executable_location = fml::paths::GetExecutableDirectoryPath().second; + auto app_path = + fml::paths::JoinPaths({executable_location, "lib", "libapp.so"}); + auto version_json_path = fml::paths::JoinPaths({assets_path, "version.json"}); + std::ifstream input(version_json_path); + if (!input) { + return false; + } + std::string json_contents{std::istreambuf_iterator(input), + std::istreambuf_iterator()}; + + rapidjson::Document json_doc; + json_doc.Parse(json_contents.c_str()); + if (json_doc.HasParseError()) { + // Could not parse version file, aborting. + return false; + } + + const auto version_map = json_doc.GetObject(); + flutter::ReleaseVersion release_version{ + version_map["version"].GetString(), + version_map["build_number"].GetString()}; + + flutter::ShorebirdConfigArgs shorebird_args(code_cache_path, code_cache_path, + app_path, shorebird_yaml_contents, + release_version); + return ConfigureShorebird(shorebird_args, patch_path); +} + gboolean fl_engine_start(FlEngine* self, GError** error) { g_return_val_if_fail(FL_IS_ENGINE(self), FALSE); @@ -538,8 +599,8 @@ gboolean fl_engine_start(FlEngine* self, GError** error) { g_autoptr(GPtrArray) command_line_args = fl_engine_get_switches(self); // FlutterProjectArgs expects a full argv, so when processing it for flags - // the first item is treated as the executable and ignored. Add a dummy value - // so that all switches are used. + // the first item is treated as the executable and ignored. Add a dummy + // value so that all switches are used. g_ptr_array_insert(command_line_args, 0, g_strdup("flutter")); gchar** dart_entrypoint_args = @@ -573,9 +634,22 @@ gboolean fl_engine_start(FlEngine* self, GError** error) { args.compositor = &compositor; if (self->embedder_api.RunsAOTCompiledDartCode()) { + // This struct contains raw C strings and needs to have its lifetime scoped + // to this block. FlutterEngineAOTDataSource source = {}; source.type = kFlutterEngineAOTDataSourceTypeElfPath; - source.elf_path = fl_dart_project_get_aot_library_path(self->project); + std::string patch_path; + auto setup_shorebird_result = + fl_set_up_shorebird(args.assets_path, patch_path); + if (setup_shorebird_result) { + // If we have a patch installed, we replace the default AOT library path + // with the patch path here. + source.elf_path = patch_path.c_str(); + } else { + FML_LOG(ERROR) << "Failed to configure Shorebird."; + source.elf_path = fl_dart_project_get_aot_library_path(self->project); + } + if (self->embedder_api.CreateAOTData(&source, &self->aot_data) != kSuccess) { g_set_error(error, fl_engine_error_quark(), FL_ENGINE_ERROR_FAILED, From 9637b88761740c1ea1d6efac2e2b73235e6eb4de Mon Sep 17 00:00:00 2001 From: bryanoltman Date: Tue, 28 Jan 2025 17:49:52 -0500 Subject: [PATCH 18/23] bump updater rev --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 065fc3a7f8803..5dfcb137998dd 100644 --- a/DEPS +++ b/DEPS @@ -18,7 +18,7 @@ vars = { "dart_sdk_revision": "5c6f8122c1ee85f833ea3b69b3b85dc07303e8d2", "dart_sdk_git": "git@github.com:shorebirdtech/dart-sdk.git", "updater_git": "https://github.com/shorebirdtech/updater.git", - "updater_rev": "69468a0c9fdfad458fada32a41c3cebb1f705bb7", + "updater_rev": "a4a7255796132ef58879fe035cdb7185887b0550", # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From 0f4e6e9355a086679828918f0bd89f823135c1d5 Mon Sep 17 00:00:00 2001 From: bryanoltman Date: Thu, 30 Jan 2025 16:58:56 -0500 Subject: [PATCH 19/23] Move updater from third_party to flutter/third_party --- DEPS | 2 +- shell/common/BUILD.gn | 4 ++-- shell/platform/android/BUILD.gn | 8 ++++---- shell/platform/darwin/ios/BUILD.gn | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/DEPS b/DEPS index 5dfcb137998dd..c6a4502a5eee6 100644 --- a/DEPS +++ b/DEPS @@ -634,7 +634,7 @@ deps = { 'src/flutter/third_party/ocmock': Var('flutter_git') + '/third_party/ocmock' + '@' + Var('ocmock_rev'), - 'src/third_party/updater': + 'src/flutter/third_party/updater': Var('updater_git') + '@' + Var('updater_rev'), 'src/flutter/third_party/libjpeg-turbo/src': diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index d0494a9c51258..dc5a140fbb549 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -166,10 +166,10 @@ source_set("common") { # Needed to compile flutter_tester for macOS. if (host_os == "mac" && target_os == "mac") { if (target_cpu == "arm64") { - libs = [ "//third_party/updater/target/aarch64-apple-darwin/release/libupdater.a" ] + libs = [ "//flutter/third_party/updater/target/aarch64-apple-darwin/release/libupdater.a" ] } else if (target_cpu == "x64") { libs = [ - "//third_party/updater/target/x86_64-apple-darwin/release/libupdater.a", + "//flutter/third_party/updater/target/x86_64-apple-darwin/release/libupdater.a", ] } } diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index bde83714ec43b..f4a565eeb546f 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -183,18 +183,18 @@ source_set("flutter_shell_native_src") { "GLESv2", ] if (target_cpu == "arm") { - libs += [ "//third_party/updater/target/armv7-linux-androideabi/release/libupdater.a" ] + libs += [ "//flutter/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", + "//flutter/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", + "//flutter/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", + "//flutter/third_party/updater/target/i686-linux-android/release/libupdater.a", ] } else { assert(false, "Unsupported target_cpu") diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 30d8eafdaafb5..7f51fe353693f 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -222,11 +222,11 @@ source_set("flutter_framework_source") { if (target_cpu == "arm64") { libs = [ - "//third_party/updater/target/aarch64-apple-ios/release/libupdater.a", + "//flutter/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" ] + [ "//flutter/third_party/updater/target/x86_64-apple-ios/release/libupdater.a" ] } else { assert(false, "Unsupported target_cpu") } From 4cc132c64d9a42c39bcfb69051902f95d710194f Mon Sep 17 00:00:00 2001 From: bryanoltman Date: Thu, 30 Jan 2025 17:00:09 -0500 Subject: [PATCH 20/23] Formatting --- shell/common/BUILD.gn | 4 +--- shell/platform/android/BUILD.gn | 12 +++--------- shell/platform/darwin/ios/BUILD.gn | 7 ++----- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index dc5a140fbb549..6bd76b2c6f12d 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -168,9 +168,7 @@ source_set("common") { if (target_cpu == "arm64") { libs = [ "//flutter/third_party/updater/target/aarch64-apple-darwin/release/libupdater.a" ] } else if (target_cpu == "x64") { - libs = [ - "//flutter/third_party/updater/target/x86_64-apple-darwin/release/libupdater.a", - ] + libs = [ "//flutter/third_party/updater/target/x86_64-apple-darwin/release/libupdater.a" ] } } diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index f4a565eeb546f..10b5c4bb232f1 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -185,17 +185,11 @@ source_set("flutter_shell_native_src") { if (target_cpu == "arm") { libs += [ "//flutter/third_party/updater/target/armv7-linux-androideabi/release/libupdater.a" ] } else if (target_cpu == "arm64") { - libs += [ - "//flutter/third_party/updater/target/aarch64-linux-android/release/libupdater.a", - ] + libs += [ "//flutter/third_party/updater/target/aarch64-linux-android/release/libupdater.a" ] } else if (target_cpu == "x64") { - libs += [ - "//flutter/third_party/updater/target/x86_64-linux-android/release/libupdater.a", - ] + libs += [ "//flutter/third_party/updater/target/x86_64-linux-android/release/libupdater.a" ] } else if (target_cpu == "x86") { - libs += [ - "//flutter/third_party/updater/target/i686-linux-android/release/libupdater.a", - ] + libs += [ "//flutter/third_party/updater/target/i686-linux-android/release/libupdater.a" ] } else { assert(false, "Unsupported target_cpu") } diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 7f51fe353693f..0eed8a8c7e3b0 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -221,12 +221,9 @@ source_set("flutter_framework_source") { ] if (target_cpu == "arm64") { - libs = [ - "//flutter/third_party/updater/target/aarch64-apple-ios/release/libupdater.a", - ] + libs = [ "//flutter/third_party/updater/target/aarch64-apple-ios/release/libupdater.a" ] } else if (target_cpu == "x64") { - libs = - [ "//flutter/third_party/updater/target/x86_64-apple-ios/release/libupdater.a" ] + libs = [ "//flutter/third_party/updater/target/x86_64-apple-ios/release/libupdater.a" ] } else { assert(false, "Unsupported target_cpu") } From f08781695267387a7ea2d9ea377b9113f7eba1a3 Mon Sep 17 00:00:00 2001 From: bryanoltman Date: Thu, 30 Jan 2025 17:07:40 -0500 Subject: [PATCH 21/23] Fix gn --- shell/common/BUILD.gn | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index 6bd76b2c6f12d..a2f33967f78d2 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -177,14 +177,14 @@ source_set("common") { if (target_cpu == "x64") { libs = [ "userenv.lib", - "//third_party/updater/target/x86_64-pc-windows-msvc/release/updater.lib", + "//flutter/third_party/updater/target/x86_64-pc-windows-msvc/release/updater.lib", ] } } if (host_os == "linux" && target_os == "linux") { if (target_cpu == "x64") { - libs = [ "//third_party/updater/target/x86_64-unknown-linux-gnu/release/libupdater.a" ] + libs = [ "//flutter/third_party/updater/target/x86_64-unknown-linux-gnu/release/libupdater.a" ] } } } From eb6a1e561080fdacb4e9505848ec6727948cafe7 Mon Sep 17 00:00:00 2001 From: bryanoltman Date: Thu, 30 Jan 2025 18:11:58 -0500 Subject: [PATCH 22/23] Expose updater symbols in flutter_windows.dll --- common/exported_symbols.sym | 2 ++ common/exported_symbols_mac.sym | 1 + common/exported_test_symbols.sym | 1 + common/exported_test_symbols_mac.sym | 2 ++ shell/common/BUILD.gn | 1 + shell/common/shorebird/BUILD.gn | 1 + shell/platform/embedder/embedder_exports.lst | 1 + shell/platform/windows/BUILD.gn | 1 + 8 files changed, 10 insertions(+) diff --git a/common/exported_symbols.sym b/common/exported_symbols.sym index c9ad42cd3350e..5ff999e5cde65 100644 --- a/common/exported_symbols.sym +++ b/common/exported_symbols.sym @@ -7,4 +7,6 @@ kDartIsolateSnapshotInstructions; InternalFlutterGpu*; kInternalFlutterGpu*; + _shorebird*; + shorebird*; }; diff --git a/common/exported_symbols_mac.sym b/common/exported_symbols_mac.sym index 09687a75427f5..683f4a0f1b742 100644 --- a/common/exported_symbols_mac.sym +++ b/common/exported_symbols_mac.sym @@ -6,3 +6,4 @@ _kDartIsolateSnapshotData _kDartIsolateSnapshotInstructions _InternalFlutterGpu* _kInternalFlutterGpu* +_shorebird* diff --git a/common/exported_test_symbols.sym b/common/exported_test_symbols.sym index 12da485f23a4a..46fc139489319 100644 --- a/common/exported_test_symbols.sym +++ b/common/exported_test_symbols.sym @@ -11,4 +11,5 @@ LoadLibraryFromKernel; LookupEntryPoint; ForceShutdownIsolate; + shorebird*; }; diff --git a/common/exported_test_symbols_mac.sym b/common/exported_test_symbols_mac.sym index 83992eeeb9d89..01651fe78ef2b 100644 --- a/common/exported_test_symbols_mac.sym +++ b/common/exported_test_symbols_mac.sym @@ -10,3 +10,5 @@ _Spawn _LoadLibraryFromKernel _LookupEntryPoint _ForceShutdownIsolate +_shorebird* +shorebird* diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index a2f33967f78d2..72eec76fa1fdf 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -136,6 +136,7 @@ source_set("common") { "//flutter/third_party/rapidjson", "//flutter/third_party/tonic", "//flutter/third_party/txt", + "//flutter/third_party/updater:updater_headers", ] deps = [ diff --git a/shell/common/shorebird/BUILD.gn b/shell/common/shorebird/BUILD.gn index 4e31c785af768..d16aaf05612aa 100644 --- a/shell/common/shorebird/BUILD.gn +++ b/shell/common/shorebird/BUILD.gn @@ -28,6 +28,7 @@ source_set("shorebird") { "//flutter/runtime:libdart", "//flutter/shell/common", "//flutter/shell/platform/embedder:embedder_headers", + "//flutter/third_party/updater:updater_headers", ] include_dirs = [ "//flutter/updater" ] diff --git a/shell/platform/embedder/embedder_exports.lst b/shell/platform/embedder/embedder_exports.lst index d5755ed5c5724..6352fbc54dcce 100644 --- a/shell/platform/embedder/embedder_exports.lst +++ b/shell/platform/embedder/embedder_exports.lst @@ -15,6 +15,7 @@ kDartVmSnapshotInstructions; InternalFlutterGpu*; kInternalFlutterGpu*; + shorebird*; local: *; }; diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index 7389e5352f645..8b09f8710245c 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -154,6 +154,7 @@ source_set("flutter_windows_source") { deps = [ ":flutter_windows_headers", + "//flutter/third_party/updater:updater_headers", "//flutter/fml:fml", "//flutter/impeller/renderer/backend/gles", "//flutter/shell/common/shorebird:shorebird", From 643d74247cfaf9901e9ebacaf5a70f68bc41383b Mon Sep 17 00:00:00 2001 From: bryanoltman Date: Thu, 30 Jan 2025 18:12:31 -0500 Subject: [PATCH 23/23] Formatting --- shell/platform/windows/BUILD.gn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index 8b09f8710245c..c8f3700863ead 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -154,7 +154,6 @@ source_set("flutter_windows_source") { deps = [ ":flutter_windows_headers", - "//flutter/third_party/updater:updater_headers", "//flutter/fml:fml", "//flutter/impeller/renderer/backend/gles", "//flutter/shell/common/shorebird:shorebird", @@ -164,6 +163,7 @@ source_set("flutter_windows_source") { "//flutter/shell/platform/common/client_wrapper:client_wrapper", "//flutter/shell/platform/embedder:embedder_as_internal_library", "//flutter/shell/platform/windows/client_wrapper:client_wrapper_windows", + "//flutter/third_party/updater:updater_headers", # libEGL_static MUST be ordered before libGLESv2_static in this list. If # reversed, a linker error will occur, reporting DllMain already defined in