Skip to content

fix: put cargo target dir inside the GN build output dir#124

Merged
eseidel merged 1 commit intoshorebird/devfrom
shorebird/cargo-target-dir-in-out
Apr 8, 2026
Merged

fix: put cargo target dir inside the GN build output dir#124
eseidel merged 1 commit intoshorebird/devfrom
shorebird/cargo-target-dir-in-out

Conversation

@eseidel
Copy link
Copy Markdown

@eseidel eseidel commented Apr 8, 2026

Summary

build_rust_updater previously let cargo write its target/ directly into the source tree at flutter/engine/src/flutter/third_party/updater/target/. Two consequences:

  1. The engine's existing rm -rf out/<config> clobber on every build doesn't touch it, so compiled rust artifacts silently survived across runs. This just bit us: a stale libzstd_sys.rlib reused on Sandpiper masked the IPHONEOS_DEPLOYMENT_TARGET fix from fix: thread Apple deployment target into Rust updater build #123 entirely — the new env var never had a chance to take effect because cargo short-circuited the rebuild.
  2. All GN configs (debug/release/ios/android/...) shared one cargo target dir, stepping on each other's rlibs.

Fix

  • Pass --target-dir to cargo, pointing at $root_out_dir/cargo_target.
  • Update build_rust_updater.gni's shorebird_updater_output_lib to match.

The cargo workspace now lives next to the rest of the GN build output and is clobbered by the same rm -rf out that has always clobbered the C++ engine artifacts. No special-case cleanup in checkout.sh. Each GN config gets its own cargo target dir.

Note for reviewer

This is a follow-up to #123 (which threaded IPHONEOS_DEPLOYMENT_TARGET through to cargo) and a follow-up to #120 (your original GN/Ninja integration). Together with #123, this should make the iOS arm64 zstd link error from shorebirdtech/_shorebird#2014 actually go away on the next Sandpiper build.

Note for ops

The old target/ directory at ~/.engine_checkout/flutter/engine/src/flutter/third_party/updater/target/ on the Sandpiper host will become orphaned after this lands (the new code never writes there). It's harmless to leave around but somebody should rm -rf it the next time they ssh in.

Bigger picture (not in this PR)

