From 2b0e47431c06963287bf214aec9ff086aa875b5b Mon Sep 17 00:00:00 2001 From: Gabor Gevay Date: Mon, 7 Sep 2026 17:05:56 +0200 Subject: [PATCH 1/2] ci: keep the cargo-fuzz workspace's crate patches in sync with the root The fuzz crates build in their own workspace, test/cargo-fuzz, which carries a copy of the root's `[patch.crates-io]` table. Its iceberg-rust entries were still pinned to the 0.9.0 fork revision after the root moved to the 0.10.1 revision in #38471. A patch that no longer satisfies the version requirement is not an error to Cargo: it lands in `[[patch.unused]]` and the crate resolves from crates.io, which lacks the fork API that #38475 started using, so every fuzz target reaching mz-storage-types has failed to build since 2026-08-28. The stale launchdarkly-server-sdk patch in the same table had drifted the same way. Repin the iceberg entries to the root's current revision, drop the stale one, and add a bin/lint-cargo check that fails when the fuzz workspace's patch table carries an entry the root lacks or one that differs from the root's, so the next root patch bump fails lint instead of the nightly. Co-Authored-By: Claude Fable 5.1 Co-Authored-By: Claude Opus 5.5 --- misc/python/materialize/cli/lint-cargo.py | 46 +++++++++++++++++++++++ test/cargo-fuzz/Cargo.toml | 16 ++++---- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/misc/python/materialize/cli/lint-cargo.py b/misc/python/materialize/cli/lint-cargo.py index c4e2c8e55d0ea..219e5921250b1 100644 --- a/misc/python/materialize/cli/lint-cargo.py +++ b/misc/python/materialize/cli/lint-cargo.py @@ -146,6 +146,51 @@ def version_req(spec: object) -> str | None: return success +def check_fuzz_patches_mirror_root(workspace: Workspace) -> bool: + """Checks that the `[patch.crates-io]` entries of the cargo-fuzz workspace + (test/cargo-fuzz) match the root workspace's. + + A patch that does not apply is not an error to cargo: it lands in + `[[patch.unused]]` and the crate resolves from crates.io instead, with only + a warning. So a fork revision that drifts from the root here silently builds + the fuzz targets against a different crate than production, and fails + whenever the fork carries API the published crate lacks. + + The fuzz workspace may omit a root entry that nothing in its graph depends + on, since cargo warns about patches it cannot apply, but it may not carry an + entry the root does not have, and every entry it shares with the root must + be identical.""" + + with open(MZ_ROOT / "Cargo.toml") as f: + root_patches = toml.load(f).get("patch", {}).get("crates-io", {}) + with open(MZ_ROOT / "test" / "cargo-fuzz" / "Cargo.toml") as f: + fuzz_patches = toml.load(f).get("patch", {}).get("crates-io", {}) + + success = True + for name, spec in sorted(fuzz_patches.items()): + if name not in root_patches: + print( + f"test/cargo-fuzz/Cargo.toml: {name} is patched here but not in " + f"the root Cargo.toml", + file=sys.stderr, + ) + success = False + elif spec != root_patches[name]: + print( + f"test/cargo-fuzz/Cargo.toml: {name} = {spec} must match the " + f"root Cargo.toml's {name} = {root_patches[name]}", + file=sys.stderr, + ) + success = False + if not success: + print( + "\nhint: copy the entry from the root `[patch.crates-io]` verbatim, " + "or drop it here if the root no longer patches that crate.", + file=sys.stderr, + ) + return success + + def main() -> None: workspace = Workspace(MZ_ROOT) lints = [ @@ -153,6 +198,7 @@ def main() -> None: check_default_members, check_workspace_dependencies, check_fuzz_versions_mirror_root, + check_fuzz_patches_mirror_root, ] # Run every lint, then combine. `success and lint(...)` would short-circuit # and skip the remaining lints after the first failure, under-reporting. diff --git a/test/cargo-fuzz/Cargo.toml b/test/cargo-fuzz/Cargo.toml index d0f62d2ebded9..6ad8f4b6bea90 100644 --- a/test/cargo-fuzz/Cargo.toml +++ b/test/cargo-fuzz/Cargo.toml @@ -14,8 +14,9 @@ # the fuzz crates' dependency graph actually uses (root entries with no consumer # here, currently `duckdb` and `postgres_array`, are omitted to avoid cargo's # "patch was not used in the crate graph" warnings). Every entry that IS present -# must match the root's rev/version verbatim. If a fuzz crate gains a dependency -# that needs another patched crate, copy that entry from the root. +# must match the root's rev/version verbatim, which `bin/lint-cargo` enforces. +# If a fuzz crate gains a dependency that needs another patched crate, copy that +# entry from the root. [workspace] resolver = "2" @@ -51,9 +52,6 @@ postgres-openssl = { git = "https://github.com/MaterializeInc/rust-postgres" } # Waiting on https://github.com/MaterializeInc/serde-value/pull/35. serde-value = { git = "https://github.com/MaterializeInc/serde-value.git" } -# Waiting for resolution of https://github.com/launchdarkly/rust-server-sdk/issues/116 -launchdarkly-server-sdk = { git = "https://github.com/MaterializeInc/rust-server-sdk", rev = "3e0a0b98b09a2970f292577a07e1c9382b65b5da" } - # Waiting on https://github.com/edenhill/librdkafka/pull/4051. rdkafka = { git = "https://github.com/MaterializeInc/rust-rdkafka.git" } rdkafka-sys = { git = "https://github.com/MaterializeInc/rust-rdkafka.git" } @@ -69,7 +67,7 @@ tiberius = { git = "https://github.com/MaterializeInc/tiberius", rev="64ca594cc2 async-compression = { git = "https://github.com/MaterializeInc/async-compression.git", rev = "fe7411eb6104a02a89e2c3a76ab326dd6594214d" } # Custom iceberg features for mz -# All changes should go to the `mz_v0.9.0` branch. -iceberg = { git = "https://github.com/MaterializeInc/iceberg-rust.git", rev = "dedd9231ee88ee979b648e14792878b40e74c20a" } -iceberg-catalog-rest = { git = "https://github.com/MaterializeInc/iceberg-rust.git", rev = "dedd9231ee88ee979b648e14792878b40e74c20a" } -iceberg-storage-opendal = { git = "https://github.com/MaterializeInc/iceberg-rust.git", rev = "dedd9231ee88ee979b648e14792878b40e74c20a" } +# All changes should go to the `mz_v0.10.x` branch. +iceberg = { git = "https://github.com/MaterializeInc/iceberg-rust.git", rev = "9e252ad09e27950d2f3cbc7e2da0d1f5255731a0" } +iceberg-catalog-rest = { git = "https://github.com/MaterializeInc/iceberg-rust.git", rev = "9e252ad09e27950d2f3cbc7e2da0d1f5255731a0" } +iceberg-storage-opendal = { git = "https://github.com/MaterializeInc/iceberg-rust.git", rev = "9e252ad09e27950d2f3cbc7e2da0d1f5255731a0" } From 6fb3506e04f6f8efcda7e6d465a21fb68cb35dbb Mon Sep 17 00:00:00 2001 From: Gabor Gevay Date: Fri, 25 Sep 2026 20:49:22 +0200 Subject: [PATCH 2/2] ci: require every root crate patch in the cargo-fuzz workspace A root `[patch.crates-io]` entry missing from test/cargo-fuzz/Cargo.toml makes the fuzz build use the crates.io release without any cargo warning. The chrono-tz fork had already gone missing that way. Add it, and make `bin/lint-cargo` fail on any missing root entry that is not explicitly listed as outside the fuzz dependency graph. Co-Authored-By: Claude Opus 5.5 --- misc/python/materialize/cli/lint-cargo.py | 32 ++++++++++++++--------- test/cargo-fuzz/Cargo.toml | 17 +++++++----- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/misc/python/materialize/cli/lint-cargo.py b/misc/python/materialize/cli/lint-cargo.py index 219e5921250b1..aeab7cb96e632 100644 --- a/misc/python/materialize/cli/lint-cargo.py +++ b/misc/python/materialize/cli/lint-cargo.py @@ -147,19 +147,18 @@ def version_req(spec: object) -> str | None: def check_fuzz_patches_mirror_root(workspace: Workspace) -> bool: - """Checks that the `[patch.crates-io]` entries of the cargo-fuzz workspace - (test/cargo-fuzz) match the root workspace's. + """Checks that the cargo-fuzz workspace (test/cargo-fuzz) carries the root + workspace's `[patch.crates-io]` entries verbatim. - A patch that does not apply is not an error to cargo: it lands in - `[[patch.unused]]` and the crate resolves from crates.io instead, with only - a warning. So a fork revision that drifts from the root here silently builds - the fuzz targets against a different crate than production, and fails - whenever the fork carries API the published crate lacks. + Cargo never fails over a patch mismatch. An entry that does not apply lands + in `[[patch.unused]]` with only a warning, and a missing entry passes with no + warning at all. Either way the fuzz targets silently build against the + crates.io release instead of the fork production uses, and the build breaks + whenever the fork carries API the published crate lacks.""" - The fuzz workspace may omit a root entry that nothing in its graph depends - on, since cargo warns about patches it cannot apply, but it may not carry an - entry the root does not have, and every entry it shares with the root must - be identical.""" + # Root patches for crates outside the fuzz crates' dependency graph. Cargo + # would warn that they are unused, so the fuzz workspace leaves them out. + OMITTED = {"duckdb", "postgres_array"} with open(MZ_ROOT / "Cargo.toml") as f: root_patches = toml.load(f).get("patch", {}).get("crates-io", {}) @@ -182,10 +181,19 @@ def check_fuzz_patches_mirror_root(workspace: Workspace) -> bool: file=sys.stderr, ) success = False + for name in sorted(root_patches.keys() - fuzz_patches.keys() - OMITTED): + print( + f"test/cargo-fuzz/Cargo.toml: {name} is patched in the root " + f"Cargo.toml but not here", + file=sys.stderr, + ) + success = False if not success: print( "\nhint: copy the entry from the root `[patch.crates-io]` verbatim, " - "or drop it here if the root no longer patches that crate.", + "or drop it here if the root no longer patches that crate. A root " + "entry that no fuzz crate depends on goes into `OMITTED` in " + "check_fuzz_patches_mirror_root instead.", file=sys.stderr, ) return success diff --git a/test/cargo-fuzz/Cargo.toml b/test/cargo-fuzz/Cargo.toml index 6ad8f4b6bea90..b4e5f7d560af1 100644 --- a/test/cargo-fuzz/Cargo.toml +++ b/test/cargo-fuzz/Cargo.toml @@ -10,13 +10,12 @@ # per-crate duplication. Each fuzz crate points here via # `package.workspace = "../../../test/cargo-fuzz"`. # -# `[patch.crates-io]` below is the SUBSET of the root `Cargo.toml`'s patches that -# the fuzz crates' dependency graph actually uses (root entries with no consumer -# here, currently `duckdb` and `postgres_array`, are omitted to avoid cargo's -# "patch was not used in the crate graph" warnings). Every entry that IS present -# must match the root's rev/version verbatim, which `bin/lint-cargo` enforces. -# If a fuzz crate gains a dependency that needs another patched crate, copy that -# entry from the root. +# `[patch.crates-io]` below mirrors the root `Cargo.toml`'s, minus the root +# entries that no fuzz crate depends on (they would only trigger cargo's "patch +# was not used in the crate graph" warning). `bin/lint-cargo` lists those +# omissions and enforces that every other root entry is present here verbatim. +# If a fuzz crate gains a dependency on an omitted crate, copy its entry from the +# root and remove it from that list. [workspace] resolver = "2" @@ -49,6 +48,10 @@ postgres-replication = { git = "https://github.com/MaterializeInc/rust-postgres" postgres-types = { git = "https://github.com/MaterializeInc/rust-postgres" } postgres-openssl = { git = "https://github.com/MaterializeInc/rust-postgres" } +# Upstream chrono-tz is unmaintained and stops at tzdata 2025b. The fork's +# `mz_changes` branch carries current IANA data only, no code changes. +chrono-tz = { git = "https://github.com/MaterializeInc/chrono-tz.git", rev = "5999cdd1b971694f834ec3467d11ef9147fc297c" } + # Waiting on https://github.com/MaterializeInc/serde-value/pull/35. serde-value = { git = "https://github.com/MaterializeInc/serde-value.git" }