Files
shorebird-updater/CLAUDE.md
T
Eric Seidel 34509fca3c refactor: split C API into Dart and engine surfaces (#350)
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`).
2026-05-04 15:56:09 -07:00

49 lines
2.8 KiB
Markdown

## 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
```bash
# 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:
1. **C API** (`c_api/mod.rs`) — stable C interface consumed by Flutter Engine; thin wrapper around Rust API. `c_file.rs` provides a read-seek interface for iOS patch files.
2. **Rust API** (`updater.rs`, re-exported from `lib.rs`) — core logic including the boot state machine and patch check/update state machines.
3. **Cache** (`cache/`) — on-disk state via `patches_state.json`. `updater_state.rs` is the public API; `patch_manager.rs` manages patch files; `signing.rs` handles cryptographic verification.
4. **Network** (`network.rs`) — server communication for patch checks and downloads.
5. **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.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`.
- CI requires semantic PR titles (conventional commits style).