feat(coreutils-port): resolve uucore Fluent messages at port time - #2309
Merged
Conversation
The weekly Coreutils Args Drift run has been failing since upstream
reworked `uucore::format`: `format/mod.rs` now pulls in
`error::strip_errno` and routes `FormatError`'s `Display` through
Fluent (`translate!` / `translate_text!`, 12 call sites). Module mode
rejected both, so regeneration aborted before it could build anything
and the workflow stayed red on main.
Args mode has always folded `translate!("k")` into a literal from the
utility's `.ftl`. Module mode now does the same, opted in per macro by
a `fluent` substitution naming its message files:
[[modules.substitutions]]
prefix = "crate::translate"
action = "fluent"
ftl_sources = ["src/uucore/locales/en-US.ftl", ...]
Each invocation folds to `String::from("…")`, or `format!("…", …)` when
the message has `{ $var }` slots, with arguments bound by name since a
message may interpolate its slots in a different order than the call
lists them. The `use` item is dropped: bashkit still links no Fluent
and ships no locale bundle.
Anything that would bake wrong user-visible text into generated code is
a hard error instead: a missing key, a slot with no matching argument,
an argument the message does not interpolate, a non-literal key, and
selectors/plurals/term references, whose rendering depends on runtime
locale data. Importing the Fluent crate itself stays rejected outright
under every manifest. A `translate*!` call that no stanza opted in is
rejected by the rewriter too, so the import-level gate cannot be
side-stepped by a fully-qualified call.
`.ftl` parsing moves to a shared `ftl.rs` now that both modes need it,
gaining pattern splitting for the placeable slots.
Also regenerate against uutils/coreutils@bfd7c63be, which is what the
drift run would have proposed. The 11 argument surfaces carry no flag
drift, only the rev header; the real diff is `format/`, where upstream
added parse spans to `FormatError` and switched its io-error text to
`strip_errno` (dropping Rust's " (os error N)" suffix, matching GNU).
Rendered message text is unchanged.
Verified locally end to end, every gate the drift workflow runs:
regeneration of all 11 arg surfaces plus the vendored module against
upstream HEAD, `cargo build -p bashkit`, the spec tests, a uutils
multicall built from the same tree, and the differential harness with
`BASHKIT_RUN_COREUTILS_DIFF=1` (22 passed). Regeneration is stable:
re-running the port produces byte-identical output.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
bashkit | f0ff8af | Commit Preview URL Branch Preview URL |
Aug 18 2026, 09:38 AM |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Module mode of
bashkit-coreutils-portcan now resolve uucore's Fluentmessages at port time, opted in per macro by a new
fluentsubstitution action in
vendored.toml:Each invocation folds to
String::from("…"), orformat!("…", …)when themessage carries
{ $var }slots, with arguments bound by name (a messagemay interpolate its slots in a different order than the call lists them). The
useitem is dropped, so bashkit still links no Fluent and ships no localebundle.
Anything that would bake wrong user-visible text into generated code is a hard
error instead: missing key, slot with no matching argument, argument the message
does not interpolate, non-literal key, and selectors / plurals / term references
(their rendering depends on runtime locale data). Importing the Fluent crate
itself stays rejected under every manifest, and a
translate*!call that nostanza opted in is rejected by the rewriter too, so the import-level gate cannot
be side-stepped by a fully-qualified call.
.ftlparsing moves into a sharedftl.rsnow that both modes need it, gainingpattern splitting for placeable slots.
This PR also carries the regeneration the drift run would have proposed, against
uutils/coreutils@bfd7c63be.Why
The weekly Coreutils Args Drift workflow has been red on
mainsinceupstream reworked
uucore::format.format/mod.rsnow importserror::strip_errnoand routesFormatError'sDisplaythrough Fluent(12
translate!/translate_text!call sites). Module mode rejected both, soregeneration aborted before it could build anything:
Adding a
strip_errnoshim alone would only have moved the failure to theFluent imports, which were a hard error by design. Args mode already resolved
translate!statically, so extending the same approach to module mode is theroot-cause fix rather than a carve-out.
Before / After
Before — regeneration aborts at the first unresolved import; the workflow
never reaches its build or test steps (run 31997016557).
After — the full pipeline runs locally, every gate the workflow uses:
The differential run used a
coreutilsmulticall built from the same upstreamtree the codegen ran against.
Generated
FormatError::fmt, folded from Fluent — no runtime i18n, textunchanged from the previous pin:
Regenerated-output diff. The 11 argument surfaces carry no flag drift —
only the rev header line. The substantive diff is
format/, where upstreamadded parse spans to
FormatError(SpecError(Vec<u8>, Range<usize>),MissingHex(Option<Range>),InvalidCharacter(char, Vec<u8>, Option<Range>))and switched its io-error text to
strip_errno, which drops Rust's(os error N)suffix and so matches GNU. Rendered message text is otherwisebyte-identical.
Regeneration is stable: re-running the port produces byte-identical output.
Risk
format/module, consumed byprintf. Itsspec/differential coverage passes against a same-rev uutils build, and the
new codegen path fails loudly rather than emitting wrong text — a missing key
or an unsupported Fluent construct aborts the port.
strip_errnochange makesprintf's write-error diagnostic match GNU bydropping the
(os error N)suffix. That is upstream's intent and the onlyuser-visible text difference in the regeneration.
printf.rs's leak-check test was updated for the new variant arities and nowcovers both spanned and unspanned constructions, keeping TM-INF-022 coverage
on the span payloads.
Checklist
Tests: 12 new module-mode tests (folding, name-binding, brace escaping in both
the literal and
format!renderings, and each hard-error path), 8 for theshared
ftl.rs, and 1 manifest-schema test.knowledge/runtimes/coreutils-args-port.mddocuments the new action and its constraints.
Generated by Claude Code