Repository navigation
ci: unify dependency features with a cargo-hakari workspace-hack - #6985
Conversation
Quest-Claim: c9386df8a93a7f8a
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve conflicts (moq-flate removed, web-transport-trait dev-deps dropped, .taplo.toml .tmp exclude), regenerate the hack, and give the new bench-harness crate its edge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review.
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
kio-macros landed on main in #7148 after this branch's last merge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Merging per the maintainer's /quest-merge pick in a /quest-complete session.
(Written by Claude Opus 5.5) |
|
quest land removed this pull request from the queue: the batch check failed (exit 123). @moq/e2ee:test | subscribe close: id=0 broadcast=qNMmsuC9j3gTo1Ao-0SEIQ track=nMIPmJanfNGw5QO79jpd52ZosKO39FsH35JgKRXIeRAxgmAWKrcsRH0GMwY
@moq/e2ee:test | https://localhost:4443/test negotiated ALPN: moqt-18
@moq/e2ee:test | subscribe_namespace written: requestId=2
@moq/e2ee:test | announced: broadcast=qNMmsuC9j3gTo1Ao-0SEIQ active=true
@moq/e2ee:test | subscribe start: id=4 broadcast=qNMmsuC9j3gTo1Ao-0SEIQ track=foFGqEdPx7V95J31I8S5JTT4IIwCYsbg-bAm1JT12smZ4dSQ1sWtBDfR1tY
@moq/e2ee:test | subscribe written: id=4 broadcast=qNMmsuC9j3gTo1Ao-0SEIQ track=foFGqEdPx7V95J31I8S5JTT4IIwCYsbg-bAm1JT12smZ4dSQ1sWtBDfR1tY
@moq/e2ee:test | publish ok: broadcast=qNMmsuC9j3gTo1Ao-0SEIQ track=foFGqEdPx7V95J31I8S5JTT4IIwCYsbg-bAm1JT12smZ4dSQ1sWtBDfR1tY
@moq/e2ee:test | subscribe ok: id=4 broadcast=qNMmsuC9j3gTo1Ao-0SEIQ track=foFGqEdPx7V95J31I8S5JTT4IIwCYsbg-bAm1JT12smZ4dSQ1sWtBDfR1tY
@moq/e2ee:test | joining fetch: id=6 group=0
@moq/e2ee:test | join head: group=0 objects=1
@moq/e2ee:test | publish done: broadcast=qNMmsuC9j3gTo1Ao-0SEIQ track=foFGqEdPx7V95J31I8S5JTT4IIwCYsbg-bAm1JT12smZ4dSQ1sWtBDfR1tY
@moq/e2ee:test | announced: broadcast=qNMmsuC9j3gTo1Ao-0SEIQ active=false
@moq/e2ee:test | subscribe close: id=4 broadcast=qNMmsuC9j3gTo1Ao-0SEIQ track=foFGqEdPx7V95J31I8S5JTT4IIwCYsbg-bAm1JT12smZ4dSQ1sWtBDfR1tY
@moq/e2ee:test | subscribe_namespace stream error: session closed
@moq/e2ee:test |
@moq/e2ee:test | 60 pass
@moq/e2ee:test | 0 fail
@moq/e2ee:test | 1641 expect() calls
@moq/e2ee:test | Ran 60 tests across 3 files. [528.00ms]
@moq/e2ee:test | Done in 542ms
@moq/hang:test | bun test v1.4.2 (744846f84)
@moq/hang:test |
@moq/hang:test | src/container/consumer.backpressure.test.ts:
@moq/hang:test | skipping missing groups: track=test 2 -> 3
@moq/hang:test |
@moq/hang:test | src/container/consumer.holes.test.ts:
@moq/hang:test | skipping missing groups: track=data 1 -> 2
@moq/hang:test | skipping missing groups: track=data 1 -> 2
@moq/hang:test | skipping missing groups: track=data 3 -> 4
@moq/hang:test | skipping missing groups: track=data 1 -> 2
@moq/hang:test | skipping missing groups: track=data 3 -> 4
@moq/hang:test | skipping missing groups: track=data 5 -> 6
@moq/hang:test | skipping missing groups: track=data 1 -> 2
@moq/hang:test | skipping missing groups: track=data 3 -> 4
@moq/hang:test | skipping missing groups: track=data 1 -> 3
@moq/hang:test | skipping missing groups: track=data 4 -> 5
@moq/hang:test |
@moq/hang:test | src/container/consumer.test.ts:
@moq/hang:test | skipping missing groups: track=test 1 -> 2
@moq/hang:test | skipping slow groups: track=test 0 -> 1 count=1
@moq/hang:test | skipping slow groups: track=test 1 -> 2 count=1
@moq/hang:test | skipping slow groups: track=test 0 -> 2 count=1
@moq/hang:test | skipping slow groups: track=test 0 -> 3 count=1
@moq/hang:test | skipping slow groups: track=test 2 -> 3 count=1
@moq/hang:test | skipping slow groups: track=test 0 -> 1 count=1
@moq/hang:test | skipping slow groups: track=test 0 -> 1 count=1
@moq/hang:test | skipping slow groups: track=test 0 -> 1 count=1
@moq/hang:test | skipping slow groups: track=test 0 -> 1 count=1
@moq/hang:test | skipping slow groups: track=test 0 -> 1 count=1
@moq/hang:test | skipping slow groups: track=test 1 -> 3 count=1
@moq/hang:test | skipping covered group: 0 -> 1
@moq/hang:test | skipping missing groups: track=test 1001 -> 2000
@moq/hang:test | skipping missing groups: track=test 1001 -> 2000
@moq/hang:test | skipping slow groups: track=test 0 -> 2 count=1
@moq/hang:test | skipping slow groups: track=test 0 -> 3 count=1
@moq/hang:test | skipping covered group: 0 -> 1
@moq/hang:test | skipping slow groups: track=test 1 -> 2 count=1
@moq/hang:test | skipping missing groups: track=test 1 -> 2
@moq/hang:test | skipping missing groups: track=test 1 -> 2
@moq/hang:test | skipping missing groups: track=test 1 -> 2
@moq/hang:test |
@moq/hang:test | src/util/libav.test.ts:
@moq/hang:test | using libav WebCodecs polyfill; performance may be degraded
@moq/hang:test |
@moq/hang:test | 521 pass
@moq/hang:test | 0 fail
@moq/hang:test | 20822 expect() calls
@moq/hang:test | Ran 521 tests across 29 files. [3.74s]
@moq/hang:test | Done in 3.77s
@moq/interop-browser:test | bun test v1.4.2 (744846f84)
@moq/interop-browser:test |
@moq/interop-browser:test | 25 pass
@moq/interop-browser:test | 0 fail
@moq/interop-browser:test | 184 expect() calls
@moq/interop-browser:test | Ran 25 tests across 7 files. [3.50s]
@moq/interop-browser:test | Done in 3.53s
@moq/json:test | bun test v1.4.2 (744846f84)
error: recipe `test-changed` failed on line 54 with exit code 143
ci: check python failed
ci: seconds per module
387 check py
35 test py
288 check rs
370 test rs
687 check c
347 check cpp
23 check obs_compile
183 check js
36 test js (cancelled)
178 check kt
79 check flake
2 check swift
10 check go (cancelled)
1 check drill
2 check quest
2 check obs
1 check ts_paths
0 check python (failed)
877 wall time
error: recipe `ci` failed on line 107 with exit code 123
. |
|
codex / Codex land fixer: exec
/bin/bash -lc "tail -4 /tmp/tmp.QE8ljpll2u/rs.log; rg -n '"'^ *FAIL|Summary'"' /tmp/tmp.QE8ljpll2u/rs.log | tail -4" in /work
succeeded in 0ms:
PASS [ 0.752s] ( 4196/10900) moq-mux container::ts::recovery_test::a_skip_across_a_resolution_change_takes_the_new_parameters
PASS [ 0.658s] ( 4197/10900) moq-mux container::ts::recovery_test::a_skip_across_the_wrap_keeps_the_clock
PASS [ 0.488s] ( 4198/10900) moq-mux container::ts::recovery_test::a_stall_drops_what_it_made_late
PASS [ 0.444s] ( 4199/10900) moq-mux container::ts::recovery_test::a_stalled_track_leaves_the_others_running
codex
The GStreamer tests now pass. More than 4,000 tests have completed with no failures in this run.
exec
/bin/bash -lc "tail -3 /tmp/tmp.QE8ljpll2u/rs.log; rg -n '"'^ *FAIL|Summary'"' /tmp/tmp.QE8ljpll2u/rs.log | tail -4" in /work
succeeded in 0ms:
PASS [ 0.357s] ( 7409/10900) moq-net::integration broadcast_close::close_ends_a_local_consumer
PASS [ 0.355s] ( 7410/10900) moq-net::integration broadcast_close::close_ends_a_remote_consumer
PASS [ 0.316s] ( 7411/10900) moq-net::integration catalog_handover::fetch_lite_06::fetch_right_after_leave
exec
/bin/bash -lc 'tail -4 /tmp/tmp.QE8ljpll2u/rs.log; tail -5 /tmp/quest-6985-check.log' in /work
succeeded in 0ms:
PASS [ 0.006s] ( 8686/10900) moq-quic transport_parameters::test::read_reset_stream_at_with_value
PASS [ 0.008s] ( 8687/10900) moq-quic transport_parameters::test::reserved_transport_parameter_generate_reserved_id
PASS [ 0.008s] ( 8688/10900) moq-quic transport_parameters::test::read_semantic_validation
PASS [ 0.006s] ( 8689/10900) moq-quic transport_parameters::test::reserved_transport_parameter_ignored_when_read
==> check python
All checks passed!
164 files already formatted
ci: check rs passed in 26s
ci: test rs started
codex
More than 8,600 tests have completed with no failures. I’ll write the result file once the suite finishes.
exec
/bin/bash -lc "tail -3 /tmp/tmp.QE8ljpll2u/rs.log; rg -n '"'^ *FAIL|Summary'"' /tmp/tmp.QE8ljpll2u/rs.log | tail -4" in /work
succeeded in 0ms:
PASS [ 0.084s] ( 9022/10900) moq-relay::integration cluster_close::a_relay_shutdown_closes_its_cluster_dials
PASS [ 0.294s] ( 9023/10900) moq-relay::integration auth_lifetime::websocket_refusal_is_in_band
PASS [ 0.483s] ( 9024/10900) moq-relay::integration auth_lifetime::the_end_carries_duration_and_bytes
exec
/bin/bash -lc 'tail -3 /tmp/tmp.QE8ljpll2u/rs.log; tail -5 /tmp/quest-6985-check.log' in /work
succeeded in 0ms:
PASS [ 0.092s] (10512/10900) moq-uring::integration web::a_clean_finish_closes_a_stream
PASS [ 0.089s] (10513/10900) moq-uring::integration web::a_dropped_session_delivers_its_close
PASS [ 0.091s] (10514/10900) moq-uring::integration web::a_dropped_stream_carries_a_webtransport_code
==> check python
All checks passed!
164 files already formatted
ci: check rs passed in 26s
ci: test rs started
succeeded in 273527ms:
exec
/bin/bash -lc 'tail -8 /tmp/quest-6985-check.log; tail -3 /tmp/tmp.QE8ljpll2u/rs.log 2>/dev/null' in /work
exited 1 in 0ms:
PASS [ 8.242s] (10900/10900) moq-video encode::producer::tests::clock::hls_records_keep_the_live_timestamps
────────────
Summary [ 243.804s] 10900 tests run: 10900 passed, 124 skipped
ci: seconds per module
26 check rs
247 test rs
0 check python
274 wall time
codex
Validation passed: 10,900 Rust tests passed, with 124 skipped, and the Rust and Python checks are green. I’m writing the `fixed` result with the branch head and validation commands.
exec
/bin/bash -lc "python3 - <<'PY'
import json, subprocess
head=subprocess.check_output(['git','rev-parse','pr-6985'],text=True).strip()
result=[{'number':6985,'action':'fixed','head':head,'reason':'Merged base and applied Ruff import/indentation formatting to sh/rs/hakari.py without behavior changes; passed ALLOW_HOST_TOOLCHAIN=1 LOCAL_SUPERVISED=1 CHECK_MODULES=\"python rs\" CI_STRICT=1 NEXTEST_PROFILE=ci sh/dispatch.sh ci base with cached native-library/include/GStreamer-plugin environment (10900 tests passed, 124 skipped); also passed CHECK_MODULES=\"toml justfile quest\" sh/dispatch.sh check base, cargo hakari generate --diff, and python3 sh/rs/hakari.py.'}]
with open('quest-land-result.json','w') as f:
json.dump(result,f,indent=2)
f.write('\\n')
assert len(head)==40
subprocess.run(['git','merge-base','--is-ancestor','2a0a295e7a26ecb767575069175cf28bd3bfd0ec',head],check=True)
PY
git status --short; git diff --check; git log -2 --format='%H %s'" in /work
succeeded in 0ms:
?? quest-land-result.json
71ae34f936ffc5bd39a8c6793985e4e7edd366a9 style: format hakari dependency checker
d3976831010f9f3be22b4666161c08716b5d70a3 Merge branch 'base' into pr-6985
codex
Fixed PR #6985 on `pr-6985` at `71ae34f936ffc5bd39a8c6793985e4e7edd366a9`: merged base and formatted the Python checker without behavior changes.
Validation passed: 10,900 tests; 124 skipped.
Wrote [quest-land-result.json](/work/quest-land-result.json) and left it uncommitted.
tokens used
98,870
Fixed PR #6985 on `pr-6985` at `71ae34f936ffc5bd39a8c6793985e4e7edd366a9`: merged base and formatted the Python checker without behavior changes.
Validation passed: 10,900 tests; 124 skipped.
Wrote [quest-land-result.json](/work/quest-land-result.json) and left it uncommitted.
fixer result written
(written by codex / Codex) |
|
codex / Codex land fixer note:
(written by codex / Codex) |
|
codex / Codex land fixer: Conflicts with the base: flake.nix (written by codex / Codex) |
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: fb988e87c7b111f2a432e05d750b415f2cf29fcc, comparing the update against my previous review while accounting for merged main changes.
Fixed: the prior Android P2 is addressed in .github/workflows/android.yml: the pinned hakari tool disables the hack and re-resolves Cargo.lock before the Android cross-check, removing the unprovisioned host PipeWire dependency path. The local recipe documents the same prerequisite.
No new actionable bugs found. Keeping the native cache optimization while disabling it at the cross-build boundary is a focused, simpler fix than provisioning unrelated media libraries on the Android runner. The checker changes are formatting-only.
Verification: static GitHub inspection of the workflow, recipes, dependency refresh, and checker, with independent validation of the fix. I did not run Android builds, tests, hakari regeneration, or performance measurements.
(Written by OpenAI)
#6985 merged after the off-cycle run's base, so point feature-unification work at the still-open hakari quest and flag which baselines it can shift. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main's #7468 added a parse-amplification measurement to quest/m2/media-fuzz.md, which this branch deletes. The measurement found three fMP4 cases that need fixing, so the quest now keeps that remaining work with the numbers instead of being deleted, and its m2 and import-metadata-policy links stay. - .config/hakari.toml: exclude moq-mux-fuzz and moq-rtmp-fuzz from the workspace-hack (#6985), like moq-net-fuzz and moq-quic-fuzz. - sps-overflow.md: H.264 now parses with h264-reader (#7398). - mp4-atom-bump.md: its prose wait moves into a ## Waiting section. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
PR-shaped Rust builds compile dependencies repeatedly when their enabled features differ, reducing reuse in the shared mbx cache.
Approach
Adds a cargo-hakari workspace-hack,
rs/workspace-hack, so test, lint, and bench builds compile each third-party crate with one feature set whatever packages a job selects. mbx keys every compilation on its inputs, so before this a PR touchingmoq-videoand one touchingmoq-relaybuilt most of the dependency graph twice.rs/workspace-hack: generated bycargo hakari generateover the four CI host platforms (.config/hakari.toml). Not published.kio-macros(trybuild copies dev-dependencies into the project each UI case compiles in, so the edge madekio-macros::uibuild the whole hack there) and the fuzz crates.features.ymlrunscargo hakari disable && cargo update --workspacebeforejust rs features, so the feature-contract checks see each crate's own declarations rather than the hack's union.just rs lintfails when the hack is stale (cargo hakari generate --diff) or an edge is missing, misplaced, or versioned (sh/rs/hakari.py).just rs fixregenerates it.cargo sortskips the hack's manifest: hakari interleaves each platform'sdependenciesandbuild-dependencies,cargo sortwants them grouped, and neither tool can be configured to agree.cargo-hakarijoins the dev shell.Why a dev-dependency
Hakari's two supported setups for published workspaces both cost more here.
cargo hakari publishmust replacecargo publish, which release-plz runs itself. Option B publishes a stubworkspace-hackcrate to crates.io. A path-only dev-dependency needs neither, becausecargo publishstrips it. It also follows the existing rule that internal dev-dependencies are path-only. Dev-dependencies are not transitive, so the hack enters a build only through a selected package's own edge, and excluding a crate needs no closure. The cost is thatcargo hakari manage-depscannot maintain these edges (it writes normal dependencies only), sosh/rs/hakari.pychecks them instead. Library-only builds (doc, the FFIcargo build --release) also stay un-unified.Measurements
The land gate's Rust test build, on current
main(2026-11-01)The gate's
test rslane (sh/rs/select.sh test, a clippy-wrappednextestbuild) replayed compile-only (--no-run) for seven PR-shaped selections in order, each pair from one empty isolated mbx store and one target directory:mainat fcef1ea64b against this branch merged with it. CPU is the whole cargo process tree's user plus system time.After the first build, recompilations fall 74% (893 → 231) and CPU 37% (1,924 s → 1,204 s). The first cold build costs 8% more, because every crate now builds the union; the gate's shared store pays that once per lockfile change. Fewer distinct artifacts also matter on the land host, whose store evicted 7-15 GiB every batch at its 50 GiB cap.
Earlier runs, from this branch's first parent (fbeac8a9fd, 2026-10-28)
These predate dropping the gate's feature permutations, cargo-hack, wasm, and loom, so they measured a wider CI than today's.
Real mbx runs
Each job's recorded cargo invocations were replayed compile-only, in order, against one empty isolated mbx store. Each job started from an empty managed target (
mbx clean), as on a fresh runner. CPU is the whole process tree's (systemd-run --wait). "Misses" includes compilations mbx could not look up.select.sh testbuilds (relay, video, hang, ffi, net), then relay and ffiselect.sh lintrs uring,cli-export,multicast,quic-stress,audio-devices,render-swPer job, the queue's test builds after the first went from 255-801 hits to 638-885 hits. The relay lint and the warm test builds each spend less CPU. The ffi lint rose from 182 s to 294 s: its isolated
feature-shapesbuilds ofmoq-ffinow compile the whole hack.Cross-job model
Every compiling cargo invocation of 40 Linux jobs was recorded through a logging
cargoshim. That covers the gate over the whole workspace and six single-crate PR selections, its binding builds, and Nightly'sfeatures, tests matrix, and Rust bench jobs. Each invocation was expanded withcargo --unit-graph. Each unit is keyed the way its-C metadatais (package, target, mode, profile, features, platform, RUSTFLAGS, clippy wrapper, and its dependencies' keys, recursively) and weighted by its compile time from cold--timingsbuilds. "Unique CPU" is what one shared cache needs to compile once. Jobs only share within a runner pool (the queue's local store, or Nightly's R2 namespace).The last column is the cost of unifying. A single-crate Nightly job now also compiles hack crates it does not use. Examples are
rs cli-export(570 → 731 units) and the release-profile bench jobs (rust-relay485 → 1,079). That cost is paid once per lockfile change while the cache holds them, and it bought the Nightly jobs' sharing: their minority feature variants fell to 0-4 per job.Across all 40 jobs, a compilation that one job misses although another built the same crate at the same version, grouped by what differs:
--target, as the Dart FFI build passes)The remaining feature splits come from the excluded crates' own builds and from library-only builds.
Feature-contract checks
With the hack disabled as
features.ymldoes (cargo hakari disable && cargo update --workspace), a planted undeclared third-party feature in each guarded crate fails its check (rerun 2026-11-01 on this head):just rs quic-featuresindexmap::map::serde_seq(indexmap/serde)just rs net-featuresuuid::Uuid::new_v4()(uuid/v4)just rs tokio-featuresreqwest::Response::json(reqwest/json)just rs media-featurestokio::fs::read(tokio/fs)Publishing
cargo publish --dry-runpasses end to end (package, verify build) formoq-clock-macrosandmoq-pattern, which both carry the hack edge. A packagedmoq-jsonmanifest has no reference to it.release-plz release --dry-runstops at the same place on this branch and its parent:moq-clockneedsmoq-clock-macros, which a dry run never uploads.release-plz updatesucceeds and leaves the hack and its edges untouched.Impact
workspace-hackispublish = false, andcargo publishstrips the path-only dev-dependency, so published manifests are unchanged.workspace-hack = { path = "../workspace-hack" }under[dev-dependencies];just rs lintnames the line. A dependency change needsjust rs fix(orcargo hakari generate).Alternatives
A published stub crate or a publishing wrapper would add release machinery. The path-only dev-dependency preserves the existing release-plz flow. Android disables the hack instead of provisioning the host dev shell's media libraries.
Decisions
Settled with the maintainer on 2026-11-01, after the gate dropped feature permutations, cargo-hack, wasm, and loom (moq-dev/moq#7472 and others):
moq-quic,moq-net,moq-tokio,moq-video,moq-audio,moq-transcode). ✅ Include them, disable the hack infeatures.yml· Keep them excluded · Include them and drop the contract checkscargo hack checkpass this PR had added. ✅ Drop it · Keep it in NightlyMade without asking, since wasm and loom are gone:
moq-clock(excluded for its wasm32 tests) andkio(excluded for loom) take the edge too.Earlier decisions that still stand:
cargo hakari remove-depsbeforerelease-plz release).quest/m0/ci/hakari.md.Follow-ups
--target x86_64-unknown-linux-gnualone splits 400+ units from every other build on the host (inquest/m0/ci/hakari.md).quest/m0/ci/hakari.md).Checks
At the committed head, in the Nix dev shell:
just ci origin/main: 26 of 27 selected phases passed, including 11,322 Rust tests (126 skipped), 113 Python tests, JavaScript tests, C/C++, OBS compilation, Kotlin, Go, Dart, Swift checks, and repository tooling.check flakefailed while building the NixOS runner image:unshare: write failed /proc/self/uid_map: Operation not permitted. This is the local Nix builder's namespace restriction, not a source/test failure. The repository's non-strict namespace probe confirms that this host cannot run the VM check; the strict VM gate still needs a host that permits it.cargo hakari generate --diff,python3 sh/rs/hakari.py,cargo metadata --locked --no-deps --format-version 1,quest check --base origin/main --branch quest/m0/ci/hakari, andgit diff --checkpass.Review
cargo tree --locked --target aarch64-linux-android -p moq-video -i pipewire --edges normal,build,devreproduces the hack's host edge. Aftercargo hakari disableandcargo update --workspace, that graph contains neither PipeWire nor clang-sys. Full Android cross-compilation was not run locally because this host has no NDK.(Written by GPT-6.1 Sol)