diff --git a/CLAUDE.md b/CLAUDE.md index 06c768c..0b38157 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -41,7 +41,7 @@ Design principle: "fail open" — always fall back to the currently installed ve ## Key Details -- C header (`include/updater.h`) is auto-generated by `cbindgen` via `build.rs` — don't edit manually. +- C headers (`include/updater_dart.h` for the ffigen-consumed Dart-stable surface, `include/updater_engine.h` for the engine-internal surface) are auto-generated by `cbindgen` via `build.rs` — don't edit manually. The two configs live at `library/cbindgen_dart.toml` and `library/cbindgen_engine.toml`. - Dart FFI bindings (`updater_bindings.g.dart`) are generated by `ffigen` — don't edit manually. - Library builds as three crate types: `lib` (Rust tests), `cdylib` (Dart FFI testing), `staticlib` (engine linking). - Boot state machine docs: `docs/boot_state_machine.md`. diff --git a/library/build.rs b/library/build.rs index 6596e07..9083031 100644 --- a/library/build.rs +++ b/library/build.rs @@ -1,25 +1,57 @@ extern crate cbindgen; use std::env; +use std::path::{Path, PathBuf}; // See: // // // fn main() { - let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - // Should this write to the out dir (target) instead? - let result = cbindgen::generate(crate_dir); + // Each header is generated from a single source file. cbindgen scans + // exactly that file and emits the `pub extern "C"` items it defines plus + // the C types they reference. Since each bucket file is self-contained + // (defines its own types), there is no cross-bucket leak and no need for + // exclusion lists in the cbindgen configs. + generate_header( + &crate_dir, + "cbindgen_dart.toml", + "src/c_api/dart.rs", + "include/updater_dart.h", + ); + generate_header( + &crate_dir, + "cbindgen_engine.toml", + "src/c_api/engine.rs", + "include/updater_engine.h", + ); +} + +fn generate_header(crate_dir: &Path, config_name: &str, src_relative: &str, output_path: &str) { + let config_path = crate_dir.join(config_name); + let config = match cbindgen::Config::from_file(&config_path) { + Ok(config) => config, + Err(e) => { + println!("cargo:warning=Error loading {}: {e}", config_path.display()); + return; + } + }; + + let src_path = crate_dir.join(src_relative); + let result = cbindgen::Builder::new() + .with_src(&src_path) + .with_config(config) + .generate(); match result { Ok(contents) => { - contents.write_to_file("include/updater.h"); + contents.write_to_file(output_path); } Err(e) => { - println!("cargo:warning=Error generating bindings: {e}"); - // If we were to exit 1 here we would stop local rust - // analysis from working. So we just print the error - // and continue. + println!("cargo:warning=Error generating {output_path}: {e}"); + // We don't exit non-zero here so local rust-analyzer keeps + // working when cbindgen has an issue. } } } diff --git a/library/cbindgen.toml b/library/cbindgen.toml deleted file mode 100644 index aa67c5a..0000000 --- a/library/cbindgen.toml +++ /dev/null @@ -1,20 +0,0 @@ -# See https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml -# for detailed documentation of every option here. -language = "C" -include_guard = "updater_h" -autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" -cpp_compat = true -line_length = 80 - -# I don't know if these are required to export the shorebird_ symbols -# since I've hit multiple levels of export trouble in libflutter.so. -# But I'm leaving them here for now. -after_includes = """ -#ifdef _WIN32 -#define SHOREBIRD_EXPORT __declspec(dllexport) -#else -#define SHOREBIRD_EXPORT __attribute__((visibility("default"))) -#endif -""" -[fn] -prefix = "SHOREBIRD_EXPORT" \ No newline at end of file diff --git a/library/cbindgen_dart.toml b/library/cbindgen_dart.toml new file mode 100644 index 0000000..a0cba68 --- /dev/null +++ b/library/cbindgen_dart.toml @@ -0,0 +1,30 @@ +# cbindgen configuration for the Dart-stable C surface. +# +# Output: include/updater_dart.h — consumed by ffigen in +# shorebird_code_push. Anything reachable from this header is part of the +# package's public ABI; do not break changes here without bumping the +# package version. +# +# build.rs runs cbindgen with `with_src("src/c_api/dart.rs")`, so cbindgen +# scans exactly that one file and emits the `pub extern "C"` items it +# defines plus the types they reference. Items defined in +# `src/c_api/engine.rs` cannot leak into this header. No exclude/include +# lists needed. +# +# See https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml +language = "C" +include_guard = "updater_dart_h" +autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" +cpp_compat = true +line_length = 80 + +after_includes = """ +#ifdef _WIN32 +#define SHOREBIRD_EXPORT __declspec(dllexport) +#else +#define SHOREBIRD_EXPORT __attribute__((visibility("default"))) +#endif +""" + +[fn] +prefix = "SHOREBIRD_EXPORT" diff --git a/library/cbindgen_engine.toml b/library/cbindgen_engine.toml new file mode 100644 index 0000000..82152c7 --- /dev/null +++ b/library/cbindgen_engine.toml @@ -0,0 +1,30 @@ +# cbindgen configuration for the engine-internal C surface. +# +# Output: include/updater_engine.h — consumed only by Shorebird's Flutter +# engine fork. No stability guarantee; both sides ship together as part of +# the engine, so this surface changes freely as the engine integration +# evolves. +# +# build.rs runs cbindgen with `with_src("src/c_api/engine.rs")`, so cbindgen +# scans exactly that one file and emits the `pub extern "C"` items it +# defines plus the types they reference. Items defined in +# `src/c_api/dart.rs` cannot leak into this header. No exclude/include +# lists needed. +# +# See https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml +language = "C" +include_guard = "updater_engine_h" +autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" +cpp_compat = true +line_length = 80 + +after_includes = """ +#ifdef _WIN32 +#define SHOREBIRD_EXPORT __declspec(dllexport) +#else +#define SHOREBIRD_EXPORT __attribute__((visibility("default"))) +#endif +""" + +[fn] +prefix = "SHOREBIRD_EXPORT" diff --git a/library/include/updater.h b/library/include/updater.h deleted file mode 100644 index 9860712..0000000 --- a/library/include/updater.h +++ /dev/null @@ -1,248 +0,0 @@ -#ifndef updater_h -#define updater_h - -/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ - -#include -#include -#include -#include -#ifdef _WIN32 -#define SHOREBIRD_EXPORT __declspec(dllexport) -#else -#define SHOREBIRD_EXPORT __attribute__((visibility("default"))) -#endif - - -/** - * An unknown error occurred while updating. The update was not installed. - * This is a catch-all for errors that don't fit into the other categories. - */ -#define SHOREBIRD_UPDATE_ERROR -1 - -/** - * No update is available (e.g. the app is already up-to-date) - */ -#define SHOREBIRD_NO_UPDATE 0 - -/** - * An update was installed successfully. It will boot from the update on the - * next app launch. - */ -#define SHOREBIRD_UPDATE_INSTALLED 1 - -/** - * An error occurred while updating. The update was not installed. - */ -#define SHOREBIRD_UPDATE_HAD_ERROR 2 - -/** - * The downloaded patch was not installed because it was invalid. - */ -#define SHOREBIRD_UPDATE_IS_BAD_PATCH 3 - -/** - * Another update was already in progress when this call was made. The - * already-running update will continue; the caller did not start a new one. - * This is a benign outcome, not an error. - */ -#define SHOREBIRD_UPDATE_IN_PROGRESS 4 - -/** - * Struct containing configuration parameters for the updater. - * Passed to all updater functions. - * NOTE: If this struct is changed all language bindings must be updated. - */ -typedef struct AppParameters { - /** - * release_version, required. Named version of the app, off of which - * updates are based. Can be either a version number or a hash. - */ - const char *release_version; - /** - * Array of paths to the original aot library, required. For Flutter apps - * these are the paths to the bundled libapp.so. May be used for - * compression downloaded artifacts. - */ - const char *const *original_libapp_paths; - /** - * Length of the original_libapp_paths array. - */ - int original_libapp_paths_size; - /** - * Path to app storage directory where the updater will store serialized - * state and other data that persists between releases. - */ - const char *app_storage_dir; - /** - * Path to cache directory where the updater will store downloaded - * artifacts and data that can be deleted when a new release is detected. - */ - const char *code_cache_dir; -} AppParameters; - -typedef struct FileCallbacks { - /** - * Opens the "file" (actually an in-memory buffer) and returns a handle. - */ - void *(*open)(void); - /** - * Reads count bytes from the file into buffer. Returns the number of - * bytes read. - */ - uintptr_t (*read)(void *file_handle, uint8_t *buffer, uintptr_t count); - /** - * Moves the file pointer to the given offset relative from whence (one of - * libc::SEEK_SET, libc::SEEK_CUR, or libc::SEEK_END). Returns the new - * offset relative to the start of the file. - */ - int64_t (*seek)(void *file_handle, int64_t offset, int32_t whence); - /** - * Closes and frees the file handle. - */ - void (*close)(void *file_handle); -} FileCallbacks; - -typedef struct UpdateResult { - int32_t status; - const char *message; -} UpdateResult; - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -/** - * Configures updater. First parameter is a struct containing configuration - * from the running app. Second parameter is a YAML string containing - * configuration compiled into the app. Returns true on success and false on - * failure. If false is returned, the updater library will not be usable. - */ -SHOREBIRD_EXPORT -bool shorebird_init(const struct AppParameters *c_params, - struct FileCallbacks c_file_callbacks, - const char *c_yaml); - -/** - * Returns if the app should run the updater automatically on launch. - */ -SHOREBIRD_EXPORT bool shorebird_should_auto_update(void); - -/** - * The currently running patch number, or 0 if the release has not been - * patched. The internal name for this concept is `running_patch`; the - * FFI symbol keeps the historical `current_boot_patch_number` spelling - * because Flutter Engine links against it. - */ -SHOREBIRD_EXPORT uintptr_t shorebird_current_boot_patch_number(void); - -/** - * The patch number that will boot on the next run of the app, or 0 if there is - * no next patch. - */ -SHOREBIRD_EXPORT uintptr_t shorebird_next_boot_patch_number(void); - -/** - * Performs integrity checks on the next boot patch. If the patch fails these checks, the patch - * will be deleted and the next boot patch will be set to the last successfully booted patch or - * the base release if there is no last successfully booted patch. - */ -SHOREBIRD_EXPORT -void shorebird_validate_next_boot_patch(void); - -/** - * The path to the patch that will boot on the next run of the app, or NULL if - * there is no next patch. - */ -SHOREBIRD_EXPORT char *shorebird_next_boot_patch_path(void); - -/** - * Free a string returned by the updater library. - * # Safety - * - * If this function is called with a non-null pointer, it must be a pointer - * returned by the updater library. - */ -SHOREBIRD_EXPORT void shorebird_free_string(const char *c_string); - -/** - * Frees an `UpdateResult` previously returned by `shorebird_check_for_update`. - * - * # Safety - * - * `result` must be a valid pointer returned by `shorebird_check_for_update`, - * or null (in which case this is a no-op). - */ -SHOREBIRD_EXPORT void shorebird_free_update_result(struct UpdateResult *result); - -/** - * Check for an update. Returns true if an update is available. - */ -SHOREBIRD_EXPORT bool shorebird_check_for_update(void); - -/** - * Check for an update on the first non-null channel of: - * 1. `c_channel` - * 2. The channel specified in shorebird.yaml - * 3. The default "stable" channel - * - * Returns true if an update exists that has not yet been downloaded. - */ -SHOREBIRD_EXPORT -bool shorebird_check_for_downloadable_update(const char *c_channel); - -/** - * Synchronously download an update if one is available. - */ -SHOREBIRD_EXPORT void shorebird_update(void); - -/** - * Synchronously download an update on the first non-null channel of: - * 1. `c_channel` - * 2. The channel specified in shorebird.yaml - * 3. The default "stable" channel - * - * Returns an [UpdateResult] indicating whether the update was successful. - */ -SHOREBIRD_EXPORT -const struct UpdateResult *shorebird_update_with_result(const char *c_channel); - -/** - * Start a thread to download an update if one is available. - */ -SHOREBIRD_EXPORT void shorebird_start_update_thread(void); - -/** - * Tell the updater that we're launching from what it told us was the - * next patch to boot from. This will copy the next boot patch to be the - * `current_boot` patch. - * - * It is required to call this function before calling - * `shorebird_report_launch_success` or `shorebird_report_launch_failure`. - */ -SHOREBIRD_EXPORT void shorebird_report_launch_start(void); - -/** - * Report that the app failed to launch. This will cause the updater to - * attempt to roll back to the previous version if this version has not - * been launched successfully before. - */ -SHOREBIRD_EXPORT void shorebird_report_launch_failure(void); - -/** - * Report that the app launched successfully. This will mark the current - * as having been launched successfully. We don't currently do anything - * with this information, but it could be used to record a point at which - * we will not roll back from. - * - * This is not currently wired up to be called from the Engine. It's unclear - * where best to connect it. Expo waits 5 seconds after the app launches - * and then marks the launch as successful. We could do something similar. - */ -SHOREBIRD_EXPORT void shorebird_report_launch_success(void); - -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus - -#endif /* updater_h */ diff --git a/library/include/updater_dart.h b/library/include/updater_dart.h new file mode 100644 index 0000000..1126e18 --- /dev/null +++ b/library/include/updater_dart.h @@ -0,0 +1,113 @@ +#ifndef updater_dart_h +#define updater_dart_h + +/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ + +#include +#include +#include +#include +#ifdef _WIN32 +#define SHOREBIRD_EXPORT __declspec(dllexport) +#else +#define SHOREBIRD_EXPORT __attribute__((visibility("default"))) +#endif + + +/** + * An unknown error occurred while updating. The update was not installed. + * This is a catch-all for errors that don't fit into the other categories. + */ +#define SHOREBIRD_UPDATE_ERROR -1 + +/** + * No update is available (e.g. the app is already up-to-date) + */ +#define SHOREBIRD_NO_UPDATE 0 + +/** + * An update was installed successfully. It will boot from the update on the + * next app launch. + */ +#define SHOREBIRD_UPDATE_INSTALLED 1 + +/** + * An error occurred while updating. The update was not installed. + */ +#define SHOREBIRD_UPDATE_HAD_ERROR 2 + +/** + * The downloaded patch was not installed because it was invalid. + */ +#define SHOREBIRD_UPDATE_IS_BAD_PATCH 3 + +/** + * Another update was already in progress when this call was made. The + * already-running update will continue; the caller did not start a new one. + * This is a benign outcome, not an error. + */ +#define SHOREBIRD_UPDATE_IN_PROGRESS 4 + +typedef struct UpdateResult { + int32_t status; + const char *message; +} UpdateResult; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * The currently running patch number, or 0 if the release has not been + * patched. The internal name for this concept is `running_patch`; the + * FFI symbol keeps the historical `current_boot_patch_number` spelling + * because the Flutter Engine and existing pub releases of + * `shorebird_code_push` link against it. + */ +SHOREBIRD_EXPORT uintptr_t shorebird_current_boot_patch_number(void); + +/** + * The patch number that will boot on the next run of the app, or 0 if there is + * no next patch. + */ +SHOREBIRD_EXPORT uintptr_t shorebird_next_boot_patch_number(void); + +/** + * Check for an update on the first non-null channel of: + * 1. `c_channel` + * 2. The channel specified in shorebird.yaml + * 3. The default "stable" channel + * + * Returns true if an update exists that has not yet been downloaded. + */ +SHOREBIRD_EXPORT +bool shorebird_check_for_downloadable_update(const char *c_channel); + +/** + * Synchronously download an update on the first non-null channel of: + * 1. `c_channel` + * 2. The channel specified in shorebird.yaml + * 3. The default "stable" channel + * + * Returns an [UpdateResult] indicating whether the update was successful. + */ +SHOREBIRD_EXPORT +const struct UpdateResult *shorebird_update_with_result(const char *c_channel); + +/** + * Frees an `UpdateResult` previously returned by + * `shorebird_update_with_result`. Frees the embedded `message` string and + * the result allocation itself. + * + * # Safety + * + * `result` must be a valid pointer returned by `shorebird_update_with_result`, + * or null (in which case this is a no-op). + */ +SHOREBIRD_EXPORT void shorebird_free_update_result(struct UpdateResult *result); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* updater_dart_h */ diff --git a/library/include/updater_engine.h b/library/include/updater_engine.h new file mode 100644 index 0000000..0c5503e --- /dev/null +++ b/library/include/updater_engine.h @@ -0,0 +1,151 @@ +#ifndef updater_engine_h +#define updater_engine_h + +/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ + +#include +#include +#include +#include +#ifdef _WIN32 +#define SHOREBIRD_EXPORT __declspec(dllexport) +#else +#define SHOREBIRD_EXPORT __attribute__((visibility("default"))) +#endif + + +/** + * Struct containing configuration parameters for the updater. + * Passed to `shorebird_init`. + * NOTE: If this struct is changed all language bindings must be updated. + */ +typedef struct AppParameters { + /** + * release_version, required. Named version of the app, off of which + * updates are based. Can be either a version number or a hash. + */ + const char *release_version; + /** + * Array of paths to the original aot library, required. For Flutter apps + * these are the paths to the bundled libapp.so. May be used for + * compression downloaded artifacts. + */ + const char *const *original_libapp_paths; + /** + * Length of the original_libapp_paths array. + */ + int original_libapp_paths_size; + /** + * Path to app storage directory where the updater will store serialized + * state and other data that persists between releases. + */ + const char *app_storage_dir; + /** + * Path to cache directory where the updater will store downloaded + * artifacts and data that can be deleted when a new release is detected. + */ + const char *code_cache_dir; +} AppParameters; + +typedef struct FileCallbacks { + /** + * Opens the "file" (actually an in-memory buffer) and returns a handle. + */ + void *(*open)(void); + /** + * Reads count bytes from the file into buffer. Returns the number of + * bytes read. + */ + uintptr_t (*read)(void *file_handle, uint8_t *buffer, uintptr_t count); + /** + * Moves the file pointer to the given offset relative from whence (one of + * libc::SEEK_SET, libc::SEEK_CUR, or libc::SEEK_END). Returns the new + * offset relative to the start of the file. + */ + int64_t (*seek)(void *file_handle, int64_t offset, int32_t whence); + /** + * Closes and frees the file handle. + */ + void (*close)(void *file_handle); +} FileCallbacks; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Free a string returned by the updater library. + * + * # Safety + * + * If this function is called with a non-null pointer, it must be a pointer + * returned by the updater library. + */ +SHOREBIRD_EXPORT void shorebird_free_string(const char *c_string); + +/** + * Configures the updater. First parameter is a struct containing + * configuration from the running app. Second parameter is a YAML string + * containing configuration compiled into the app. Returns true on success + * and false on failure. If false is returned, the updater library will not + * be usable. + */ +SHOREBIRD_EXPORT +bool shorebird_init(const struct AppParameters *c_params, + struct FileCallbacks c_file_callbacks, + const char *c_yaml); + +/** + * Returns if the app should run the updater automatically on launch. + */ +SHOREBIRD_EXPORT bool shorebird_should_auto_update(void); + +/** + * Performs integrity checks on the next boot patch. If the patch fails + * these checks, the patch will be deleted and the next boot patch will be + * set to the last successfully booted patch or the base release if there is + * no last successfully booted patch. + */ +SHOREBIRD_EXPORT void shorebird_validate_next_boot_patch(void); + +/** + * The path to the patch that will boot on the next run of the app, or NULL + * if there is no next patch. The caller must free the returned string with + * `shorebird_free_string`. + */ +SHOREBIRD_EXPORT char *shorebird_next_boot_patch_path(void); + +/** + * Start a thread to download an update if one is available. + */ +SHOREBIRD_EXPORT void shorebird_start_update_thread(void); + +/** + * Tell the updater that we're launching from what it told us was the + * next patch to boot from. This will copy the next boot patch to be the + * `current_boot` patch. + * + * It is required to call this function before calling + * `shorebird_report_launch_success` or `shorebird_report_launch_failure`. + */ +SHOREBIRD_EXPORT void shorebird_report_launch_start(void); + +/** + * Report that the app failed to launch. This will cause the updater to + * attempt to roll back to the previous version if this version has not been + * launched successfully before. + */ +SHOREBIRD_EXPORT void shorebird_report_launch_failure(void); + +/** + * Report that the app launched successfully. The Shell constructor calls + * this once per process when the VM has finished booting; it pairs with + * `shorebird_report_launch_start` to mark a patch as having booted cleanly. + */ +SHOREBIRD_EXPORT void shorebird_report_launch_success(void); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* updater_engine_h */ diff --git a/library/src/c_api/c_file.rs b/library/src/c_api/c_file.rs index 83b86e2..5cdcea9 100644 --- a/library/src/c_api/c_file.rs +++ b/library/src/c_api/c_file.rs @@ -2,7 +2,7 @@ use std::io::{Read, Seek}; use crate::{ExternalFileProvider, ReadSeek}; -use super::FileCallbacks; +use super::engine::FileCallbacks; struct CFile { file_callbacks: FileCallbacks, diff --git a/library/src/c_api/dart.rs b/library/src/c_api/dart.rs new file mode 100644 index 0000000..d0d3732 --- /dev/null +++ b/library/src/c_api/dart.rs @@ -0,0 +1,138 @@ +//! Dart-stable C API surface. +//! +//! These symbols are consumed by `package:shorebird_code_push` via FFI. The +//! header generated for this module (`include/updater_dart.h`) is the input +//! to ffigen, so anything declared here is part of the package's public ABI. +//! Do not break changes here without bumping the package version. +//! +//! cbindgen reads this file directly (see `build.rs`) and emits exactly the +//! `pub extern "C"` items found here plus the types they reference. Adding +//! a function to this file automatically adds it to the Dart header — no +//! cbindgen-config update required. Conversely, anything declared in +//! `c_api::engine` cannot leak into this header. +//! +//! For symbols consumed only by Shorebird's Flutter engine, see +//! `c_api::engine` — that surface is unstable and changes freely. +use std::os::raw::c_char; + +use super::{allocate_c_string, free_c_string, log_on_error, to_rust_option}; +use crate::{updater, UpdateStatus}; + +/// An unknown error occurred while updating. The update was not installed. +/// This is a catch-all for errors that don't fit into the other categories. +pub const SHOREBIRD_UPDATE_ERROR: i32 = -1; + +/// No update is available (e.g. the app is already up-to-date) +pub const SHOREBIRD_NO_UPDATE: i32 = 0; + +/// An update was installed successfully. It will boot from the update on the +/// next app launch. +pub const SHOREBIRD_UPDATE_INSTALLED: i32 = 1; + +/// An error occurred while updating. The update was not installed. +pub const SHOREBIRD_UPDATE_HAD_ERROR: i32 = 2; + +/// The downloaded patch was not installed because it was invalid. +pub const SHOREBIRD_UPDATE_IS_BAD_PATCH: i32 = 3; + +/// Another update was already in progress when this call was made. The +/// already-running update will continue; the caller did not start a new one. +/// This is a benign outcome, not an error. +pub const SHOREBIRD_UPDATE_IN_PROGRESS: i32 = 4; + +#[repr(C)] +pub struct UpdateResult { + pub status: i32, + pub message: *const libc::c_char, +} + +fn to_update_result(status: anyhow::Result) -> UpdateResult { + match status { + Ok(status) => { + let message = status.to_string(); + UpdateResult { + status: status as i32, + message: allocate_c_string(message.as_str()).unwrap_or(std::ptr::null_mut()), + } + } + Err(err) => UpdateResult { + status: SHOREBIRD_UPDATE_ERROR, + message: allocate_c_string(&err.to_string()).unwrap_or(std::ptr::null_mut()), + }, + } +} + +/// The currently running patch number, or 0 if the release has not been +/// patched. The internal name for this concept is `running_patch`; the +/// FFI symbol keeps the historical `current_boot_patch_number` spelling +/// because the Flutter Engine and existing pub releases of +/// `shorebird_code_push` link against it. +#[no_mangle] +pub extern "C" fn shorebird_current_boot_patch_number() -> usize { + log_on_error( + || Ok(updater::running_patch()?.map_or(0, |p| p.number)), + "fetching running_patch_number", + 0, + ) +} + +/// The patch number that will boot on the next run of the app, or 0 if there is +/// no next patch. +#[no_mangle] +pub extern "C" fn shorebird_next_boot_patch_number() -> usize { + log_on_error( + || Ok(updater::next_boot_patch()?.map_or(0, |p| p.number)), + "fetching next_boot_patch_number", + 0, + ) +} + +/// Check for an update on the first non-null channel of: +/// 1. `c_channel` +/// 2. The channel specified in shorebird.yaml +/// 3. The default "stable" channel +/// +/// Returns true if an update exists that has not yet been downloaded. +#[no_mangle] +pub extern "C" fn shorebird_check_for_downloadable_update(c_channel: *const c_char) -> bool { + log_on_error( + || { + let channel = to_rust_option(c_channel)?; + updater::check_for_downloadable_update(channel.as_deref()) + }, + "checking for update", + false, + ) +} + +/// Synchronously download an update on the first non-null channel of: +/// 1. `c_channel` +/// 2. The channel specified in shorebird.yaml +/// 3. The default "stable" channel +/// +/// Returns an [UpdateResult] indicating whether the update was successful. +#[no_mangle] +pub extern "C" fn shorebird_update_with_result(c_channel: *const c_char) -> *const UpdateResult { + let result = match to_rust_option(c_channel) { + Ok(channel) => to_update_result(updater::update(channel.as_deref())), + Err(err) => to_update_result(Err(err)), + }; + Box::into_raw(Box::new(result)) +} + +/// Frees an `UpdateResult` previously returned by +/// `shorebird_update_with_result`. Frees the embedded `message` string and +/// the result allocation itself. +/// +/// # Safety +/// +/// `result` must be a valid pointer returned by `shorebird_update_with_result`, +/// or null (in which case this is a no-op). +#[no_mangle] +pub unsafe extern "C" fn shorebird_free_update_result(result: *mut UpdateResult) { + if result.is_null() { + return; + } + let result = unsafe { Box::from_raw(result) }; + unsafe { free_c_string(result.message) }; +} diff --git a/library/src/c_api/engine.rs b/library/src/c_api/engine.rs new file mode 100644 index 0000000..434f49b --- /dev/null +++ b/library/src/c_api/engine.rs @@ -0,0 +1,220 @@ +//! Engine-internal C API surface. +//! +//! These symbols are consumed by Shorebird's Flutter engine +//! (`flutter::shorebird::Updater` in `shell/common/shorebird/updater.cc`) and +//! by no other public consumer. Both sides ship together as part of the +//! engine, so this surface has no stability guarantee — change freely as the +//! engine integration evolves. +//! +//! cbindgen reads this file directly (see `build.rs`) and emits exactly the +//! `pub extern "C"` items found here plus the types they reference. Adding +//! a function to this file automatically adds it to the engine header — no +//! cbindgen-config update required. Conversely, anything declared in +//! `c_api::dart` cannot leak into this header. +//! +//! For the stable Dart surface consumed by `package:shorebird_code_push`, +//! see `c_api::dart`. +use std::os::raw::c_char; +use std::path::PathBuf; + +use super::{allocate_c_string, free_c_string, log_on_error, to_rust}; +use crate::c_api::c_file::CFileProvider; +use crate::updater; + +/// Struct containing configuration parameters for the updater. +/// Passed to `shorebird_init`. +/// NOTE: If this struct is changed all language bindings must be updated. +#[repr(C)] +pub struct AppParameters { + /// release_version, required. Named version of the app, off of which + /// updates are based. Can be either a version number or a hash. + pub release_version: *const libc::c_char, + + /// Array of paths to the original aot library, required. For Flutter apps + /// these are the paths to the bundled libapp.so. May be used for + /// compression downloaded artifacts. + pub original_libapp_paths: *const *const libc::c_char, + + /// Length of the original_libapp_paths array. + pub original_libapp_paths_size: libc::c_int, + + /// Path to app storage directory where the updater will store serialized + /// state and other data that persists between releases. + pub app_storage_dir: *const libc::c_char, + + /// Path to cache directory where the updater will store downloaded + /// artifacts and data that can be deleted when a new release is detected. + pub code_cache_dir: *const libc::c_char, +} + +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct FileCallbacks { + /// Opens the "file" (actually an in-memory buffer) and returns a handle. + pub open: extern "C" fn() -> *mut libc::c_void, + + /// Reads count bytes from the file into buffer. Returns the number of + /// bytes read. + pub read: extern "C" fn(file_handle: *mut libc::c_void, buffer: *mut u8, count: usize) -> usize, + + /// Moves the file pointer to the given offset relative from whence (one of + /// libc::SEEK_SET, libc::SEEK_CUR, or libc::SEEK_END). Returns the new + /// offset relative to the start of the file. + pub seek: extern "C" fn(file_handle: *mut libc::c_void, offset: i64, whence: i32) -> i64, + + /// Closes and frees the file handle. + pub close: extern "C" fn(file_handle: *mut libc::c_void), +} + +fn to_rust_vector( + c_array: *const *const libc::c_char, + size: libc::c_int, +) -> anyhow::Result> { + let mut result = Vec::new(); + for i in 0..size { + let c_string = unsafe { *c_array.offset(i as isize) }; + result.push(to_rust(c_string)?); + } + Ok(result) +} + +fn app_config_from_c(c_params: *const AppParameters) -> anyhow::Result { + anyhow::ensure!( + !c_params.is_null(), + "Null parameters passed to app_config_from_c" + ); + let c_params_ref = unsafe { &*c_params }; + + Ok(updater::AppConfig { + app_storage_dir: to_rust(c_params_ref.app_storage_dir)?, + code_cache_dir: to_rust(c_params_ref.code_cache_dir)?, + release_version: to_rust(c_params_ref.release_version)?, + original_libapp_paths: to_rust_vector( + c_params_ref.original_libapp_paths, + c_params_ref.original_libapp_paths_size, + )?, + }) +} + +fn path_to_c_string(path: Option) -> anyhow::Result<*mut c_char> { + Ok(match path { + Some(v) => allocate_c_string(v.to_str().unwrap())?, + None => std::ptr::null_mut(), + }) +} + +/// Free a string returned by the updater library. +/// +/// # Safety +/// +/// If this function is called with a non-null pointer, it must be a pointer +/// returned by the updater library. +#[no_mangle] +pub unsafe extern "C" fn shorebird_free_string(c_string: *const c_char) { + unsafe { free_c_string(c_string) } +} + +/// Configures the updater. First parameter is a struct containing +/// configuration from the running app. Second parameter is a YAML string +/// containing configuration compiled into the app. Returns true on success +/// and false on failure. If false is returned, the updater library will not +/// be usable. +#[no_mangle] +pub extern "C" fn shorebird_init( + c_params: *const AppParameters, + c_file_callbacks: FileCallbacks, + c_yaml: *const libc::c_char, +) -> bool { + log_on_error( + || { + let config = app_config_from_c(c_params)?; + let file_provider = Box::new(CFileProvider { + file_callbacks: c_file_callbacks, + }); + let yaml_string = to_rust(c_yaml)?; + updater::init(config, file_provider, &yaml_string)?; + Ok(true) + }, + "initializing updater", + false, + ) +} + +/// Returns if the app should run the updater automatically on launch. +#[no_mangle] +pub extern "C" fn shorebird_should_auto_update() -> bool { + log_on_error( + updater::should_auto_update, + "fetching update behavior", + true, + ) +} + +/// Performs integrity checks on the next boot patch. If the patch fails +/// these checks, the patch will be deleted and the next boot patch will be +/// set to the last successfully booted patch or the base release if there is +/// no last successfully booted patch. +#[no_mangle] +pub extern "C" fn shorebird_validate_next_boot_patch() { + log_on_error( + updater::validate_next_boot_patch, + "validating next_boot_patch", + (), + ); +} + +/// The path to the patch that will boot on the next run of the app, or NULL +/// if there is no next patch. The caller must free the returned string with +/// `shorebird_free_string`. +#[no_mangle] +pub extern "C" fn shorebird_next_boot_patch_path() -> *mut c_char { + log_on_error( + || { + let maybe_path = updater::next_boot_patch()?.map(|p| p.path); + path_to_c_string(maybe_path) + }, + "fetching next_boot_patch_path", + std::ptr::null_mut(), + ) +} + +/// Start a thread to download an update if one is available. +#[no_mangle] +pub extern "C" fn shorebird_start_update_thread() { + updater::start_update_thread(); +} + +/// Tell the updater that we're launching from what it told us was the +/// next patch to boot from. This will copy the next boot patch to be the +/// `current_boot` patch. +/// +/// It is required to call this function before calling +/// `shorebird_report_launch_success` or `shorebird_report_launch_failure`. +#[no_mangle] +pub extern "C" fn shorebird_report_launch_start() { + log_on_error(updater::report_launch_start, "reporting launch start", ()); +} + +/// Report that the app failed to launch. This will cause the updater to +/// attempt to roll back to the previous version if this version has not been +/// launched successfully before. +#[no_mangle] +pub extern "C" fn shorebird_report_launch_failure() { + log_on_error( + updater::report_launch_failure, + "reporting launch failure", + (), + ); +} + +/// Report that the app launched successfully. The Shell constructor calls +/// this once per process when the VM has finished booting; it pairs with +/// `shorebird_report_launch_start` to mark a patch as having booted cleanly. +#[no_mangle] +pub extern "C" fn shorebird_report_launch_success() { + log_on_error( + updater::report_launch_success, + "reporting launch success", + (), + ); +} diff --git a/library/src/c_api/mod.rs b/library/src/c_api/mod.rs index e256923..0c05243 100644 --- a/library/src/c_api/mod.rs +++ b/library/src/c_api/mod.rs @@ -1,106 +1,41 @@ -// This file handles translating the updater library's types into C types. - -// Currently manually prefixing all functions with "shorebird_" to avoid -// name collisions with other libraries. -// `cbindgen:prefix-with-name` could do this for us. - -/// This file contains the C API for the updater library. -/// It is intended to be used by language bindings, and is not intended to be -/// used directly by Rust code. -/// The C API is not stable and may change at any time. -/// You can see usage of this API in Shorebird's Flutter engine: -/// +// This module translates the updater library's types into C types. +// +// The C surface is split into two submodules, each fully self-contained: +// - `dart` — stable surface consumed by `package:shorebird_code_push` +// (header: `include/updater_dart.h`, driven by ffigen). +// - `engine` — surface consumed only by Shorebird's Flutter engine +// (header: `include/updater_engine.h`, no stability guarantee). +// +// Each submodule defines the `pub extern "C"` items it exports plus any C +// types those items reference. cbindgen scans the submodule files directly +// (see `build.rs`) — there is no cross-bucket exclusion list, and a new +// function added to one bucket cannot leak into the other bucket's header. +// +// Items in this file are private Rust helpers shared between the two +// buckets. They are not `extern "C"`, so cbindgen never emits them. +// +// Engine-side usage lives at `engine/src/flutter/shell/common/shorebird/updater.cc` +// in the Shorebird Flutter monorepo: . use std::ffi::{CStr, CString}; use std::os::raw::c_char; -use std::path::PathBuf; - -use crate::{updater, UpdateStatus}; - -use self::c_file::CFileProvider; mod c_file; +pub mod dart; +pub mod engine; -/// Struct containing configuration parameters for the updater. -/// Passed to all updater functions. -/// NOTE: If this struct is changed all language bindings must be updated. -#[repr(C)] -pub struct AppParameters { - /// release_version, required. Named version of the app, off of which - /// updates are based. Can be either a version number or a hash. - pub release_version: *const libc::c_char, - - /// Array of paths to the original aot library, required. For Flutter apps - /// these are the paths to the bundled libapp.so. May be used for - /// compression downloaded artifacts. - pub original_libapp_paths: *const *const libc::c_char, - - /// Length of the original_libapp_paths array. - pub original_libapp_paths_size: libc::c_int, - - /// Path to app storage directory where the updater will store serialized - /// state and other data that persists between releases. - pub app_storage_dir: *const libc::c_char, - - /// Path to cache directory where the updater will store downloaded - /// artifacts and data that can be deleted when a new release is detected. - pub code_cache_dir: *const libc::c_char, -} - -/// An unknown error occurred while updating. The update was not installed. -/// This is a catch-all for errors that don't fit into the other categories. -pub const SHOREBIRD_UPDATE_ERROR: i32 = -1; - -/// No update is available (e.g. the app is already up-to-date) -pub const SHOREBIRD_NO_UPDATE: i32 = 0; - -/// An update was installed successfully. It will boot from the update on the -/// next app launch. -pub const SHOREBIRD_UPDATE_INSTALLED: i32 = 1; - -/// An error occurred while updating. The update was not installed. -pub const SHOREBIRD_UPDATE_HAD_ERROR: i32 = 2; - -/// The downloaded patch was not installed because it was invalid. -pub const SHOREBIRD_UPDATE_IS_BAD_PATCH: i32 = 3; - -/// Another update was already in progress when this call was made. The -/// already-running update will continue; the caller did not start a new one. -/// This is a benign outcome, not an error. -pub const SHOREBIRD_UPDATE_IN_PROGRESS: i32 = 4; - -#[repr(C)] -pub struct UpdateResult { - pub status: i32, - pub message: *const libc::c_char, -} - -#[derive(Clone, Copy, Debug)] -#[repr(C)] -pub struct FileCallbacks { - /// Opens the "file" (actually an in-memory buffer) and returns a handle. - pub open: extern "C" fn() -> *mut libc::c_void, - - /// Reads count bytes from the file into buffer. Returns the number of - /// bytes read. - pub read: extern "C" fn(file_handle: *mut libc::c_void, buffer: *mut u8, count: usize) -> usize, - - /// Moves the file pointer to the given offset relative from whence (one of - /// libc::SEEK_SET, libc::SEEK_CUR, or libc::SEEK_END). Returns the new - /// offset relative to the start of the file. - pub seek: extern "C" fn(file_handle: *mut libc::c_void, offset: i64, whence: i32) -> i64, - - /// Closes and frees the file handle. - pub close: extern "C" fn(file_handle: *mut libc::c_void), -} +#[cfg(test)] +pub use self::dart::*; +#[cfg(test)] +pub use self::engine::*; /// Converts a C string to a Rust string, does not free the C string. -fn to_rust(c_string: *const libc::c_char) -> anyhow::Result { +pub(super) fn to_rust(c_string: *const libc::c_char) -> anyhow::Result { anyhow::ensure!(!c_string.is_null(), "Null string passed to to_rust"); let c_str = unsafe { CStr::from_ptr(c_string) }; Ok(c_str.to_str()?.to_string()) } -fn to_rust_option(c_string: *const c_char) -> anyhow::Result> { +pub(super) fn to_rust_option(c_string: *const c_char) -> anyhow::Result> { if c_string.is_null() { return Ok(None); } @@ -108,168 +43,21 @@ fn to_rust_option(c_string: *const c_char) -> anyhow::Result> { } /// Converts a Rust string to a C string, caller must free the C string. -fn allocate_c_string(rust_string: &str) -> anyhow::Result<*mut c_char> { +pub(super) fn allocate_c_string(rust_string: &str) -> anyhow::Result<*mut c_char> { let c_str = CString::new(rust_string)?; Ok(c_str.into_raw()) } -fn to_rust_vector( - c_array: *const *const libc::c_char, - size: libc::c_int, -) -> anyhow::Result> { - let mut result = Vec::new(); - for i in 0..size { - let c_string = unsafe { *c_array.offset(i as isize) }; - result.push(to_rust(c_string)?); - } - Ok(result) -} - -fn app_config_from_c(c_params: *const AppParameters) -> anyhow::Result { - anyhow::ensure!( - !c_params.is_null(), - "Null parameters passed to app_config_from_c" - ); - let c_params_ref = unsafe { &*c_params }; - - Ok(updater::AppConfig { - app_storage_dir: to_rust(c_params_ref.app_storage_dir)?, - code_cache_dir: to_rust(c_params_ref.code_cache_dir)?, - release_version: to_rust(c_params_ref.release_version)?, - original_libapp_paths: to_rust_vector( - c_params_ref.original_libapp_paths, - c_params_ref.original_libapp_paths_size, - )?, - }) -} - -/// Helper function to log errors instead of panicking or returning a result. -fn log_on_error(f: F, context: &str, error_result: R) -> R -where - F: FnOnce() -> Result, -{ - f().unwrap_or_else(|e| { - shorebird_error!("Error {}: {:?}", context, e); - error_result - }) -} - -/// Configures updater. First parameter is a struct containing configuration -/// from the running app. Second parameter is a YAML string containing -/// configuration compiled into the app. Returns true on success and false on -/// failure. If false is returned, the updater library will not be usable. -#[no_mangle] -pub extern "C" fn shorebird_init( - c_params: *const AppParameters, - c_file_callbacks: FileCallbacks, - c_yaml: *const libc::c_char, -) -> bool { - log_on_error( - || { - let config = app_config_from_c(c_params)?; - let file_provider = Box::new(CFileProvider { - file_callbacks: c_file_callbacks, - }); - let yaml_string = to_rust(c_yaml)?; - updater::init(config, file_provider, &yaml_string)?; - Ok(true) - }, - "initializing updater", - false, - ) -} - -/// Returns if the app should run the updater automatically on launch. -#[no_mangle] -pub extern "C" fn shorebird_should_auto_update() -> bool { - log_on_error( - updater::should_auto_update, - "fetching update behavior", - true, - ) -} - -/// The currently running patch number, or 0 if the release has not been -/// patched. The internal name for this concept is `running_patch`; the -/// FFI symbol keeps the historical `current_boot_patch_number` spelling -/// because Flutter Engine links against it. -#[no_mangle] -pub extern "C" fn shorebird_current_boot_patch_number() -> usize { - log_on_error( - || Ok(updater::running_patch()?.map_or(0, |p| p.number)), - "fetching running_patch_number", - 0, - ) -} - -/// The patch number that will boot on the next run of the app, or 0 if there is -/// no next patch. -#[no_mangle] -pub extern "C" fn shorebird_next_boot_patch_number() -> usize { - log_on_error( - || Ok(updater::next_boot_patch()?.map_or(0, |p| p.number)), - "fetching next_boot_patch_number", - 0, - ) -} - -fn path_to_c_string(path: Option) -> anyhow::Result<*mut c_char> { - Ok(match path { - Some(v) => allocate_c_string(v.to_str().unwrap())?, - None => std::ptr::null_mut(), - }) -} - -fn to_update_result(status: anyhow::Result) -> UpdateResult { - let result = match status { - Ok(status) => { - let message = status.to_string(); - return UpdateResult { - status: status as i32, - message: allocate_c_string(message.as_str()).unwrap_or(std::ptr::null_mut()), - }; - } - Err(err) => UpdateResult { - status: SHOREBIRD_UPDATE_ERROR, - message: allocate_c_string(&err.to_string()).unwrap_or(std::ptr::null_mut()), - }, - }; - result -} - -/// Performs integrity checks on the next boot patch. If the patch fails these checks, the patch -/// will be deleted and the next boot patch will be set to the last successfully booted patch or -/// the base release if there is no last successfully booted patch. -#[no_mangle] -pub extern "C" fn shorebird_validate_next_boot_patch() { - log_on_error( - updater::validate_next_boot_patch, - "validating next_boot_patch", - (), - ); -} - -/// The path to the patch that will boot on the next run of the app, or NULL if -/// there is no next patch. -#[no_mangle] -pub extern "C" fn shorebird_next_boot_patch_path() -> *mut c_char { - log_on_error( - || { - let maybe_path = updater::next_boot_patch()?.map(|p| p.path); - path_to_c_string(maybe_path) - }, - "fetching next_boot_patch_path", - std::ptr::null_mut(), - ) -} - -/// Free a string returned by the updater library. +/// Drops a C string previously allocated by `allocate_c_string`. No-op on +/// null. Callable by both buckets — `engine::shorebird_free_string` and +/// `dart::shorebird_free_update_result` both delegate here so the +/// CString-from-raw unsafe ownership logic lives in one place. +/// /// # Safety /// -/// If this function is called with a non-null pointer, it must be a pointer -/// returned by the updater library. -#[no_mangle] -pub unsafe extern "C" fn shorebird_free_string(c_string: *const c_char) { +/// `c_string` must be null or a pointer previously returned by +/// `allocate_c_string` and not yet freed. +pub(super) unsafe fn free_c_string(c_string: *const c_char) { if c_string.is_null() { return; } @@ -278,124 +66,15 @@ pub unsafe extern "C" fn shorebird_free_string(c_string: *const c_char) { } } -/// Frees an `UpdateResult` previously returned by `shorebird_check_for_update`. -/// -/// # Safety -/// -/// `result` must be a valid pointer returned by `shorebird_check_for_update`, -/// or null (in which case this is a no-op). -#[no_mangle] -pub unsafe extern "C" fn shorebird_free_update_result(result: *mut UpdateResult) { - if result.is_null() { - return; - } - let message = (*result).message; - if !message.is_null() { - shorebird_free_string(message); - } - unsafe { - drop(Box::from_raw(result)); - } -} - -/// Check for an update. Returns true if an update is available. -#[no_mangle] -pub extern "C" fn shorebird_check_for_update() -> bool { - log_on_error( - || updater::check_for_downloadable_update(None), - "checking for update", - false, - ) -} - -/// Check for an update on the first non-null channel of: -/// 1. `c_channel` -/// 2. The channel specified in shorebird.yaml -/// 3. The default "stable" channel -/// -/// Returns true if an update exists that has not yet been downloaded. -#[no_mangle] -pub extern "C" fn shorebird_check_for_downloadable_update(c_channel: *const c_char) -> bool { - log_on_error( - || { - let channel = to_rust_option(c_channel)?; - updater::check_for_downloadable_update(channel.as_deref()) - }, - "checking for update", - false, - ) -} - -/// Synchronously download an update if one is available. -#[no_mangle] -pub extern "C" fn shorebird_update() { - log_on_error( - || updater::update(None).map(|result| shorebird_info!("Update result: {}", result)), - "downloading update", - (), - ); -} - -/// Synchronously download an update on the first non-null channel of: -/// 1. `c_channel` -/// 2. The channel specified in shorebird.yaml -/// 3. The default "stable" channel -/// -/// Returns an [UpdateResult] indicating whether the update was successful. -#[no_mangle] -pub extern "C" fn shorebird_update_with_result(c_channel: *const c_char) -> *const UpdateResult { - let channel = to_rust_option(c_channel); - let result = match channel { - Ok(channel) => to_update_result(updater::update(channel.as_deref())), - Err(err) => to_update_result(Err(err)), - }; - Box::into_raw(Box::new(result)) -} - -/// Start a thread to download an update if one is available. -#[no_mangle] -pub extern "C" fn shorebird_start_update_thread() { - updater::start_update_thread(); -} - -/// Tell the updater that we're launching from what it told us was the -/// next patch to boot from. This will copy the next boot patch to be the -/// `current_boot` patch. -/// -/// It is required to call this function before calling -/// `shorebird_report_launch_success` or `shorebird_report_launch_failure`. -#[no_mangle] -pub extern "C" fn shorebird_report_launch_start() { - log_on_error(updater::report_launch_start, "reporting launch start", ()); -} - -/// Report that the app failed to launch. This will cause the updater to -/// attempt to roll back to the previous version if this version has not -/// been launched successfully before. -#[no_mangle] -pub extern "C" fn shorebird_report_launch_failure() { - log_on_error( - updater::report_launch_failure, - "reporting launch failure", - (), - ); -} - -/// Report that the app launched successfully. This will mark the current -/// as having been launched successfully. We don't currently do anything -/// with this information, but it could be used to record a point at which -/// we will not roll back from. -/// -/// This is not currently wired up to be called from the Engine. It's unclear -/// where best to connect it. Expo waits 5 seconds after the app launches -/// and then marks the launch as successful. We could do something similar. -#[no_mangle] -pub extern "C" fn shorebird_report_launch_success() { - log_on_error( - updater::report_launch_success, - "reporting launch success", - (), - ); +/// Helper function to log errors instead of panicking or returning a result. +pub(super) fn log_on_error(f: F, context: &str, error_result: R) -> R +where + F: FnOnce() -> Result, +{ + f().unwrap_or_else(|e| { + shorebird_error!("Error {}: {:?}", context, e); + error_result + }) } #[cfg(test)] @@ -407,6 +86,7 @@ mod test { UNEXPECTED_REPORT, }, test_utils::write_fake_apk, + updater, }; use anyhow::Ok; use serial_test::serial; @@ -451,13 +131,13 @@ mod test { // libapp_path is currently Android-style with a virtual path // of at least 3 directories in depth ending in libapp.so. - fn parameters(tmp_dir: &TempDir, libapp_path: &str) -> super::AppParameters { + fn parameters(tmp_dir: &TempDir, libapp_path: &str) -> AppParameters { let cache_dir = tmp_dir.path().to_str().unwrap().to_string(); let app_paths_vec = vec![libapp_path.to_owned()]; let app_paths_size = app_paths_vec.len() as i32; let app_paths = c_array(app_paths_vec); - super::AppParameters { + AppParameters { app_storage_dir: c_string(&cache_dir), code_cache_dir: c_string(&cache_dir), release_version: c_string("1.0.0"), @@ -466,7 +146,7 @@ mod test { } } - fn free_parameters(params: super::AppParameters) { + fn free_parameters(params: AppParameters) { free_c_string(params.app_storage_dir as *mut libc::c_char); free_c_string(params.code_cache_dir as *mut libc::c_char); free_c_string(params.release_version as *mut libc::c_char); @@ -476,6 +156,18 @@ mod test { ) } + /// Run `shorebird_update_with_result` with the given channel, assert the + /// status equals `expected`, then free the result. Replaces uses of the + /// retired `shorebird_update()` helper inside tests that don't otherwise + /// inspect the result. + fn run_update_expecting(channel: *const c_char, expected: i32) { + let result = shorebird_update_with_result(channel); + unsafe { + assert_eq!(result.read().status, expected); + shorebird_free_update_result(result as *mut UpdateResult); + } + } + /// A precomputed bidiff patch artifact along with the inputs that /// produced it. Generate one with: /// cargo run --bin string_patch -- "" "" @@ -572,6 +264,68 @@ mod test { )); } + /// Exercises the `to_rust_vector` failure path in + /// `app_config_from_c` (engine.rs). All scalar fields are valid, but + /// the libapp_paths array contains a null entry — `to_rust` rejects + /// null and the `?` propagates. Without this test the array-conversion + /// branch is never reached because `init_with_null_app_parameters` + /// fails earlier on the scalar fields. + #[serial] + #[test] + fn init_with_null_libapp_path_in_array() { + testing_reset_config(); + let tmp_dir = TempDir::new().unwrap(); + let cache_dir = tmp_dir.path().to_str().unwrap().to_string(); + + let null_path: *const libc::c_char = std::ptr::null(); + let paths_array = [null_path]; + + let c_params = AppParameters { + app_storage_dir: c_string(&cache_dir), + code_cache_dir: c_string(&cache_dir), + release_version: c_string("1.0.0"), + original_libapp_paths: paths_array.as_ptr(), + original_libapp_paths_size: 1, + }; + let c_yaml = c_string("app_id: foo"); + + assert!(!shorebird_init(&c_params, FileCallbacks::new(), c_yaml)); + + free_c_string(c_yaml); + free_c_string(c_params.app_storage_dir as *mut libc::c_char); + free_c_string(c_params.code_cache_dir as *mut libc::c_char); + free_c_string(c_params.release_version as *mut libc::c_char); + } + + /// Exercises the `Err` arm of the channel decode in + /// `shorebird_update_with_result` (dart.rs). The channel pointer is + /// non-null (so `to_rust_option` doesn't short-circuit to `Ok(None)`), + /// but contains invalid UTF-8, so `CStr::to_str()` returns an error + /// that propagates into `to_update_result(Err(_))`. + #[serial] + #[test] + fn update_with_result_with_invalid_utf8_channel() { + testing_reset_config(); + let tmp_dir = TempDir::new().unwrap(); + let fake_libapp_path = tmp_dir.path().join("lib/arch/libapp.so"); + let c_params = parameters(&tmp_dir, fake_libapp_path.to_str().unwrap()); + let c_yaml = c_string("app_id: foo"); + assert!(shorebird_init(&c_params, FileCallbacks::new(), c_yaml)); + free_c_string(c_yaml); + free_parameters(c_params); + + // 0xFF is an invalid UTF-8 start byte. CStr::from_ptr accepts it + // (C strings are null-terminated bytes, no encoding) but the + // to_str() conversion fails — which is the path we're covering. + let bad_bytes: [u8; 4] = [0xFF, 0xFE, 0xFD, 0]; + let result = shorebird_update_with_result(bad_bytes.as_ptr() as *const c_char); + + unsafe { + assert_eq!(result.read().status, SHOREBIRD_UPDATE_ERROR); + shorebird_free_update_result(result as *mut UpdateResult); + } + } + #[serial] #[test] fn init_with_bad_yaml() { @@ -683,7 +437,7 @@ mod test { assert!(shorebird_check_for_downloadable_update(std::ptr::null())); // Go ahead and do the update. - shorebird_update(); + run_update_expecting(std::ptr::null(), SHOREBIRD_UPDATE_INSTALLED); assert_eq!(shorebird_current_boot_patch_number(), 0); assert_eq!(shorebird_next_boot_patch_number(), 1); @@ -931,7 +685,7 @@ mod test { // There is an update available. assert!(shorebird_check_for_downloadable_update(std::ptr::null())); // Go ahead and do the update. - shorebird_update(); + run_update_expecting(std::ptr::null(), SHOREBIRD_UPDATE_INSTALLED); // Ensure we have not yet updated the current patch. assert_eq!(shorebird_current_boot_patch_number(), 0); @@ -992,7 +746,7 @@ mod test { ); assert!(shorebird_check_for_downloadable_update(std::ptr::null())); - shorebird_update(); + run_update_expecting(std::ptr::null(), SHOREBIRD_UPDATE_INSTALLED); shorebird_report_launch_start(); shorebird_report_launch_success(); @@ -1076,7 +830,7 @@ mod test { |_url, _event| Ok(()), ); assert!(shorebird_check_for_downloadable_update(std::ptr::null())); - shorebird_update(); + run_update_expecting(std::ptr::null(), SHOREBIRD_UPDATE_INSTALLED); shorebird_report_launch_start(); shorebird_report_launch_success(); assert_eq!(shorebird_current_boot_patch_number(), 1); @@ -1125,8 +879,9 @@ mod test { /// Patch-to-patch rollback: device on patch 2, server rolls back to /// patch 1 (sends rollback signal AND a downloadable replacement). /// `check_for_downloadable_update` returns true (replacement available), - /// and after `update()` installs patch 1, the running session sees - /// `current=2, next=1` — the signal Dart needs for `restartRequired`. + /// and after `update_with_result` installs patch 1, the running session + /// sees `current=2, next=1` — the signal Dart needs for + /// `restartRequired`. #[serial] #[test] fn rollback_patch_to_patch_reports_current_and_next_distinctly() { @@ -1163,7 +918,7 @@ mod test { |_url, _event| Ok(()), ); assert!(shorebird_check_for_downloadable_update(std::ptr::null())); - shorebird_update(); + run_update_expecting(std::ptr::null(), SHOREBIRD_UPDATE_INSTALLED); shorebird_report_launch_start(); shorebird_report_launch_success(); assert_eq!(shorebird_current_boot_patch_number(), 2); @@ -1187,7 +942,7 @@ mod test { |_url, _event| Ok(()), ); assert!(shorebird_check_for_downloadable_update(std::ptr::null())); - shorebird_update(); + run_update_expecting(std::ptr::null(), SHOREBIRD_UPDATE_INSTALLED); // Running process is still on patch 2; next boot will be patch 1. assert_eq!(shorebird_current_boot_patch_number(), 2); diff --git a/shorebird_code_push/CHANGELOG.md b/shorebird_code_push/CHANGELOG.md index 311c432..7edfca0 100644 --- a/shorebird_code_push/CHANGELOG.md +++ b/shorebird_code_push/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.0.7 + +- chore: internal cleanup; no public API changes. + # 2.0.6 - fix: `checkForUpdate` now reports `restartRequired` when the current patch diff --git a/shorebird_code_push/CONTRIBUTING.md b/shorebird_code_push/CONTRIBUTING.md index 01d34a1..c38d48c 100644 --- a/shorebird_code_push/CONTRIBUTING.md +++ b/shorebird_code_push/CONTRIBUTING.md @@ -11,11 +11,16 @@ Flutter engine) via FFI. For an Updater function to be visible to the Dart code, it must: -1. Be declared in c_api.rs as `pub extern "C"`. - 1. This will add the function to the `library/include/updater.h` header - file, which is generated by [cbindgen](https://github.com/mozilla/cbindgen) - when the Updater is built. +1. Be declared in `library/src/c_api/dart.rs` as `pub extern "C"` (the + Dart-stable surface). Functions only meant for the Flutter engine go in + `library/src/c_api/engine.rs` instead and will not appear in the Dart + bindings. + 1. The two buckets emit `library/include/updater_dart.h` and + `library/include/updater_engine.h` respectively, generated by + [cbindgen](https://github.com/mozilla/cbindgen) when the Updater is + built. 1. Be included in the generated ffi bindings. These can be regenerated using - `dart run ffigen`. + `dart run ffigen`. ffigen reads only `updater_dart.h`, so engine-only + symbols are not bound. 1. Android specific: be listed in - https://github.com/shorebirdtech/engine/blob/main/shell/platform/android/android_exports.lst + https://github.com/shorebirdtech/flutter/blob/shorebird/dev/engine/src/flutter/shell/platform/android/android_exports.lst diff --git a/shorebird_code_push/lib/src/generated/updater_bindings.g.dart b/shorebird_code_push/lib/src/generated/updater_bindings.g.dart index 63eeb34..efba27f 100644 --- a/shorebird_code_push/lib/src/generated/updater_bindings.g.dart +++ b/shorebird_code_push/lib/src/generated/updater_bindings.g.dart @@ -3,7 +3,7 @@ // AUTO GENERATED FILE, DO NOT EDIT. // // Generated by `package:ffigen`. -// ignore_for_file: type=lint +// ignore_for_file: type=lint, unused_import import 'dart:ffi' as ffi; class UpdaterBindings { @@ -157,18 +157,17 @@ class UpdaterBindings { late final _setrlimit = _setrlimitPtr.asFunction)>(); - int wait1( + int wait( ffi.Pointer arg0, ) { - return _wait1( + return _wait( arg0, ); } - late final _wait1Ptr = + late final _waitPtr = _lookup)>>('wait'); - late final _wait1 = - _wait1Ptr.asFunction)>(); + late final _wait = _waitPtr.asFunction)>(); int waitpid( int arg0, @@ -189,13 +188,13 @@ class UpdaterBindings { _waitpidPtr.asFunction, int)>(); int waitid( - int arg0, - int arg1, + idtype_t arg0, + Dart__uint32_t arg1, ffi.Pointer arg2, int arg3, ) { return _waitid( - arg0, + arg0.value, arg1, arg2, arg3, @@ -204,8 +203,8 @@ class UpdaterBindings { late final _waitidPtr = _lookup< ffi.NativeFunction< - ffi.Int Function( - ffi.Int32, id_t, ffi.Pointer, ffi.Int)>>('waitid'); + ffi.Int Function(ffi.UnsignedInt, id_t, ffi.Pointer, + ffi.Int)>>('waitid'); late final _waitid = _waitidPtr .asFunction, int)>(); @@ -250,10 +249,10 @@ class UpdaterBindings { int Function(int, ffi.Pointer, int, ffi.Pointer)>(); ffi.Pointer alloca( - int arg0, + int __size, ) { return _alloca( - arg0, + __size, ); } @@ -913,12 +912,12 @@ class UpdaterBindings { int mbstowcs( ffi.Pointer arg0, ffi.Pointer arg1, - int arg2, + int __n, ) { return _mbstowcs( arg0, arg1, - arg2, + __n, ); } @@ -932,12 +931,12 @@ class UpdaterBindings { int mbtowc( ffi.Pointer arg0, ffi.Pointer arg1, - int arg2, + int __n, ) { return _mbtowc( arg0, arg1, - arg2, + __n, ); } @@ -1149,12 +1148,12 @@ class UpdaterBindings { int wcstombs( ffi.Pointer arg0, ffi.Pointer arg1, - int arg2, + int __n, ) { return _wcstombs( arg0, arg1, - arg2, + __n, ); } @@ -1330,12 +1329,12 @@ class UpdaterBindings { ffi.Pointer initstate( int arg0, ffi.Pointer arg1, - int arg2, + int __size, ) { return _initstate( arg0, arg1, - arg2, + __size, ); } @@ -1668,11 +1667,11 @@ class UpdaterBindings { void arc4random_addrandom( ffi.Pointer arg0, - int arg1, + int __datlen, ) { return _arc4random_addrandom( arg0, - arg1, + __datlen, ); } @@ -1971,11 +1970,11 @@ class UpdaterBindings { int getloadavg( ffi.Pointer arg0, - int arg1, + int __nelem, ) { return _getloadavg( arg0, - arg1, + __nelem, ); } @@ -2351,43 +2350,11 @@ class UpdaterBindings { set suboptarg(ffi.Pointer value) => _suboptarg.value = value; - /// Configures updater. First parameter is a struct containing configuration - /// from the running app. Second parameter is a YAML string containing - /// configuration compiled into the app. Returns true on success and false on - /// failure. If false is returned, the updater library will not be usable. - bool shorebird_init( - ffi.Pointer c_params, - FileCallbacks c_file_callbacks, - ffi.Pointer c_yaml, - ) { - return _shorebird_init( - c_params, - c_file_callbacks, - c_yaml, - ); - } - - late final _shorebird_initPtr = _lookup< - ffi.NativeFunction< - ffi.Bool Function(ffi.Pointer, FileCallbacks, - ffi.Pointer)>>('shorebird_init'); - late final _shorebird_init = _shorebird_initPtr.asFunction< - bool Function( - ffi.Pointer, FileCallbacks, ffi.Pointer)>(); - - /// Returns if the app should run the updater automatically on launch. - bool shorebird_should_auto_update() { - return _shorebird_should_auto_update(); - } - - late final _shorebird_should_auto_updatePtr = - _lookup>( - 'shorebird_should_auto_update'); - late final _shorebird_should_auto_update = - _shorebird_should_auto_updatePtr.asFunction(); - /// The currently running patch number, or 0 if the release has not been - /// patched. + /// patched. The internal name for this concept is `running_patch`; the + /// FFI symbol keeps the historical `current_boot_patch_number` spelling + /// because the Flutter Engine and existing pub releases of + /// `shorebird_code_push` link against it. int shorebird_current_boot_patch_number() { return _shorebird_current_boot_patch_number(); } @@ -2410,52 +2377,6 @@ class UpdaterBindings { late final _shorebird_next_boot_patch_number = _shorebird_next_boot_patch_numberPtr.asFunction(); - /// The path to the patch that will boot on the next run of the app, or NULL if - /// there is no next patch. - ffi.Pointer shorebird_next_boot_patch_path() { - return _shorebird_next_boot_patch_path(); - } - - late final _shorebird_next_boot_patch_pathPtr = - _lookup Function()>>( - 'shorebird_next_boot_patch_path'); - late final _shorebird_next_boot_patch_path = - _shorebird_next_boot_patch_pathPtr - .asFunction Function()>(); - - /// Free a string returned by the updater library. - /// # Safety - /// - /// If this function is called with a non-null pointer, it must be a pointer - /// returned by the updater library. - void shorebird_free_string( - ffi.Pointer c_string, - ) { - return _shorebird_free_string( - c_string, - ); - } - - late final _shorebird_free_stringPtr = - _lookup)>>( - 'shorebird_free_string'); - late final _shorebird_free_string = _shorebird_free_stringPtr - .asFunction)>(); - - void shorebird_free_update_result( - ffi.Pointer result, - ) { - return _shorebird_free_update_result( - result, - ); - } - - late final _shorebird_free_update_resultPtr = - _lookup)>>( - 'shorebird_free_update_result'); - late final _shorebird_free_update_result = _shorebird_free_update_resultPtr - .asFunction)>(); - /// Check for an update on the first non-null channel of: /// 1. `c_channel` /// 2. The channel specified in shorebird.yaml @@ -2477,16 +2398,6 @@ class UpdaterBindings { _shorebird_check_for_downloadable_updatePtr .asFunction)>(); - /// Synchronously download an update if one is available. - void shorebird_update() { - return _shorebird_update(); - } - - late final _shorebird_updatePtr = - _lookup>('shorebird_update'); - late final _shorebird_update = - _shorebird_updatePtr.asFunction(); - /// Synchronously download an update on the first non-null channel of: /// 1. `c_channel` /// 2. The channel specified in shorebird.yaml @@ -2508,65 +2419,87 @@ class UpdaterBindings { late final _shorebird_update_with_result = _shorebird_update_with_resultPtr .asFunction Function(ffi.Pointer)>(); - /// Start a thread to download an update if one is available. - void shorebird_start_update_thread() { - return _shorebird_start_update_thread(); - } - - late final _shorebird_start_update_threadPtr = - _lookup>( - 'shorebird_start_update_thread'); - late final _shorebird_start_update_thread = - _shorebird_start_update_threadPtr.asFunction(); - - /// Tell the updater that we're launching from what it told us was the - /// next patch to boot from. This will copy the next boot patch to be the - /// `current_boot` patch. + /// Frees an `UpdateResult` previously returned by + /// `shorebird_update_with_result`. Frees the embedded `message` string and + /// the result allocation itself. /// - /// It is required to call this function before calling - /// `shorebird_report_launch_success` or `shorebird_report_launch_failure`. - void shorebird_report_launch_start() { - return _shorebird_report_launch_start(); - } - - late final _shorebird_report_launch_startPtr = - _lookup>( - 'shorebird_report_launch_start'); - late final _shorebird_report_launch_start = - _shorebird_report_launch_startPtr.asFunction(); - - /// Report that the app failed to launch. This will cause the updater to - /// attempt to roll back to the previous version if this version has not - /// been launched successfully before. - void shorebird_report_launch_failure() { - return _shorebird_report_launch_failure(); - } - - late final _shorebird_report_launch_failurePtr = - _lookup>( - 'shorebird_report_launch_failure'); - late final _shorebird_report_launch_failure = - _shorebird_report_launch_failurePtr.asFunction(); - - /// Report that the app launched successfully. This will mark the current - /// as having been launched successfully. We don't currently do anything - /// with this information, but it could be used to record a point at which - /// we will not roll back from. + /// # Safety /// - /// This is not currently wired up to be called from the Engine. It's unclear - /// where best to connect it. Expo waits 5 seconds after the app launches - /// and then marks the launch as successful. We could do something similar. - void shorebird_report_launch_success() { - return _shorebird_report_launch_success(); + /// `result` must be a valid pointer returned by `shorebird_update_with_result`, + /// or null (in which case this is a no-op). + void shorebird_free_update_result( + ffi.Pointer result, + ) { + return _shorebird_free_update_result( + result, + ); } - late final _shorebird_report_launch_successPtr = - _lookup>( - 'shorebird_report_launch_success'); - late final _shorebird_report_launch_success = - _shorebird_report_launch_successPtr.asFunction(); + late final _shorebird_free_update_resultPtr = + _lookup)>>( + 'shorebird_free_update_result'); + late final _shorebird_free_update_result = _shorebird_free_update_resultPtr + .asFunction)>(); } +typedef __builtin_va_list = ffi.Pointer; +typedef __gnuc_va_list = __builtin_va_list; +typedef va_list = __builtin_va_list; +typedef int_least8_t = ffi.Int8; +typedef Dartint_least8_t = int; +typedef int_least16_t = ffi.Int16; +typedef Dartint_least16_t = int; +typedef int_least32_t = ffi.Int32; +typedef Dartint_least32_t = int; +typedef int_least64_t = ffi.Int64; +typedef Dartint_least64_t = int; +typedef uint_least8_t = ffi.Uint8; +typedef Dartuint_least8_t = int; +typedef uint_least16_t = ffi.Uint16; +typedef Dartuint_least16_t = int; +typedef uint_least32_t = ffi.Uint32; +typedef Dartuint_least32_t = int; +typedef uint_least64_t = ffi.Uint64; +typedef Dartuint_least64_t = int; +typedef int_fast8_t = ffi.Int8; +typedef Dartint_fast8_t = int; +typedef int_fast16_t = ffi.Int16; +typedef Dartint_fast16_t = int; +typedef int_fast32_t = ffi.Int32; +typedef Dartint_fast32_t = int; +typedef int_fast64_t = ffi.Int64; +typedef Dartint_fast64_t = int; +typedef uint_fast8_t = ffi.Uint8; +typedef Dartuint_fast8_t = int; +typedef uint_fast16_t = ffi.Uint16; +typedef Dartuint_fast16_t = int; +typedef uint_fast32_t = ffi.Uint32; +typedef Dartuint_fast32_t = int; +typedef uint_fast64_t = ffi.Uint64; +typedef Dartuint_fast64_t = int; +typedef __int8_t = ffi.SignedChar; +typedef Dart__int8_t = int; +typedef __uint8_t = ffi.UnsignedChar; +typedef Dart__uint8_t = int; +typedef __int16_t = ffi.Short; +typedef Dart__int16_t = int; +typedef __uint16_t = ffi.UnsignedShort; +typedef Dart__uint16_t = int; +typedef __int32_t = ffi.Int; +typedef Dart__int32_t = int; +typedef __uint32_t = ffi.UnsignedInt; +typedef Dart__uint32_t = int; +typedef __int64_t = ffi.LongLong; +typedef Dart__int64_t = int; +typedef __uint64_t = ffi.UnsignedLongLong; +typedef Dart__uint64_t = int; +typedef __darwin_intptr_t = ffi.Long; +typedef Dart__darwin_intptr_t = int; +typedef __darwin_natural_t = ffi.UnsignedInt; +typedef Dart__darwin_natural_t = int; +typedef __darwin_ct_rune_t = ffi.Int; +typedef Dart__darwin_ct_rune_t = int; + final class __mbstate_t extends ffi.Union { @ffi.Array.multi([128]) external ffi.Array __mbstate8; @@ -2575,6 +2508,45 @@ final class __mbstate_t extends ffi.Union { external int _mbstateL; } +typedef __darwin_mbstate_t = __mbstate_t; +typedef __darwin_ptrdiff_t = ffi.Long; +typedef Dart__darwin_ptrdiff_t = int; +typedef __darwin_size_t = ffi.UnsignedLong; +typedef Dart__darwin_size_t = int; +typedef __darwin_va_list = __builtin_va_list; +typedef __darwin_wchar_t = ffi.Int; +typedef Dart__darwin_wchar_t = int; +typedef __darwin_rune_t = __darwin_wchar_t; +typedef __darwin_wint_t = ffi.Int; +typedef Dart__darwin_wint_t = int; +typedef __darwin_clock_t = ffi.UnsignedLong; +typedef Dart__darwin_clock_t = int; +typedef __darwin_socklen_t = __uint32_t; +typedef __darwin_ssize_t = ffi.Long; +typedef Dart__darwin_ssize_t = int; +typedef __darwin_time_t = ffi.Long; +typedef Dart__darwin_time_t = int; +typedef __darwin_blkcnt_t = __int64_t; +typedef __darwin_blksize_t = __int32_t; +typedef __darwin_dev_t = __int32_t; +typedef __darwin_fsblkcnt_t = ffi.UnsignedInt; +typedef Dart__darwin_fsblkcnt_t = int; +typedef __darwin_fsfilcnt_t = ffi.UnsignedInt; +typedef Dart__darwin_fsfilcnt_t = int; +typedef __darwin_gid_t = __uint32_t; +typedef __darwin_id_t = __uint32_t; +typedef __darwin_ino64_t = __uint64_t; +typedef __darwin_ino_t = __darwin_ino64_t; +typedef __darwin_mach_port_name_t = __darwin_natural_t; +typedef __darwin_mach_port_t = __darwin_mach_port_name_t; +typedef __darwin_mode_t = __uint16_t; +typedef __darwin_off_t = __int64_t; +typedef __darwin_pid_t = __int32_t; +typedef __darwin_sigset_t = __uint32_t; +typedef __darwin_suseconds_t = __int32_t; +typedef __darwin_uid_t = __uint32_t; +typedef __darwin_useconds_t = __uint32_t; + final class __darwin_pthread_handler_rec extends ffi.Struct { external ffi .Pointer)>> @@ -2659,12 +2631,70 @@ final class _opaque_pthread_t extends ffi.Struct { external ffi.Array __opaque; } -abstract class idtype_t { - static const int P_ALL = 0; - static const int P_PID = 1; - static const int P_PGID = 2; +typedef __darwin_pthread_attr_t = _opaque_pthread_attr_t; +typedef __darwin_pthread_cond_t = _opaque_pthread_cond_t; +typedef __darwin_pthread_condattr_t = _opaque_pthread_condattr_t; +typedef __darwin_pthread_key_t = ffi.UnsignedLong; +typedef Dart__darwin_pthread_key_t = int; +typedef __darwin_pthread_mutex_t = _opaque_pthread_mutex_t; +typedef __darwin_pthread_mutexattr_t = _opaque_pthread_mutexattr_t; +typedef __darwin_pthread_once_t = _opaque_pthread_once_t; +typedef __darwin_pthread_rwlock_t = _opaque_pthread_rwlock_t; +typedef __darwin_pthread_rwlockattr_t = _opaque_pthread_rwlockattr_t; +typedef __darwin_pthread_t = ffi.Pointer<_opaque_pthread_t>; +typedef intmax_t = ffi.Long; +typedef Dartintmax_t = int; +typedef uintmax_t = ffi.UnsignedLong; +typedef Dartuintmax_t = int; +typedef __darwin_nl_item = ffi.Int; +typedef Dart__darwin_nl_item = int; +typedef __darwin_wctrans_t = ffi.Int; +typedef Dart__darwin_wctrans_t = int; +typedef __darwin_wctype_t = __uint32_t; + +enum idtype_t { + P_ALL(0), + P_PID(1), + P_PGID(2); + + final int value; + const idtype_t(this.value); + + static idtype_t fromValue(int value) => switch (value) { + 0 => P_ALL, + 1 => P_PID, + 2 => P_PGID, + _ => throw ArgumentError('Unknown value for idtype_t: $value'), + }; } +typedef pid_t = __darwin_pid_t; +typedef id_t = __darwin_id_t; +typedef sig_atomic_t = ffi.Int; +typedef Dartsig_atomic_t = int; +typedef u_int8_t = ffi.UnsignedChar; +typedef Dartu_int8_t = int; +typedef u_int16_t = ffi.UnsignedShort; +typedef Dartu_int16_t = int; +typedef u_int32_t = ffi.UnsignedInt; +typedef Dartu_int32_t = int; +typedef u_int64_t = ffi.UnsignedLongLong; +typedef Dartu_int64_t = int; +typedef register_t = ffi.Int64; +typedef Dartregister_t = int; +typedef user_addr_t = u_int64_t; +typedef user_size_t = u_int64_t; +typedef user_ssize_t = ffi.Int64; +typedef Dartuser_ssize_t = int; +typedef user_long_t = ffi.Int64; +typedef Dartuser_long_t = int; +typedef user_ulong_t = u_int64_t; +typedef user_time_t = ffi.Int64; +typedef Dartuser_time_t = int; +typedef user_off_t = ffi.Int64; +typedef Dartuser_off_t = int; +typedef syscall_arg_t = u_int64_t; + final class __darwin_arm_exception_state extends ffi.Struct { @__uint32_t() external int __exception; @@ -2676,9 +2706,6 @@ final class __darwin_arm_exception_state extends ffi.Struct { external int __far; } -typedef __uint32_t = ffi.UnsignedInt; -typedef Dart__uint32_t = int; - final class __darwin_arm_exception_state64 extends ffi.Struct { @__uint64_t() external int __far; @@ -2690,9 +2717,6 @@ final class __darwin_arm_exception_state64 extends ffi.Struct { external int __exception; } -typedef __uint64_t = ffi.UnsignedLongLong; -typedef Dart__uint64_t = int; - final class __darwin_arm_exception_state64_v2 extends ffi.Struct { @__uint64_t() external int __far; @@ -2758,6 +2782,37 @@ final class __arm_pagein_state extends ffi.Struct { external int __pagein_error; } +final class __darwin_arm_sme_state extends ffi.Struct { + @__uint64_t() + external int __svcr; + + @__uint64_t() + external int __tpidr2_el0; + + @__uint16_t() + external int __svl_b; +} + +final class __darwin_arm_sve_z_state extends ffi.Struct { + @ffi.Array.multi([16, 256]) + external ffi.Array> __z; +} + +final class __darwin_arm_sve_p_state extends ffi.Struct { + @ffi.Array.multi([16, 32]) + external ffi.Array> __p; +} + +final class __darwin_arm_sme_za_state extends ffi.Struct { + @ffi.Array.multi([4096]) + external ffi.Array __za; +} + +final class __darwin_arm_sme2_state extends ffi.Struct { + @ffi.Array.multi([64]) + external ffi.Array __zt0; +} + final class __arm_legacy_debug_state extends ffi.Struct { @ffi.Array.multi([16]) external ffi.Array<__uint32_t> __bvr; @@ -2821,6 +2876,9 @@ final class __darwin_mcontext32 extends ffi.Struct { final class __darwin_mcontext64 extends ffi.Opaque {} +typedef mcontext_t = ffi.Pointer<__darwin_mcontext64>; +typedef pthread_attr_t = __darwin_pthread_attr_t; + final class __darwin_sigaltstack extends ffi.Struct { external ffi.Pointer ss_sp; @@ -2831,8 +2889,7 @@ final class __darwin_sigaltstack extends ffi.Struct { external int ss_flags; } -typedef __darwin_size_t = ffi.UnsignedLong; -typedef Dart__darwin_size_t = int; +typedef stack_t = __darwin_sigaltstack; final class __darwin_ucontext extends ffi.Struct { @ffi.Int() @@ -2851,7 +2908,9 @@ final class __darwin_ucontext extends ffi.Struct { external ffi.Pointer<__darwin_mcontext64> uc_mcontext; } -typedef __darwin_sigset_t = __uint32_t; +typedef ucontext_t = __darwin_ucontext; +typedef sigset_t = __darwin_sigset_t; +typedef uid_t = __darwin_uid_t; final class sigval extends ffi.Union { @ffi.Int() @@ -2875,9 +2934,6 @@ final class sigevent extends ffi.Struct { external ffi.Pointer sigev_notify_attributes; } -typedef pthread_attr_t = __darwin_pthread_attr_t; -typedef __darwin_pthread_attr_t = _opaque_pthread_attr_t; - final class __siginfo extends ffi.Struct { @ffi.Int() external int si_signo; @@ -2908,12 +2964,7 @@ final class __siginfo extends ffi.Struct { external ffi.Array __pad; } -typedef pid_t = __darwin_pid_t; -typedef __darwin_pid_t = __int32_t; -typedef __int32_t = ffi.Int; -typedef Dart__int32_t = int; -typedef uid_t = __darwin_uid_t; -typedef __darwin_uid_t = __uint32_t; +typedef siginfo_t = __siginfo; final class __sigaction_u extends ffi.Union { external ffi.Pointer> @@ -2927,7 +2978,7 @@ final class __sigaction_u extends ffi.Union { } final class __sigaction extends ffi.Struct { - external __sigaction_u __sigaction_u1; + external __sigaction_u __sigaction_u$1; external ffi.Pointer< ffi.NativeFunction< @@ -2941,11 +2992,8 @@ final class __sigaction extends ffi.Struct { external int sa_flags; } -typedef siginfo_t = __siginfo; -typedef sigset_t = __darwin_sigset_t; - final class sigaction extends ffi.Struct { - external __sigaction_u __sigaction_u1; + external __sigaction_u __sigaction_u$1; @sigset_t() external int sa_mask; @@ -2954,6 +3002,10 @@ final class sigaction extends ffi.Struct { external int sa_flags; } +typedef sig_tFunction = ffi.Void Function(ffi.Int); +typedef Dartsig_tFunction = void Function(int); +typedef sig_t = ffi.Pointer>; + final class sigvec extends ffi.Struct { external ffi.Pointer> sv_handler; @@ -2980,9 +3032,7 @@ final class timeval extends ffi.Struct { external int tv_usec; } -typedef __darwin_time_t = ffi.Long; -typedef Dart__darwin_time_t = int; -typedef __darwin_suseconds_t = __int32_t; +typedef rlim_t = __uint64_t; final class rusage extends ffi.Struct { external timeval ru_utime; @@ -3032,6 +3082,8 @@ final class rusage extends ffi.Struct { external int ru_nivcsw; } +typedef rusage_info_t = ffi.Pointer; + final class rusage_info_v0 extends ffi.Struct { @ffi.Array.multi([16]) external ffi.Array ri_uuid; @@ -3637,6 +3689,8 @@ final class rusage_info_v6 extends ffi.Struct { external ffi.Array ri_reserved; } +typedef rusage_info_current = rusage_info_v6; + final class rlimit extends ffi.Struct { @rlim_t() external int rlim_cur; @@ -3645,8 +3699,6 @@ final class rlimit extends ffi.Struct { external int rlim_max; } -typedef rlim_t = __uint64_t; - final class proc_rlimit_control_wakeupmon extends ffi.Struct { @ffi.Uint32() external int wm_flags; @@ -3655,10 +3707,10 @@ final class proc_rlimit_control_wakeupmon extends ffi.Struct { external int wm_rate; } -typedef id_t = __darwin_id_t; -typedef __darwin_id_t = __uint32_t; +final class wait$1 extends ffi.Opaque {} -final class wait extends ffi.Opaque {} +typedef ct_rune_t = __darwin_ct_rune_t; +typedef rune_t = __darwin_rune_t; final class div_t extends ffi.Struct { @ffi.Int() @@ -3691,64 +3743,7 @@ final class _malloc_zone_t extends ffi.Opaque {} typedef malloc_zone_t = _malloc_zone_t; typedef dev_t = __darwin_dev_t; -typedef __darwin_dev_t = __int32_t; typedef mode_t = __darwin_mode_t; -typedef __darwin_mode_t = __uint16_t; -typedef __uint16_t = ffi.UnsignedShort; -typedef Dart__uint16_t = int; - -/// Struct containing configuration parameters for the updater. -/// Passed to all updater functions. -/// NOTE: If this struct is changed all language bindings must be updated. -final class AppParameters extends ffi.Struct { - /// release_version, required. Named version of the app, off of which - /// updates are based. Can be either a version number or a hash. - external ffi.Pointer release_version; - - /// Array of paths to the original aot library, required. For Flutter apps - /// these are the paths to the bundled libapp.so. May be used for - /// compression downloaded artifacts. - external ffi.Pointer> original_libapp_paths; - - /// Length of the original_libapp_paths array. - @ffi.Int() - external int original_libapp_paths_size; - - /// Path to app storage directory where the updater will store serialized - /// state and other data that persists between releases. - external ffi.Pointer app_storage_dir; - - /// Path to cache directory where the updater will store downloaded - /// artifacts and data that can be deleted when a new release is detected. - external ffi.Pointer code_cache_dir; -} - -final class FileCallbacks extends ffi.Struct { - /// Opens the "file" (actually an in-memory buffer) and returns a handle. - external ffi.Pointer Function()>> - open; - - /// Reads count bytes from the file into buffer. Returns the number of - /// bytes read. - external ffi.Pointer< - ffi.NativeFunction< - ffi.UintPtr Function(ffi.Pointer file_handle, - ffi.Pointer buffer, ffi.UintPtr count)>> read; - - /// Moves the file pointer to the given offset relative from whence (one of - /// libc::SEEK_SET, libc::SEEK_CUR, or libc::SEEK_END). Returns the new - /// offset relative to the start of the file. - external ffi.Pointer< - ffi.NativeFunction< - ffi.Int64 Function(ffi.Pointer file_handle, - ffi.Int64 offset, ffi.Int32 whence)>> seek; - - /// Closes and frees the file handle. - external ffi.Pointer< - ffi - .NativeFunction file_handle)>> - close; -} final class UpdateResult extends ffi.Struct { @ffi.Int32() @@ -3759,9 +3754,9 @@ final class UpdateResult extends ffi.Struct { const int __bool_true_false_are_defined = 1; -const int true1 = 1; +const int true$ = 1; -const int false1 = 0; +const int false$ = 0; const int __WORDSIZE = 64; @@ -3805,6 +3800,8 @@ const int _DARWIN_FEATURE_UNIX_CONFORMANCE = 3; const int __has_ptrcheck = 0; +const int __has_bounds_safety_attributes = 0; + const int __DARWIN_NULL = 0; const int __PTHREAD_SIZE__ = 8176; @@ -3933,18 +3930,32 @@ const int __API_TO_BE_DEPRECATED = 100000; const int __API_TO_BE_DEPRECATED_MACOS = 100000; +const int __API_TO_BE_DEPRECATED_MACOSAPPLICATIONEXTENSION = 100000; + const int __API_TO_BE_DEPRECATED_IOS = 100000; +const int __API_TO_BE_DEPRECATED_IOSAPPLICATIONEXTENSION = 100000; + const int __API_TO_BE_DEPRECATED_MACCATALYST = 100000; +const int __API_TO_BE_DEPRECATED_MACCATALYSTAPPLICATIONEXTENSION = 100000; + const int __API_TO_BE_DEPRECATED_WATCHOS = 100000; +const int __API_TO_BE_DEPRECATED_WATCHOSAPPLICATIONEXTENSION = 100000; + const int __API_TO_BE_DEPRECATED_TVOS = 100000; +const int __API_TO_BE_DEPRECATED_TVOSAPPLICATIONEXTENSION = 100000; + const int __API_TO_BE_DEPRECATED_DRIVERKIT = 100000; const int __API_TO_BE_DEPRECATED_VISIONOS = 100000; +const int __API_TO_BE_DEPRECATED_VISIONOSAPPLICATIONEXTENSION = 100000; + +const int __API_TO_BE_DEPRECATED_KERNELKIT = 100000; + const int __MAC_10_0 = 1000; const int __MAC_10_1 = 1010; @@ -4055,6 +4066,8 @@ const int __MAC_13_5 = 130500; const int __MAC_13_6 = 130600; +const int __MAC_13_7 = 130700; + const int __MAC_14_0 = 140000; const int __MAC_14_1 = 140100; @@ -4067,10 +4080,36 @@ const int __MAC_14_4 = 140400; const int __MAC_14_5 = 140500; +const int __MAC_14_6 = 140600; + +const int __MAC_14_7 = 140700; + const int __MAC_15_0 = 150000; const int __MAC_15_1 = 150100; +const int __MAC_15_2 = 150200; + +const int __MAC_15_3 = 150300; + +const int __MAC_15_4 = 150400; + +const int __MAC_15_5 = 150500; + +const int __MAC_15_6 = 150600; + +const int __MAC_16_0 = 160000; + +const int __MAC_26_0 = 260000; + +const int __MAC_26_1 = 260100; + +const int __MAC_26_2 = 260200; + +const int __MAC_26_3 = 260300; + +const int __MAC_26_4 = 260400; + const int __IPHONE_2_0 = 20000; const int __IPHONE_2_1 = 20100; @@ -4175,8 +4214,6 @@ const int __IPHONE_14_3 = 140300; const int __IPHONE_14_5 = 140500; -const int __IPHONE_14_4 = 140400; - const int __IPHONE_14_6 = 140600; const int __IPHONE_14_7 = 140700; @@ -4229,10 +4266,36 @@ const int __IPHONE_17_4 = 170400; const int __IPHONE_17_5 = 170500; +const int __IPHONE_17_6 = 170600; + +const int __IPHONE_17_7 = 170700; + const int __IPHONE_18_0 = 180000; const int __IPHONE_18_1 = 180100; +const int __IPHONE_18_2 = 180200; + +const int __IPHONE_18_3 = 180300; + +const int __IPHONE_18_4 = 180400; + +const int __IPHONE_18_5 = 180500; + +const int __IPHONE_18_6 = 180600; + +const int __IPHONE_19_0 = 190000; + +const int __IPHONE_26_0 = 260000; + +const int __IPHONE_26_1 = 260100; + +const int __IPHONE_26_2 = 260200; + +const int __IPHONE_26_3 = 260300; + +const int __IPHONE_26_4 = 260400; + const int __WATCHOS_1_0 = 10000; const int __WATCHOS_2_0 = 20000; @@ -4327,10 +4390,36 @@ const int __WATCHOS_10_4 = 100400; const int __WATCHOS_10_5 = 100500; +const int __WATCHOS_10_6 = 100600; + +const int __WATCHOS_10_7 = 100700; + const int __WATCHOS_11_0 = 110000; const int __WATCHOS_11_1 = 110100; +const int __WATCHOS_11_2 = 110200; + +const int __WATCHOS_11_3 = 110300; + +const int __WATCHOS_11_4 = 110400; + +const int __WATCHOS_11_5 = 110500; + +const int __WATCHOS_11_6 = 110600; + +const int __WATCHOS_12_0 = 120000; + +const int __WATCHOS_26_0 = 260000; + +const int __WATCHOS_26_1 = 260100; + +const int __WATCHOS_26_2 = 260200; + +const int __WATCHOS_26_3 = 260300; + +const int __WATCHOS_26_4 = 260400; + const int __TVOS_9_0 = 90000; const int __TVOS_9_1 = 90100; @@ -4427,10 +4516,34 @@ const int __TVOS_17_4 = 170400; const int __TVOS_17_5 = 170500; +const int __TVOS_17_6 = 170600; + const int __TVOS_18_0 = 180000; const int __TVOS_18_1 = 180100; +const int __TVOS_18_2 = 180200; + +const int __TVOS_18_3 = 180300; + +const int __TVOS_18_4 = 180400; + +const int __TVOS_18_5 = 180500; + +const int __TVOS_18_6 = 180600; + +const int __TVOS_19_0 = 190000; + +const int __TVOS_26_0 = 260000; + +const int __TVOS_26_1 = 260100; + +const int __TVOS_26_2 = 260200; + +const int __TVOS_26_3 = 260300; + +const int __TVOS_26_4 = 260400; + const int __BRIDGEOS_2_0 = 20000; const int __BRIDGEOS_3_0 = 30000; @@ -4483,10 +4596,32 @@ const int __BRIDGEOS_8_4 = 80400; const int __BRIDGEOS_8_5 = 80500; +const int __BRIDGEOS_8_6 = 80600; + const int __BRIDGEOS_9_0 = 90000; const int __BRIDGEOS_9_1 = 90100; +const int __BRIDGEOS_9_2 = 90200; + +const int __BRIDGEOS_9_3 = 90300; + +const int __BRIDGEOS_9_4 = 90400; + +const int __BRIDGEOS_9_5 = 90500; + +const int __BRIDGEOS_9_6 = 90600; + +const int __BRIDGEOS_10_0 = 100000; + +const int __BRIDGEOS_10_1 = 100100; + +const int __BRIDGEOS_10_2 = 100200; + +const int __BRIDGEOS_10_3 = 100300; + +const int __BRIDGEOS_10_4 = 100400; + const int __DRIVERKIT_19_0 = 190000; const int __DRIVERKIT_20_0 = 200000; @@ -4513,20 +4648,66 @@ const int __DRIVERKIT_23_4 = 230400; const int __DRIVERKIT_23_5 = 230500; +const int __DRIVERKIT_23_6 = 230600; + const int __DRIVERKIT_24_0 = 240000; const int __DRIVERKIT_24_1 = 240100; +const int __DRIVERKIT_24_2 = 240200; + +const int __DRIVERKIT_24_3 = 240300; + +const int __DRIVERKIT_24_4 = 240400; + +const int __DRIVERKIT_24_5 = 240500; + +const int __DRIVERKIT_24_6 = 240600; + +const int __DRIVERKIT_25_0 = 250000; + +const int __DRIVERKIT_25_1 = 250100; + +const int __DRIVERKIT_25_2 = 250200; + +const int __DRIVERKIT_25_3 = 250300; + +const int __DRIVERKIT_25_4 = 250400; + const int __VISIONOS_1_0 = 10000; const int __VISIONOS_1_1 = 10100; const int __VISIONOS_1_2 = 10200; +const int __VISIONOS_1_3 = 10300; + const int __VISIONOS_2_0 = 20000; const int __VISIONOS_2_1 = 20100; +const int __VISIONOS_2_2 = 20200; + +const int __VISIONOS_2_3 = 20300; + +const int __VISIONOS_2_4 = 20400; + +const int __VISIONOS_2_5 = 20500; + +const int __VISIONOS_2_6 = 20600; + +const int __VISIONOS_3_0 = 30000; + +const int __VISIONOS_26_0 = 260000; + +const int __VISIONOS_26_1 = 260100; + +const int __VISIONOS_26_2 = 260200; + +const int __VISIONOS_26_3 = 260300; + +const int __VISIONOS_26_4 = 260400; + const int MAC_OS_X_VERSION_10_0 = 1000; const int MAC_OS_X_VERSION_10_1 = 1010; @@ -4637,6 +4818,8 @@ const int MAC_OS_VERSION_13_5 = 130500; const int MAC_OS_VERSION_13_6 = 130600; +const int MAC_OS_VERSION_13_7 = 130700; + const int MAC_OS_VERSION_14_0 = 140000; const int MAC_OS_VERSION_14_1 = 140100; @@ -4649,13 +4832,45 @@ const int MAC_OS_VERSION_14_4 = 140400; const int MAC_OS_VERSION_14_5 = 140500; +const int MAC_OS_VERSION_14_6 = 140600; + +const int MAC_OS_VERSION_14_7 = 140700; + const int MAC_OS_VERSION_15_0 = 150000; const int MAC_OS_VERSION_15_1 = 150100; -const int __MAC_OS_X_VERSION_MIN_REQUIRED = 150000; +const int MAC_OS_VERSION_15_2 = 150200; -const int __MAC_OS_X_VERSION_MAX_ALLOWED = 150100; +const int MAC_OS_VERSION_15_3 = 150300; + +const int MAC_OS_VERSION_15_4 = 150400; + +const int MAC_OS_VERSION_15_5 = 150500; + +const int MAC_OS_VERSION_15_6 = 150600; + +const int MAC_OS_VERSION_16_0 = 160000; + +const int MAC_OS_VERSION_26_0 = 260000; + +const int MAC_OS_VERSION_26_1 = 260100; + +const int MAC_OS_VERSION_26_2 = 260200; + +const int MAC_OS_VERSION_26_3 = 260300; + +const int MAC_OS_VERSION_26_4 = 260400; + +const int __AVAILABILITY_VERSIONS_VERSION_HASH = 93585900; + +const String __AVAILABILITY_VERSIONS_VERSION_STRING = 'Local'; + +const String __AVAILABILITY_FILE = 'AvailabilityVersions.h'; + +const int __MAC_OS_X_VERSION_MIN_REQUIRED = 260000; + +const int __MAC_OS_X_VERSION_MAX_ALLOWED = 260400; const int __ENABLE_LEGACY_MAC_AVAILABILITY = 1; @@ -4747,6 +4962,8 @@ const int SIGEV_SIGNAL = 1; const int SIGEV_THREAD = 3; +const int SIGEV_KEVENT = 4; + const int ILL_NOOP = 0; const int ILL_ILLOPC = 1; @@ -4995,6 +5212,8 @@ const int IOPOL_TYPE_VFS_ALLOW_LOW_SPACE_WRITES = 9; const int IOPOL_TYPE_VFS_DISALLOW_RW_FOR_O_EVTONLY = 10; +const int IOPOL_TYPE_VFS_ENTITLED_RESERVE_ACCESS = 14; + const int IOPOL_SCOPE_PROCESS = 0; const int IOPOL_SCOPE_THREAD = 1; @@ -5027,6 +5246,10 @@ const int IOPOL_MATERIALIZE_DATALESS_FILES_OFF = 1; const int IOPOL_MATERIALIZE_DATALESS_FILES_ON = 2; +const int IOPOL_MATERIALIZE_DATALESS_FILES_ORIG = 4; + +const int IOPOL_MATERIALIZE_DATALESS_FILES_BASIC_MASK = 3; + const int IOPOL_VFS_STATFS_NO_DATA_VOLUME_DEFAULT = 0; const int IOPOL_VFS_STATFS_FORCE_NO_DATA_VOLUME = 1; @@ -5047,6 +5270,8 @@ const int IOPOL_VFS_SKIP_MTIME_UPDATE_OFF = 0; const int IOPOL_VFS_SKIP_MTIME_UPDATE_ON = 1; +const int IOPOL_VFS_SKIP_MTIME_UPDATE_IGNORE = 2; + const int IOPOL_VFS_ALLOW_LOW_SPACE_WRITES_OFF = 0; const int IOPOL_VFS_ALLOW_LOW_SPACE_WRITES_ON = 1; @@ -5059,6 +5284,10 @@ const int IOPOL_VFS_NOCACHE_WRITE_FS_BLKSIZE_DEFAULT = 0; const int IOPOL_VFS_NOCACHE_WRITE_FS_BLKSIZE_ON = 1; +const int IOPOL_VFS_ENTITLED_RESERVE_ACCESS_OFF = 0; + +const int IOPOL_VFS_ENTITLED_RESERVE_ACCESS_ON = 1; + const int WNOHANG = 1; const int WUNTRACED = 2; @@ -5107,6 +5336,8 @@ const int EXIT_SUCCESS = 0; const int RAND_MAX = 2147483647; +const int _MALLOC_TYPE_MALLOC_BACKDEPLOY_PUBLIC = 1; + const int SHOREBIRD_UPDATE_ERROR = -1; const int SHOREBIRD_NO_UPDATE = 0; diff --git a/shorebird_code_push/lib/src/shorebird_updater_io.dart b/shorebird_code_push/lib/src/shorebird_updater_io.dart index 62a5962..52e03fa 100644 --- a/shorebird_code_push/lib/src/shorebird_updater_io.dart +++ b/shorebird_code_push/lib/src/shorebird_updater_io.dart @@ -95,15 +95,7 @@ class ShorebirdUpdaterImpl implements ShorebirdUpdater { Future update({UpdateTrack? track}) async { if (!_isAvailable) return; - Pointer result = nullptr; - - try { - result = await _run(() => _updater.update(track: track)); - // Explicitly catch all errors/exceptions to ensure we gracefully fallback. - // ignore: avoid_catches_without_on_clauses - } catch (_) { - return _legacyFallback(); - } + final result = await _run(() => _updater.update(track: track)); const unknownErrorMessage = 'An unknown error occurred.'; @@ -138,22 +130,6 @@ class ShorebirdUpdaterImpl implements ShorebirdUpdater { _updater.freeUpdateResult(result); } } - - // Fallback to downloadUpdate if update is not available. - Future _legacyFallback() async { - await _run(_updater.downloadUpdate); - final (current, next) = await (readCurrentPatch(), readNextPatch()).wait; - final status = next != null && current?.number != next.number - ? UpdateStatus.restartRequired - : UpdateStatus.upToDate; - if (status == UpdateStatus.restartRequired) return; - throw const UpdateException( - message: ''' -Downloading update failed but reason is unknown due to legacy updater. -Please upgrade the Shorebird Engine for improved error messages.''', - reason: UpdateFailureReason.unknown, - ); - } } extension on int { diff --git a/shorebird_code_push/lib/src/updater.dart b/shorebird_code_push/lib/src/updater.dart index aa5535a..7857394 100644 --- a/shorebird_code_push/lib/src/updater.dart +++ b/shorebird_code_push/lib/src/updater.dart @@ -26,11 +26,6 @@ class Updater { /// currentPatchNumber if no new patch is available. int nextPatchNumber() => bindings.shorebird_next_boot_patch_number(); - /// Downloads the latest patch, if available. - void downloadUpdate() => bindings.shorebird_update(); - - // New Methods added to support v2.0.0 of the Dart APIs // - /// Whether a new patch is available for download. bool checkForDownloadableUpdate({UpdateTrack? track}) => bindings.shorebird_check_for_downloadable_update( diff --git a/shorebird_code_push/pubspec.yaml b/shorebird_code_push/pubspec.yaml index d7d7f7d..4765b0f 100644 --- a/shorebird_code_push/pubspec.yaml +++ b/shorebird_code_push/pubspec.yaml @@ -1,6 +1,6 @@ name: shorebird_code_push description: Check for and download Shorebird code push updates from your app. -version: 2.0.6 +version: 2.0.7 homepage: https://shorebird.dev repository: https://github.com/shorebirdtech/updater/tree/main/shorebird_code_push @@ -22,7 +22,11 @@ ffigen: output: "lib/src/generated/updater_bindings.g.dart" name: "UpdaterBindings" headers: + # Only the Dart-stable surface drives ffigen. Engine-only symbols live in + # ../library/include/updater_engine.h and must NOT be reachable from the + # generated bindings — the package's public ABI is exactly what this file + # exposes. entry-points: - - "../library/include/updater.h" + - "../library/include/updater_dart.h" preamble: | // ignore_for_file: unused_element, unused_field diff --git a/shorebird_code_push/test/src/shorebird_updater_io_test.dart b/shorebird_code_push/test/src/shorebird_updater_io_test.dart index 347ff66..45462f0 100644 --- a/shorebird_code_push/test/src/shorebird_updater_io_test.dart +++ b/shorebird_code_push/test/src/shorebird_updater_io_test.dart @@ -354,7 +354,7 @@ void main() { overridePrint((_) async { shorebirdUpdater = ShorebirdUpdaterImpl(updater: updater, run: run); await expectLater(shorebirdUpdater.update(), completes); - verifyNever(updater.downloadUpdate); + verifyNever(() => updater.update()); }), ); }); @@ -387,6 +387,31 @@ void main() { }); }); + group('when the FFI call throws', () { + // Pre-2.0.7 the package wrapped this call in a try/catch and routed + // throws into a legacy fallback that called the now-removed + // shorebird_update symbol. The fallback was unreachable under the + // package's flutter: >=3.24.5 constraint, so it was deleted along + // with the symbol. The replacement contract: any unexpected throw + // from the FFI propagates to the caller, and freeUpdateResult is + // not invoked (we never received a pointer to free). + setUp(() { + when(() => updater.currentPatchNumber()).thenReturn(0); + when(() => updater.update()).thenThrow(Exception('boom')); + shorebirdUpdater = ShorebirdUpdaterImpl(updater: updater, run: run); + }); + + test('propagates the exception and does not call freeUpdateResult', + () async { + await expectLater( + shorebirdUpdater.update(), + throwsA(isA()), + ); + verify(() => updater.update()).called(1); + verifyNever(() => updater.freeUpdateResult(any())); + }); + }); + group('when no update is available', () { setUp(() { when(() => updater.currentPatchNumber()).thenReturn(0); @@ -540,52 +565,6 @@ void main() { }); }); - group('when an outdated version of the engine is used', () { - setUp(() { - when(updater.currentPatchNumber).thenReturn(0); - when(updater.nextPatchNumber).thenReturn(1); - when(() => updater.update()).thenThrow(Exception('oops')); - shorebirdUpdater = ShorebirdUpdaterImpl(updater: updater, run: run); - }); - - test('falls back to downloadUpdate', () async { - await expectLater(shorebirdUpdater.update(), completes); - verify(updater.update).called(1); - verify(updater.downloadUpdate).called(1); - verifyNever(() => updater.freeUpdateResult(any())); - }); - - group('when update fails', () { - setUp(() { - when(updater.currentPatchNumber).thenReturn(0); - when(updater.nextPatchNumber).thenReturn(0); - shorebirdUpdater = ShorebirdUpdaterImpl(updater: updater, run: run); - }); - - test('throws if legacy update fails', () async { - await expectLater( - shorebirdUpdater.update, - throwsA( - isA().having( - (e) => e.message, - 'message', - ''' -Downloading update failed but reason is unknown due to legacy updater. -Please upgrade the Shorebird Engine for improved error messages.''', - ).having( - (e) => e.reason, - 'reason', - UpdateFailureReason.unknown, - ), - ), - ); - verify(updater.update).called(1); - verify(updater.downloadUpdate).called(1); - verifyNever(() => updater.freeUpdateResult(any())); - }); - }); - }); - group('when an unsupported status code is returned', () { setUp(() { when(() => updater.currentPatchNumber()).thenReturn(0); diff --git a/shorebird_code_push/test/src/updater_test.dart b/shorebird_code_push/test/src/updater_test.dart index cbc39a8..6228159 100644 --- a/shorebird_code_push/test/src/updater_test.dart +++ b/shorebird_code_push/test/src/updater_test.dart @@ -30,7 +30,7 @@ void main() { }); group('currentPatchNumber', () { - test('forwards the result of shorebird_next_boot_patch_number', () { + test('forwards the result of shorebird_current_boot_patch_number', () { when( () => updaterBindings.shorebird_current_boot_patch_number(), ).thenReturn(123); @@ -40,7 +40,8 @@ void main() { }); group('checkForDownloadableUpdate', () { - test('forwards the result of shorebird_check_for_update', () { + test('forwards the result of shorebird_check_for_downloadable_update', + () { when( () => updaterBindings.shorebird_check_for_downloadable_update( nullptr, @@ -65,7 +66,8 @@ void main() { ).thenReturn(true); }); - test('forwards the result of shorebird_check_for_update', () { + test('forwards the result of shorebird_check_for_downloadable_update', + () { expect( updater.checkForDownloadableUpdate(track: UpdateTrack.beta), isTrue, @@ -101,14 +103,6 @@ void main() { }); }); - group('downloadUpdate', () { - test('calls bindings.shorebird_update', () { - when(() => updaterBindings.shorebird_update()).thenReturn(null); - updater.downloadUpdate(); - verify(() => updaterBindings.shorebird_update()).called(1); - }); - }); - group('update', () { test('calls bindings.shorebird_update_with_result', () { when(