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 eb091429de1f3..2f543c8b3030d 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": "71b5ed65fab03fcf241edf0c74d027de9998da51", # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. @@ -258,26 +262,24 @@ 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 = { - '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', @@ -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/common/config.gni b/common/config.gni index 0a5a95306c2fd..66941233d72a8 100644 --- a/common/config.gni +++ b/common/config.gni @@ -73,6 +73,14 @@ 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) { + feature_defines_list += [ "SHOREBIRD_USES_MIXED_MODE=1" ] +} + if (is_ios || is_mac) { flutter_cflags_objc = [ "-Werror=overriding-method-mismatch", diff --git a/lib/snapshot/BUILD.gn b/lib/snapshot/BUILD.gn index 73611e45063ab..2509e27cfdbd3 100644 --- a/lib/snapshot/BUILD.gn +++ b/lib/snapshot/BUILD.gn @@ -34,14 +34,17 @@ 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" ] } # 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)" ] } } @@ -234,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") { diff --git a/runtime/dart_snapshot.cc b/runtime/dart_snapshot.cc index 5943524aa9f80..d20568cdc0a4e 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 SHOREBIRD_USES_MIXED_MODE + // 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..aee1fcc550f8f 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,27 @@ 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", + ] + } + } + + # 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/shell.cc b/shell/common/shell.cc index 453845b3b7836..9e626a95b6db7 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 SHOREBIRD_PLATFORM_SUPPORTED + 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..87df986a13502 --- /dev/null +++ b/shell/common/shorebird/BUILD.gn @@ -0,0 +1,55 @@ +import("//flutter/common/config.gni") +import("//flutter/testing/testing.gni") + +source_set("snapshots_data_handle") { + sources = [ + "snapshots_data_handle.cc", + "snapshots_data_handle.h", + ] + + deps = [ + "//flutter/fml", + "//flutter/runtime", + "//flutter/runtime:libdart", + "//flutter/shell/common", + ] +} + +source_set("shorebird") { + sources = [ + "shorebird.cc", + "shorebird.h", + ] + + deps = [ + ":snapshots_data_handle", + "//flutter/fml", + "//flutter/runtime", + "//flutter/runtime:libdart", + "//flutter/shell/common", + ] + + include_dirs = [ "//flutter/updater" ] +} + +if (enable_unittests) { + test_fixtures("shorebird_fixtures") { + fixtures = [] + } + + executable("shorebird_unittests") { + testonly = true + + sources = [ "snapshots_data_handle_unittests.cc" ] + + # This only includes snapshots_data_handle and not shorebird because + # shorebird fails to link due to a missing updater lib. + deps = [ + ":shorebird_fixtures", + ":snapshots_data_handle", + "//flutter/runtime", + "//flutter/testing", + "//flutter/testing:fixture_test", + ] + } +} diff --git a/shell/common/shorebird/shorebird.cc b/shell/common/shorebird/shorebird.cc new file mode 100644 index 0000000000000..6ec7b813ac7b9 --- /dev/null +++ b/shell/common/shorebird/shorebird.cc @@ -0,0 +1,302 @@ + +#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/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. + FML_LOG(INFO) << "Setting base snapshots"; + FML_LOG(INFO) << "getting vm snapshot"; + // segfaults on the next line + vm_snapshot = DartSnapshot::VMSnapshotFromSettings(settings); + FML_LOG(INFO) << "getting isolate snapshot"; + isolate_snapshot = DartSnapshot::IsolateSnapshotFromSettings(settings); + FML_LOG(INFO) << "Calling Shorebird_SetBaseSnapshots"; + 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, + }; +} + +// 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()); + + 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 + + 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(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..4e4b674aebdd6 --- /dev/null +++ b/shell/common/shorebird/shorebird.h @@ -0,0 +1,46 @@ +#ifndef FLUTTER_SHELL_COMMON_SHOREBIRD_SHOREBIRD_H_ +#define FLUTTER_SHELL_COMMON_SHOREBIRD_SHOREBIRD_H_ + +#include "flutter/common/settings.h" + +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) {} +}; + +void SetBaseSnapshot(Settings& settings); + +bool ConfigureShorebird(const ShorebirdConfigArgs& args, + std::string& patch_path); + +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); + +} // 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..1f483306bbab0 100644 --- a/shell/platform/android/android_exports.lst +++ b/shell/platform/android/android_exports.lst @@ -11,6 +11,17 @@ _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_check_for_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..b7ce1ca36ffe2 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -123,10 +123,12 @@ 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", "//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..d4f1606e81680 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -3,6 +3,7 @@ // found in the LICENSE file. #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h" +#include #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h" #include @@ -10,6 +11,8 @@ #include #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" @@ -680,6 +683,42 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint { }; FlutterRendererConfig rendererConfig = [_renderer createRendererConfig]; + + { + 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"}); + + NSLog(@"Constructing shorebird args"); + flutter::ShorebirdConfigArgs shorebirdArgs(cache_path, cache_path, + std::string(bundlePath.UTF8String), + std::string(shorebirdYamlContents.UTF8String), + { + std::string(appVersion.UTF8String), + std::string(appBuildNumber.UTF8String), + }); + NSLog(@"Constructed shorebird args"); + std::string _patch_path = ""; + NSLog(@"calling configure shorebird"); + if (!flutter::ConfigureShorebird(shorebirdArgs, _patch_path)) { + NSLog(@"Failed to configure shorebird"); + } + NSLog(@"Done configuring shorebird!"); + } + FlutterEngineResult result = _embedderAPI.Initialize( FLUTTER_ENGINE_VERSION, &rendererConfig, &flutterArguments, (__bridge void*)(self), &_engine); if (result != kSuccess) { 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..e1c0ea8194190 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,12 @@ FlutterEngineResult FlutterEngineInitialize(size_t version, "Could not infer the Flutter project to run from given arguments."); } +#if SHOREBIRD_USES_MIXED_MODE + FML_LOG(INFO) << "Using mixed mode, setting base snapshot"; + flutter::SetBaseSnapshot(settings); + FML_LOG(INFO) << "Set base snapshot"; +#endif + // Create the engine but don't launch the shell or run the root isolate. auto embedder_engine = std::make_unique( std::move(thread_host), // @@ -2324,31 +2331,39 @@ FlutterEngineResult FlutterEngineInitialize(size_t version, FlutterEngineResult FlutterEngineRunInitialized( FLUTTER_API_SYMBOL(FlutterEngine) engine) { + FML_LOG(ERROR) << "In FlutterEngineRunInitialized"; if (!engine) { return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); } auto embedder_engine = reinterpret_cast(engine); + FML_LOG(ERROR) << "Checking is valid"; // The engine must not already be running. Initialize may only be called // once on an engine instance. if (embedder_engine->IsValid()) { return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); } + FML_LOG(ERROR) << "Checking is valid done"; + FML_LOG(ERROR) << "Launching shell"; // Step 1: Launch the shell. if (!embedder_engine->LaunchShell()) { return LOG_EMBEDDER_ERROR(kInvalidArguments, "Could not launch the engine using supplied " "initialization arguments."); } + FML_LOG(ERROR) << "Launching shell done"; + FML_LOG(ERROR) << "running NotifyCreated"; // Step 2: Tell the platform view to initialize itself. if (!embedder_engine->NotifyCreated()) { return LOG_EMBEDDER_ERROR(kInternalInconsistency, "Could not create platform view components."); } + FML_LOG(ERROR) << "NotifyCreated done"; + FML_LOG(ERROR) << "running root isolate"; // Step 3: Launch the root isolate. if (!embedder_engine->RunRootIsolate()) { return LOG_EMBEDDER_ERROR( @@ -2356,6 +2371,7 @@ FlutterEngineResult FlutterEngineRunInitialized( "Could not run the root isolate of the Flutter application using the " "project arguments specified."); } + FML_LOG(ERROR) << "ran root isolate"; return kSuccess; } 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); 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():