* fix: surface flush errors and write atomically in disk_io::write
`BufWriter`'s `Drop` impl silently discards flush errors. Because
`patches_state.json` and `state.json` are small enough to fit inside
`BufWriter`'s 8 KB buffer, the only time the bytes actually reach disk
is during the implicit flush at drop — and that flush's I/O errors
(transient iOS Data Protection lock, ENOSPC, etc.) were invisible to
the caller. `disk_io::write` returned Ok, `install_patch` returned Ok,
`update()` returned `UpdateInstalled`, but `patches_state.json` was
left at 0 bytes. On the next launch, `load_patches_state` failed to
deserialize and fell back to default (`next_boot_patch: None`), so the
subsequent `checkForUpdate()` saw the server's patch as newly
installable and reported `UpdateStatus.outdated` despite the app
having "successfully" installed it moments earlier.
Change `disk_io::write` to:
- Write to a sibling `<file>.tmp` and atomically `rename` into place,
so `path` is never observed in a truncated/empty state by a
concurrent or post-crash reader.
- Explicitly unwrap the `BufWriter` via `into_inner()`, which calls
`flush_buf` and returns any I/O error as `IntoInnerError` instead
of dropping it on the floor.
- Clean up the temp file on failure.
Extract `serialize_and_flush` so the flush-error path is unit-testable
without filesystem tricks. Add three tests:
- temp file is cleaned up after a successful write
- a failed write preserves the existing file at \`path\`
- regression: \`serialize_and_flush\` surfaces inner-writer errors
(confirmed to fail on the pre-fix code)
* style: apply cargo fmt and rename roundtripped -> reloaded for cspell
* test: drop unreachable flush body in FailingWriter
* test: drop Result<()>/? boilerplate in new tests
Updater library
This is the Rust side of the Shorebird code push system. This is built
in Rust with a C API for easy calling from other languages, most notably
for linking into libflutter.so.
The primary modification Shorebird makes to the stock Flutter engine is adding support for the updater library (this repo). The updater library is written in Rust and is used to update the code running in the Flutter app. The updater library is built as a static library and is linked into the Flutter engine during build time.
Parts
library: Runtime library linked into Flutter Engine to unpack and apply updates. Build into libupdater.a and linked into libflutter.so.patch: Developer tooling to package Shorebird updates ("patches"). Built intopatch.exeand downloaded and run byshorebirdcommand line tool: https://github.com/shorebirdtech/shorebird/tree/main/packages/shorebird_cli.shorebird_code_push: The Dart bindings for communicating with the updater library from within a Flutter app. Published to pub.dev, usage is optional by developers.
Most interesting code is in the library directory. There is also
a README.md in that directory explaining the design.
Developing
It's best to edit this repository from within an engine checkout. See BUILDING_ENGINE.md for instructions on how to set up an engine checkout.
The workflow I use involves 2 to 3 VSC windows:
- Opening the engine
src.
In that terminal I:
cd third_party/updater
- To build the updater as part of the engine:
cargo ndk --target aarch64-linux-android build --release && \
ninja -C ../../out/android_release_arm64 && say "done"
The cargo part should not be needed, but I haven't yet done the work to integrate the Rust code into the gn files for the Flutter engine yet.
I add say "done" to the end as linking can take several minutes for release
Android builds.
-
In a second window, I open
code third_party/updater. I do this because otherwise therust_analyzercan't seem to find the rust code. We could fix this by adding the directory to the VSC workspace, but I'm not sure where we would put the workspace file in the first place.srcis actuallyshorebirdtech/buildrootand is controlled viagclientbyshorebirdtech/engine/DEPS. -
In a third window I open my test app. e.g.:
flutter create test_app
cd test_app
shorebird init
shorebird release
code .
- To run the test app with my local engine I use:
shorebird run --local-engine-src-path $HOME/Documents/GitHub/engine/src \
--local-engine android_release_arm64
You may also need to build out/host_release once as flutter build looks for
some Dart .dill files in host_release.
Coverage
We'd like to get to 100% coverage but aren't there yet.
https://github.com/taiki-e/cargo-llvm-cov is the best tool I've found for generating coverage reports.
Install: https://github.com/taiki-e/cargo-llvm-cov#installation
cargo llvm-cov will then generate the report.