The C surface in `library/src/c_api` was a single bucket of `pub extern "C"` functions covering both consumers — `package:shorebird_code_push` (via ffigen) and Shorebird's Flutter engine fork (via direct C++ link). That made it hard to reason about which symbols are stable ABI versus internal, and ffigen was generating bindings for engine-only symbols that no Dart code calls. Split into two self-contained submodules and two cbindgen-generated headers: - `c_api::dart` → `include/updater_dart.h` (stable ABI; ffigen entry point). Defines `UpdateResult`, the `SHOREBIRD_*` status constants, and the five Dart-stable functions: `shorebird_current_boot_patch_number`, `shorebird_next_boot_patch_number`, `shorebird_check_for_downloadable_update`, `shorebird_update_with_result`, `shorebird_free_update_result`. - `c_api::engine` → `include/updater_engine.h` (no stability guarantee). Defines `AppParameters`, `FileCallbacks`, and the engine-only functions: `shorebird_init`, `shorebird_should_auto_update`, `shorebird_validate_next_boot_patch`, `shorebird_next_boot_patch_path`, `shorebird_free_string`, `shorebird_start_update_thread`, and the `shorebird_report_launch_*` trio. Each bucket file is self-contained: cbindgen scans only the file (`with_src` in build.rs) and emits the items it defines plus the C types they reference. There are no exclude/include lists in the cbindgen configs — adding a function to one bucket automatically lands it in the right header, and items in the other bucket cannot leak. `mod.rs` shrinks to a thin layer of private helpers shared by both buckets (`to_rust`, `allocate_c_string`, `free_c_string`, `log_on_error`) plus the test module. `include/updater.h` is removed; consumers include the specific header for their use case. The Flutter engine's `shell/common/shorebird/updater.cc` will be updated in a follow-up engine-repo PR to include `updater_engine.h` directly. Also drops two retired Dart-side symbols: - `shorebird_update` (replaced by `shorebird_update_with_result` in the Dart 2.0 rewrite, Nov 2024). - `shorebird_check_for_update` (replaced by `shorebird_check_for_downloadable_update` in the same rewrite). The shorebird_code_push package's `_legacyFallback` was the only path that still called `shorebird_update`. The package's `flutter: >=3.24.5` constraint guarantees the engine has `shorebird_update_with_result`, so the fallback was unreachable in practice. Removing it lets us drop the ABI symbol. Bumps shorebird_code_push to 2.0.7. Bindings regenerated via ffigen now contain only the five Dart-stable symbols. Follow-up engine PR will: include `updater_engine.h` instead of the removed `updater.h`; clean up `android_exports.lst` (drop the ghost `shorebird_active_path` and `shorebird_active_patch_number` exports, drop `shorebird_check_for_update`).
2.8 KiB
What This Is
Shorebird's updater library — a Rust core that gets linked into Flutter Engine (libflutter.so) as a static C library to enable over-the-air code push for Flutter apps. The Dart package (shorebird_code_push) calls into it via FFI.
Build & Test
# Rust (from workspace root or individual crate dir)
cargo build --verbose
cargo test
cargo llvm-cov --lcov --output-path lcov.info # tests + coverage (CI uses this)
# Prerequisites for coverage: rustup component add llvm-tools-preview && cargo install cargo-llvm-cov
# Dart package
cd shorebird_code_push
flutter pub get
flutter test --coverage
dart format --set-exit-if-changed .
dart analyze --fatal-warnings lib test
Rust tests that call shorebird_init must run single-threaded — use the #[serial] attribute from serial_test. The global static config means concurrent shorebird_init calls stomp each other, so any test that touches init needs #[serial].
Workspace Layout
Cargo workspace with two members: library (core updater) and patch (CLI tool for packaging patches). The Dart FFI package lives in shorebird_code_push/.
Architecture (library/)
Layers from outside in:
- C API (
c_api/mod.rs) — stable C interface consumed by Flutter Engine; thin wrapper around Rust API.c_file.rsprovides a read-seek interface for iOS patch files. - Rust API (
updater.rs, re-exported fromlib.rs) — core logic including the boot state machine and patch check/update state machines. - Cache (
cache/) — on-disk state viapatches_state.json.updater_state.rsis the public API;patch_manager.rsmanages patch files;signing.rshandles cryptographic verification. - Network (
network.rs) — server communication for patch checks and downloads. - Platform (
android.rs,logging.rs) — platform-specific integration (Android logcat, iOS oslog, etc.).
Thread safety: global config object with locking, since it's called from both flutter_main thread (init) and Dart/UI thread (updates).
Design principle: "fail open" — always fall back to the currently installed version; never leave the app in a broken state.
Key Details
- C headers (
include/updater_dart.hfor the ffigen-consumed Dart-stable surface,include/updater_engine.hfor the engine-internal surface) are auto-generated bycbindgenviabuild.rs— don't edit manually. The two configs live atlibrary/cbindgen_dart.tomlandlibrary/cbindgen_engine.toml. - Dart FFI bindings (
updater_bindings.g.dart) are generated byffigen— 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. - CI requires semantic PR titles (conventional commits style).