We discussed whether to keep cargo-as-opaque-blob or migrate to rustc-direct (Fuchsia/Chromium style, where each crate becomes a GN target compiled by rustc directly with the engine's clang for any *-sys C code). For now we're keeping cargo since this is a one-week-old integration with one Rust dep, but the rustc-direct option becomes worth doing once we have a second crate or hit hermetic-build requirements. There's a // TODO worth adding in build_rust_updater.gni later.

Tracking: shorebirdtech/_shorebird#2014
Follow-up to: #120, #123

build_rust_updater previously let cargo write its target/ directly into
the source tree at flutter/engine/src/flutter/third_party/updater/target.
Two consequences:

1. The engine's existing 'rm -rf out/<config>' clobber on every build
   doesn't touch it, so compiled rust artifacts silently survived
   across runs and could mask source/env changes (we just hit this when
   a stale libzstd_sys.rlib reused on Sandpiper masked the
   IPHONEOS_DEPLOYMENT_TARGET fix from #123).
2. All GN configs (debug/release/ios/android/...) shared one cargo
   target dir, stepping on each other's rlibs.

Pass --target-dir to cargo, pointing at $root_out_dir/cargo_target,
and update build_rust_updater.gni's shorebird_updater_output_lib to
match. The cargo workspace now lives next to the rest of the GN build
output and is clobbered by the same 'rm -rf out' that has always
clobbered the C++ engine artifacts.

Tracking: shorebirdtech/_shorebird#2014
Follow-up to: #123
@eseidel eseidel requested a review from bdero April 8, 2026 04:00
@eseidel eseidel merged commit 87e6420 into shorebird/dev Apr 8, 2026
6 of 8 checks passed
@eseidel eseidel deleted the shorebird/cargo-target-dir-in-out branch April 8, 2026 04:09
eseidel added a commit that referenced this pull request Apr 8, 2026
The updater's .cargo/config.toml sets '-C target-feature=+crt-static'
so rustc compiles the rlib expecting static-CRT linkage. But cargo
populates CARGO_CFG_TARGET_FEATURE from the rustc target spec's default
features, not from user rustflags, so +crt-static is invisible to
build scripts. The cc crate (used by transitive *-sys deps like
zstd-sys to compile their C sources) therefore falls back to /MD,
producing .obj files full of __imp_* references that the engine's /MT
link cannot resolve.

Symptom: engine link on Windows fails with LNK2019 unresolved external
symbol __imp_clock / __imp__wassert / __imp_qsort_s from zstd-sys's
zdict/cover/fastcover/divsufsort translation units, after the updater
rolled to a version whose ureq stack pulls in zstd-sys via its
compression features.

Fix: force /MT via the per-target CFLAGS env that cc honors. cl.exe
emits warning D9025 overriding '/MD' with '/MT' and uses the last one
specified -- /MT wins, the resulting .obj files reference the static
CRT, and the engine link succeeds.

This is another instance of the toolchain-consistency problem that
#123 solved for iOS deployment target. Every new platform-specific C
build setting we discover needs another env var threaded through
build_rust_updater.py. Longer term this argues for going rustc-direct
(Fuchsia/Chromium style), but for now we have one rust dep and the
env-var-per-setting pattern is tractable.

Tracking: shorebirdtech/_shorebird#2014
Follow-up to: #123, #124
eseidel added a commit that referenced this pull request Apr 8, 2026
The updater's .cargo/config.toml sets '-C target-feature=+crt-static'
so rustc compiles the rlib expecting static-CRT linkage. But cargo
populates CARGO_CFG_TARGET_FEATURE from the rustc target spec's default
features, not from user rustflags, so +crt-static is invisible to
build scripts. The cc crate (used by transitive *-sys deps like
zstd-sys to compile their C sources) therefore falls back to /MD,
producing .obj files full of __imp_* references that the engine's /MT
link cannot resolve.

Symptom: engine link on Windows fails with LNK2019 unresolved external
symbol __imp_clock / __imp__wassert / __imp_qsort_s from zstd-sys's
zdict/cover/fastcover/divsufsort translation units, after the updater
rolled to a version whose ureq stack pulls in zstd-sys via its
compression features.

Fix: force /MT via the per-target CFLAGS env that cc honors. cl.exe
emits warning D9025 overriding '/MD' with '/MT' and uses the last one
specified -- /MT wins, the resulting .obj files reference the static
CRT, and the engine link succeeds.

This is another instance of the toolchain-consistency problem that
#123 solved for iOS deployment target. Every new platform-specific C
build setting we discover needs another env var threaded through
build_rust_updater.py. Longer term this argues for going rustc-direct
(Fuchsia/Chromium style), but for now we have one rust dep and the
env-var-per-setting pattern is tractable.

Tracking: shorebirdtech/_shorebird#2014
Follow-up to: #123, #124
eseidel added a commit that referenced this pull request Apr 8, 2026
The updater's .cargo/config.toml sets '-C target-feature=+crt-static'
so rustc compiles the rlib expecting static-CRT linkage. But cargo
populates CARGO_CFG_TARGET_FEATURE from the rustc target spec's default
features, not from user rustflags, so +crt-static is invisible to
build scripts. The cc crate (used by transitive *-sys deps like
zstd-sys to compile their C sources) therefore falls back to /MD,
producing .obj files full of __imp_* references that the engine's /MT
link cannot resolve.

Symptom: engine link on Windows fails with LNK2019 unresolved external
symbol __imp_clock / __imp__wassert / __imp_qsort_s from zstd-sys's
zdict/cover/fastcover/divsufsort translation units, after the updater
rolled to a version whose ureq stack pulls in zstd-sys via its
compression features.

Fix: force /MT via the per-target CFLAGS env that cc honors. cl.exe
emits warning D9025 overriding '/MD' with '/MT' and uses the last one
specified -- /MT wins, the resulting .obj files reference the static
CRT, and the engine link succeeds.

This is another instance of the toolchain-consistency problem that
#123 solved for iOS deployment target. Every new platform-specific C
build setting we discover needs another env var threaded through
build_rust_updater.py. Longer term this argues for going rustc-direct
(Fuchsia/Chromium style), but for now we have one rust dep and the
env-var-per-setting pattern is tractable.

Tracking: shorebirdtech/_shorebird#2014
Follow-up to: #123, #124
eseidel added a commit that referenced this pull request Apr 8, 2026
mac_build.sh, linux_build.sh, and win_build.sh each had a leftover
'cd $UPDATER_SRC/library && cargo build --target ...' preamble from
before bdero's #120 GN/Ninja integration of the rust updater build.
mac_build.sh even had a FIXME at the top documenting the intent to
delete it. These preambles:

  1. Bypass the GN action entirely, so none of the env-var plumbing in
     build_rust_updater.py applies (no IPHONEOS_DEPLOYMENT_TARGET from
     #123, no CFLAGS_*_windows_msvc=/MT from #125, no Android NDK env).
  2. Write to the source-tree default cargo target dir at
     third_party/updater/target/, ignoring #124's redirect to
     $root_out_dir/cargo_target/, so the engine 'rm -rf out' clobber
     never touches their output.
  3. On the persistent Sandpiper runner this means stale rlibs from
     pre-fix builds keep getting reused, masking #123 entirely and
     producing the same iOS arm64 zstd_sys deployment-target failure
     after every fix attempt.

Delete the library cargo invocations from all three scripts. The GN
build of any engine target that depends transitively on
//flutter/shell/common/shorebird:updater (which is most of them) will
now invoke build_rust_updater.py via Ninja and produce libupdater.a /
updater.lib in the right place with the right env vars.

The patch tool's separate cargo build in mac_build.sh / linux_build.sh
is a standalone CLI not linked into the engine, so the GN build does
not cover it. Left in place with a TODO to migrate it later.

Tracking: shorebirdtech/_shorebird#2014
Follow-up to: #120, #123, #124, #125
eseidel added a commit that referenced this pull request Apr 8, 2026
mac_build.sh, linux_build.sh, and win_build.sh each had a leftover
'cd $UPDATER_SRC/library && cargo build --target ...' preamble from
before bdero's #120 GN/Ninja integration of the rust updater build.
mac_build.sh even had a FIXME at the top documenting the intent to
delete it. These preambles:

  1. Bypass the GN action entirely, so none of the env-var plumbing in
     build_rust_updater.py applies (no IPHONEOS_DEPLOYMENT_TARGET from
     #123, no CFLAGS_*_windows_msvc=/MT from #125, no Android NDK env).
  2. Write to the source-tree default cargo target dir at
     third_party/updater/target/, ignoring #124's redirect to
     $root_out_dir/cargo_target/, so the engine 'rm -rf out' clobber
     never touches their output.
  3. On the persistent Sandpiper runner this means stale rlibs from
     pre-fix builds keep getting reused, masking #123 entirely and
     producing the same iOS arm64 zstd_sys deployment-target failure
     after every fix attempt.

Delete the library cargo invocations from all three scripts. The GN
build of any engine target that depends transitively on
//flutter/shell/common/shorebird:updater (which is most of them) will
now invoke build_rust_updater.py via Ninja and produce libupdater.a /
updater.lib in the right place with the right env vars.

The patch tool's separate cargo build in mac_build.sh / linux_build.sh
is a standalone CLI not linked into the engine, so the GN build does
not cover it. Left in place with a TODO to migrate it later.

Tracking: shorebirdtech/_shorebird#2014
Follow-up to: #120, #123, #124, #125
eseidel added a commit that referenced this pull request Apr 8, 2026
mac_build.sh, linux_build.sh, and win_build.sh each had a leftover
'cd $UPDATER_SRC/library && cargo build --target ...' preamble from
before bdero's #120 GN/Ninja integration of the rust updater build.
mac_build.sh even had a FIXME at the top documenting the intent to
delete it. These preambles:

  1. Bypass the GN action entirely, so none of the env-var plumbing in
     build_rust_updater.py applies (no IPHONEOS_DEPLOYMENT_TARGET from
     #123, no CFLAGS_*_windows_msvc=/MT from #125, no Android NDK env).
  2. Write to the source-tree default cargo target dir at
     third_party/updater/target/, ignoring #124's redirect to
     $root_out_dir/cargo_target/, so the engine 'rm -rf out' clobber
     never touches their output.
  3. On the persistent Sandpiper runner this means stale rlibs from
     pre-fix builds keep getting reused, masking #123 entirely and
     producing the same iOS arm64 zstd_sys deployment-target failure
     after every fix attempt.

Delete the library cargo invocations from all three scripts. The GN
build of any engine target that depends transitively on
//flutter/shell/common/shorebird:updater (which is most of them) will
now invoke build_rust_updater.py via Ninja and produce libupdater.a /
updater.lib in the right place with the right env vars.

The patch tool's separate cargo build in mac_build.sh / linux_build.sh
is a standalone CLI not linked into the engine, so the GN build does
not cover it. Left in place with a TODO to migrate it later.

Tracking: shorebirdtech/_shorebird#2014
Follow-up to: #120, #123, #124, #125
eseidel added a commit that referenced this pull request Apr 8, 2026
The action only declared its stamp file as an output, which worked
when shorebird_updater_output_lib lived under //flutter/third_party/...
because GN treated source-tree paths as existing files needing no rule.
After #124 moved the cargo target dir into $root_out_dir/cargo_target,
the lib path became a build-dir path and Ninja correctly demands a
rule producing it. Without the lib in outputs:

  ninja: error: 'cargo_target/aarch64-linux-android/release/libupdater.a',
  needed by 'libflutter.so', missing and no known rule to make it

Add shorebird_updater_output_lib to the action's outputs alongside the
stamp file. The cargo invocation already produces the file at this
path; this just tells Ninja which rule is responsible.

Tracking: shorebirdtech/_shorebird#2014
Follow-up to: #124, #127
eseidel added a commit that referenced this pull request Apr 8, 2026
…128)

The action only declared its stamp file as an output, which worked
when shorebird_updater_output_lib lived under //flutter/third_party/...
because GN treated source-tree paths as existing files needing no rule.
After #124 moved the cargo target dir into $root_out_dir/cargo_target,
the lib path became a build-dir path and Ninja correctly demands a
rule producing it. Without the lib in outputs:

  ninja: error: 'cargo_target/aarch64-linux-android/release/libupdater.a',
  needed by 'libflutter.so', missing and no known rule to make it

Add shorebird_updater_output_lib to the action's outputs alongside the
stamp file. The cargo invocation already produces the file at this
path; this just tells Ninja which rule is responsible.

Tracking: shorebirdtech/_shorebird#2014
Follow-up to: #124, #127
eseidel added a commit that referenced this pull request Apr 8, 2026
build_rust_updater.gni previously set shorebird_updater_supported based
only on target_os (is_android || is_ios || ...). But GN evaluates BUILD
files under every toolchain in use, including host and sub toolchains
that can have unusual (target_os, target_cpu) combinations we don't
handle. When a toolchain entered the `if (shorebird_updater_supported)`
block but hit none of the os/cpu cases that set
`shorebird_updater_rust_target`, GN blew up:

  ERROR at build_rust_updater.gni:72: Undefined identifier in string
    expansion.
    shorebird_updater_output_lib = "$...$shorebird_updater_rust_target..."
  "shorebird_updater_rust_target" is not currently in scope.

This happens on Windows host builds of android engine variants when
the host toolchain(s) land on a combo like (is_win, target_cpu != x64)
or similar, which our is_win branch doesn't cover.

Flip the logic: compute shorebird_updater_rust_target first, default
to empty, and derive `shorebird_updater_supported` from whether it
got a value. Unhandled combos become unsupported, so the consuming
BUILD.gn files' `if (shorebird_updater_supported)` gate cleanly skips
the updater for that toolchain — which is correct, since the updater
only needs to be built for toolchains whose final binary is the engine.

Tracking: shorebirdtech/_shorebird#2014
Follow-up to: #124, #127, #128
eseidel added a commit that referenced this pull request Apr 8, 2026
…130)

build_rust_updater.gni previously set shorebird_updater_supported based
only on target_os (is_android || is_ios || ...). But GN evaluates BUILD
files under every toolchain in use, including host and sub toolchains
that can have unusual (target_os, target_cpu) combinations we don't
handle. When a toolchain entered the `if (shorebird_updater_supported)`
block but hit none of the os/cpu cases that set
`shorebird_updater_rust_target`, GN blew up:

  ERROR at build_rust_updater.gni:72: Undefined identifier in string
    expansion.
    shorebird_updater_output_lib = "$...$shorebird_updater_rust_target..."
  "shorebird_updater_rust_target" is not currently in scope.

This happens on Windows host builds of android engine variants when
the host toolchain(s) land on a combo like (is_win, target_cpu != x64)
or similar, which our is_win branch doesn't cover.

Flip the logic: compute shorebird_updater_rust_target first, default
to empty, and derive `shorebird_updater_supported` from whether it
got a value. Unhandled combos become unsupported, so the consuming
BUILD.gn files' `if (shorebird_updater_supported)` gate cleanly skips
the updater for that toolchain — which is correct, since the updater
only needs to be built for toolchains whose final binary is the engine.

Tracking: shorebirdtech/_shorebird#2014
Follow-up to: #124, #127, #128
eseidel added a commit that referenced this pull request Apr 8, 2026
Picks up the updater roll (#122) plus the GN/build fixes needed to
build the engine against the new ureq-based updater stack on all three
platforms (#123, #124, #125, #127, #128, #130). This is the first
engine revision where a full Linux + macOS + Windows build completes
against the rolled updater.

Build: https://github.com/shorebirdtech/_build_engine/actions/runs/24139880449
eseidel added a commit that referenced this pull request Apr 8, 2026
Picks up the updater roll (#122) plus the GN/build fixes needed to
build the engine against the new ureq-based updater stack on all three
platforms (#123, #124, #125, #127, #128, #130). This is the first
engine revision where a full Linux + macOS + Windows build completes
against the rolled updater.

Build: https://github.com/shorebirdtech/_build_engine/actions/runs/24139880449
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants