diff --git a/DEPS b/DEPS index 3e8cc32f1d46b..2f543c8b3030d 100644 --- a/DEPS +++ b/DEPS @@ -18,7 +18,7 @@ vars = { "dart_sdk_revision": "4f9b5084a041a0e7cc26295da1da9109df43ac4c", "dart_sdk_git": "git@github.com:shorebirdtech/dart-sdk.git", "updater_git": "https://github.com/shorebirdtech/updater.git", - "updater_rev": "54e1e2ce2f82e5fe004bd0730c00d4310ab637dc", + "updater_rev": "71b5ed65fab03fcf241edf0c74d027de9998da51", # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. diff --git a/common/config.gni b/common/config.gni index 0a5a95306c2fd..66d300d99670d 100644 --- a/common/config.gni +++ b/common/config.gni @@ -73,6 +73,10 @@ if (slimpeller) { feature_defines_list += [ "SLIMPELLER=1" ] } +if (is_ios || is_mac || is_android || is_win) { + feature_defines_list += [ "SHOREBIRD_PLATFORM_SUPPORTED=1" ] +} + if (is_ios || is_mac) { flutter_cflags_objc = [ "-Werror=overriding-method-mismatch", diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 0878c020de259..9e626a95b6db7 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -444,7 +444,7 @@ Shell::Shell(DartVMRef vm, weak_factory_gpu_(nullptr), weak_factory_(this) { // FIXME: This is probably the wrong place to hook into. -#if FML_OS_ANDROID || FML_OS_IOS || FML_OS_MACOSX +#if SHOREBIRD_PLATFORM_SUPPORTED if (!vm_) { shorebird_report_launch_failure(); } else { diff --git a/shell/common/shorebird/BUILD.gn b/shell/common/shorebird/BUILD.gn index 4e31c785af768..0a28f76bce43b 100644 --- a/shell/common/shorebird/BUILD.gn +++ b/shell/common/shorebird/BUILD.gn @@ -31,6 +31,15 @@ source_set("shorebird") { ] include_dirs = [ "//flutter/updater" ] + + if (host_os == "win" && target_os == "win") { + if (target_cpu == "x64") { + libs = [ + "userenv.lib", + "//third_party/updater/target/x86_64-pc-windows-msvc/release/updater.lib", + ] + } + } } if (enable_unittests) { diff --git a/shell/common/shorebird/shorebird.cc b/shell/common/shorebird/shorebird.cc index 89d49d68fafa1..77608a0e29563 100644 --- a/shell/common/shorebird/shorebird.cc +++ b/shell/common/shorebird/shorebird.cc @@ -21,6 +21,7 @@ #include "flutter/shell/common/shorebird/snapshots_data_handle.h" #include "flutter/shell/common/switches.h" #include "fml/logging.h" +#include "shell/platform/embedder/embedder.h" #include "third_party/dart/runtime/include/dart_tools_api.h" #include "third_party/updater/library/include/updater.h" @@ -79,8 +80,100 @@ FileCallbacks ShorebirdFileCallbacks() { }; } +// FIXME: consolidate this with the other ConfigureShorebird +bool ConfigureShorebird(const ShorebirdConfigArgs& args, + std::string& patch_path) { + patch_path = args.release_app_library_path; + auto shorebird_updater_dir_name = "shorebird_updater"; + + auto code_cache_dir = fml::paths::JoinPaths( + {std::move(args.code_cache_path), shorebird_updater_dir_name}); + auto app_storage_dir = fml::paths::JoinPaths( + {std::move(args.app_storage_path), shorebird_updater_dir_name}); + + fml::CreateDirectory(fml::paths::GetCachesDirectory(), + {shorebird_updater_dir_name}, + fml::FilePermission::kReadWrite); + + bool init_result; + // Using a block to make AppParameters lifetime explicit. + { + AppParameters app_parameters; + // Combine version and version_code into a single string. + // We could also pass these separately through to the updater if needed. + auto release_version = + args.release_version.version + "+" + args.release_version.build_number; + app_parameters.release_version = release_version.c_str(); + app_parameters.code_cache_dir = code_cache_dir.c_str(); + app_parameters.app_storage_dir = app_storage_dir.c_str(); + + // https://stackoverflow.com/questions/26032039/convert-vectorstring-into-char-c + std::vector c_paths{}; + c_paths.push_back(args.release_app_library_path.c_str()); + // Do not modify application_library_path or c_strings will invalidate. + + app_parameters.original_libapp_paths = c_paths.data(); + app_parameters.original_libapp_paths_size = c_paths.size(); + + // shorebird_init copies from app_parameters and shorebirdYaml. + init_result = shorebird_init(&app_parameters, ShorebirdFileCallbacks(), + args.shorebird_yaml.c_str()); + } + + // We've decided not to support synchronous updates on launch for now. + // It's a terrible user experience (having the app hang on launch) and + // instead we will provide examples of how to build a custom update UI + // within Dart, including updating as part of login, etc. + // https://github.com/shorebirdtech/shorebird/issues/950 + + // We only set the base snapshot on iOS for now. + // TODO: this won't compile as we don't have a settings object here. + // #if FML_OS_IOS || FML_OS_MACOSX + // SetBaseSnapshot(settings); + // #endif + + FML_LOG(INFO) << "Checking for active patch"; + char* c_active_path = shorebird_next_boot_patch_path(); + if (c_active_path != NULL) { + patch_path = c_active_path; + shorebird_free_string(c_active_path); + FML_LOG(INFO) << "Shorebird updater: patch path: " << patch_path; + } else { + FML_LOG(INFO) << "Shorebird updater: no active patch."; + } + + // We are careful only to report a launch start in the case where it's the + // first time we've configured shorebird this process. Otherwise we could end + // up in a case where we report a launch start, but never a completion (e.g. + // from package:flutter_work_manager which sometimes creates a FlutterEngine + // (and thus configures shorebird) but never runs it. The proper fix for this + // is probably to move the launch_start() call to be later in the lifecycle + // (when the snapshot is loaded and run, rather than when FlutterEngine is + // initialized). This "hack" will still have a problem where FlutterEngine is + // initialized but never run before the app is quit, could still cause us to + // suddenly mark-bad a patch that was never actually attempted to launch. + if (!init_result) { + return false; + } + + // Once start_update_thread is called, the next_boot_patch* functions may + // change their return values if the shorebird_report_launch_failed + // function is called. + shorebird_report_launch_start(); + + if (shorebird_should_auto_update()) { + FML_LOG(INFO) << "Starting Shorebird update"; + shorebird_start_update_thread(); + } else { + FML_LOG(INFO) + << "Shorebird auto_update disabled, not checking for updates."; + } + + return true; +} + void ConfigureShorebird(const ShorebirdFlutterProjectArgs& args, - flutter::Settings& settings) { + Settings& settings) { // cache_path is used for both code_cache and app_storage, as we don't persist // any data between releases. args.app_path is appended to // the settings.application_library_path vector at this function's call site. diff --git a/shell/common/shorebird/shorebird.h b/shell/common/shorebird/shorebird.h index 1c6cb8bcd4443..b4efa4c28321b 100644 --- a/shell/common/shorebird/shorebird.h +++ b/shell/common/shorebird/shorebird.h @@ -6,12 +6,39 @@ namespace flutter { +struct ReleaseVersion { + std::string version; + std::string build_number; +}; + +struct ShorebirdConfigArgs { + std::string code_cache_path; + std::string app_storage_path; + std::string release_app_library_path; + std::string shorebird_yaml; + ReleaseVersion release_version; + + ShorebirdConfigArgs(std::string code_cache_path, + std::string app_storage_path, + std::string release_app_library_path, + std::string shorebird_yaml, + ReleaseVersion release_version) + : code_cache_path(code_cache_path), + app_storage_path(app_storage_path), + release_app_library_path(release_app_library_path), + shorebird_yaml(shorebird_yaml), + release_version(release_version) {} +}; + +bool ConfigureShorebird(const ShorebirdConfigArgs& args, + std::string& patch_path); + void ConfigureShorebird(const ShorebirdFlutterProjectArgs& args, - flutter::Settings& settings); + Settings& settings); void ConfigureShorebird(std::string code_cache_path, std::string app_storage_path, - flutter::Settings& settings, + Settings& settings, const std::string& shorebird_yaml, const std::string& version, const std::string& version_code); diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 6f6ffc2b24df9..f1646d85eab08 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -2306,13 +2306,16 @@ FlutterEngineResult FlutterEngineInitialize(size_t version, "Could not infer the Flutter project to run from given arguments."); } + // FIXME: This is probably the wrong place to call ConfigureShorebird, as + // some platforms (i.e., Windows) need to to swap out the app path before + // this point. // Begin shorebird +#if FML_OS_MACOSX if (args->shorebird_args.shorebird_yaml_contents) { settings.application_library_path.push_back(args->shorebird_args.app_path); flutter::ConfigureShorebird(args->shorebird_args, settings); - } else { - FML_LOG(INFO) << "[shorebird] No shorebird YAML contents provided."; } +#endif // End shorebird // Create the engine but don't launch the shell or run the root isolate. diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index e80662e592a72..7389e5352f645 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -156,6 +156,7 @@ source_set("flutter_windows_source") { ":flutter_windows_headers", "//flutter/fml:fml", "//flutter/impeller/renderer/backend/gles", + "//flutter/shell/common/shorebird:shorebird", "//flutter/shell/platform/common:common_cpp", "//flutter/shell/platform/common:common_cpp_input", "//flutter/shell/platform/common:common_cpp_switches", @@ -169,6 +170,7 @@ source_set("flutter_windows_source") { "//flutter/third_party/angle:libEGL_static", "//flutter/third_party/angle:libGLESv2_static", "//flutter/third_party/rapidjson", + "//flutter/third_party/tonic", ] } diff --git a/shell/platform/windows/flutter_project_bundle.h b/shell/platform/windows/flutter_project_bundle.h index 09770b5c094fc..8e9126dbacedd 100644 --- a/shell/platform/windows/flutter_project_bundle.h +++ b/shell/platform/windows/flutter_project_bundle.h @@ -44,6 +44,10 @@ class FlutterProjectBundle { // Sets engine switches. void SetSwitches(const std::vector& switches); + void SetAotLibraryPath(const std::filesystem::path& aot_library_path) { + aot_library_path_ = aot_library_path; + } + // Attempts to load AOT data for this bundle. The returned data must be // retained until any engine instance it is passed to has been shut down. // diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc index d08591a10b520..c6cff1b63b53d 100644 --- a/shell/platform/windows/flutter_windows_engine.cc +++ b/shell/platform/windows/flutter_windows_engine.cc @@ -5,15 +5,20 @@ #include "flutter/shell/platform/windows/flutter_windows_engine.h" #include +#include +#include +#include #include #include #include +#include #include "flutter/fml/logging.h" #include "flutter/fml/paths.h" #include "flutter/fml/platform/win/wstring_conversion.h" #include "flutter/fml/synchronization/waitable_event.h" +#include "flutter/shell/common/shorebird/shorebird.h" #include "flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h" #include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_message_codec.h" #include "flutter/shell/platform/common/path_utils.h" @@ -26,6 +31,7 @@ #include "flutter/shell/platform/windows/system_utils.h" #include "flutter/shell/platform/windows/task_runner.h" #include "flutter/third_party/accessibility/ax/ax_node.h" +#include "third_party/tonic/filesystem/filesystem/file.h" // winbase.h defines GetCurrentTime as a macro. #undef GetCurrentTime @@ -240,13 +246,125 @@ bool FlutterWindowsEngine::Run() { return Run(""); } +int GetReleaseVersionAndBuildNumber(ReleaseVersion* release_version) { + char module_path[MAX_PATH]; + // Get the full path of the currently running executable. The return value is + // the size of the string that was copied to the buffer, with -1 indicating + // failure. + if (GetModuleFileNameA(NULL, module_path, MAX_PATH) == -1) { + return -1; + } + + // Get the size of the version information + DWORD handle = -1; + DWORD version_info_size = GetFileVersionInfoSizeA(module_path, &handle); + if (version_info_size == -1) { + return -1; + } + + // Allocate memory for version info + // std::vector version_data(version_info_size); + std::unique_ptr version_data(new char[version_info_size]); + if (!GetFileVersionInfoA(module_path, handle, version_info_size, + version_data.get())) { + return -1; + } + + // Get the version info structure + VS_FIXEDFILEINFO* file_info = nullptr; + UINT file_info_size = -1; + if (!VerQueryValueA(version_data.get(), "\\", + reinterpret_cast(&file_info), &file_info_size)) { + return -1; + } + + if (file_info) { + // Extract version numbers + DWORD major = HIWORD(file_info->dwFileVersionMS); + DWORD minor = LOWORD(file_info->dwFileVersionMS); + DWORD build = HIWORD(file_info->dwFileVersionLS); + + char version[49]; + snprintf(version, sizeof(version), "%lu.%lu.%lu", major, minor, build); + release_version->version = std::string(version); + release_version->build_number = + std::to_string(LOWORD(file_info->dwFileVersionLS)); + return kSuccess; + } + + return -1; +} + +bool GetLocalAppDataPath(std::string& outPath) { + PWSTR path = nullptr; + HRESULT result = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &path); + if (!SUCCEEDED(result)) { + return false; + } + + std::wstring widePath(path); + std::string localAppDataPath(widePath.begin(), widePath.end()); + // The calling process is responsible for freeing this resource + // https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath + CoTaskMemFree(path); + outPath = localAppDataPath; + return true; +} + +bool SetUpShorebird(std::string assets_path_string, std::string& patch_path) { + auto shorebird_yaml_path = + fml::paths::JoinPaths({assets_path_string, "shorebird.yaml"}); + std::string shorebird_yaml_contents(""); + if (!filesystem::ReadFileToString(shorebird_yaml_path, + &shorebird_yaml_contents)) { + FML_LOG(ERROR) << "Failed to read shorebird.yaml."; + return false; + } + + std::string code_cache_path; + if (!GetLocalAppDataPath(code_cache_path)) { + FML_LOG(ERROR) << "Failed to retrieve the local AppData directory."; + return false; + } + + auto executable_location = fml::paths::GetExecutableDirectoryPath().second; + auto app_path = + fml::paths::JoinPaths({executable_location, "data", "app.so"}); + ReleaseVersion release_version; + auto release_version_result = + GetReleaseVersionAndBuildNumber(&release_version); + if (release_version_result != kSuccess) { + FML_LOG(ERROR) + << "Failed to retrieve the release version and build number."; + return false; + } + + ShorebirdConfigArgs shorebird_args(code_cache_path, code_cache_path, app_path, + shorebird_yaml_contents, release_version); + return ConfigureShorebird(shorebird_args, patch_path); +} + bool FlutterWindowsEngine::Run(std::string_view entrypoint) { + std::string assets_path_string = project_->assets_path().u8string(); + std::string icu_path_string = project_->icu_path().u8string(); + if (!project_->HasValidPaths()) { FML_LOG(ERROR) << "Missing or unresolvable paths to assets."; return false; } - std::string assets_path_string = project_->assets_path().u8string(); - std::string icu_path_string = project_->icu_path().u8string(); + + std::string patch_path; + auto setup_shorebird_result = SetUpShorebird(assets_path_string, patch_path); + if (setup_shorebird_result) { + // If we have a patch installed, we replace the default AOT library path + // with the patch path here. + FML_LOG(INFO) << "Setting project patch path: " << patch_path; + project_->SetAotLibraryPath(patch_path); + } else { + FML_LOG(ERROR) << "Failed to configure Shorebird."; + } + + // This loads AOT data from the project_'s aot_library_path_. if (embedder_api_.RunsAOTCompiledDartCode()) { aot_data_ = project_->LoadAotData(embedder_api_); if (!aot_data_) { @@ -370,6 +488,15 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) { host->root_isolate_create_callback_(); } }; + // Copied from shell\platform\darwin\macos\framework\Source\FlutterEngine.mm + // Writes log messages to stdout. + args.log_message_callback = [](const char* tag, const char* message, + void* user_data) { + if (tag && tag[0]) { + std::cout << tag << ": "; + } + std::cout << message << std::endl; + }; args.channel_update_callback = [](const FlutterChannelUpdate* update, void* user_data) { auto host = static_cast(user_data);