diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 826d0e09..a68f2c80 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,10 +12,11 @@ concurrency: cancel-in-progress: ${{ github.event_name == 'pull_request' }} # The vendored C/C++ oracles (libaom, dav1d, libavif, libheif, exiv2, libjpeg-turbo, the DNG SDK, -# libjxl, …) dominate CI: they are workspace members under `tooling/`, and Swatinem/rust-cache -# deliberately drops workspace crates — including their build-script OUT_DIRs, where every static -# archive lives — so before sccache each heavy job rebuilt all fourteen from scratch (~9 min, paid -# twice per PR). +# libjxl, …) dominate CI: they are local path crates — the `tooling/` oracles are +# `[workspace].exclude` entries reached as path dev-dependencies, and libjxl builds inside the +# member `gamut-jxl-sys` — and Swatinem/rust-cache deliberately drops local crates, including +# their build-script OUT_DIRs where every static archive lives, so before sccache each heavy job +# rebuilt all fourteen from scratch (~9 min, paid twice per PR). # # sccache is the right tool because it is content-addressed on preprocessed source + compiler + # flags: a `third_party/` submodule bump changes the content, changes the hash, and misses. There diff --git a/AGENTS.md b/AGENTS.md index ea794785..ab907eda 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -161,6 +161,13 @@ chokepoint. So a native build failure is **not** explained by the invoking shell settings — do not "fix" it by overriding `CC`/`CXX` per command. `GAMUT_BUILD_KEEP_ENV=1` opts out (and confirms a suspected env interaction is real). +A build-script failure that is **not** a compile error — cargo reporting that it could not run +the build script itself — is usually a lost owner-execute bit under `target/debug/build/`, not a +code problem. The damaged mode is `-rw-rwx---`: the group keeps `x`, so `ls` output looks +unremarkable, but cargo runs as the owner and the owner class is what the kernel checks. Recover +with `chmod -R u+x target/debug/build/` and re-run. The cause is environmental and outside this +repo — nothing here sets file modes — so do not work around it by editing build scripts. + ## Conventions - All `pub` items need doc comments. Mark fallible/owning return types `#[must_use]` where diff --git a/mise.toml b/mise.toml index 063ff065..d3ad33a7 100644 --- a/mise.toml +++ b/mise.toml @@ -46,11 +46,41 @@ run = "rustup component add rustfmt --toolchain nightly 2>/dev/null || rustup to description = "Format code (nightly rustfmt; merge-resilient imports)" depends = ["ensure-nightly-rustfmt"] run = "cargo +nightly fmt --all" +depends_post = ["fmt-tooling"] [tasks.fmt-check] description = "Check formatting without modifying (nightly rustfmt)" depends = ["ensure-nightly-rustfmt"] run = "cargo +nightly fmt --all --check" +depends_post = ["fmt-tooling-check"] + +# `cargo fmt --all` formats workspace *members*, and `members = ["crates/*"]` — so no crate under +# `tooling/` has ever been fmt-gated. This is wider than the compile gap `check-dng-real` closes: +# that one is unique to `gamut-dng-real-conformance`, the only excluded crate nothing depends on, +# whereas rustfmt is blind to all fifteen — a path dev-dependency edge makes a crate reachable to +# `clippy --workspace --all-targets`, but does nothing for `fmt --all`. Hung off `fmt`/`fmt-check` +# rather than exposed as a separate gate so the hooks (hk.pkl) and CI inherit it with no second +# call site to keep in sync. rustfmt compiles nothing, so iterating the manifests costs seconds. +# The glob tracks `[workspace].exclude`; a `tooling/` crate promoted to a member would just be +# formatted twice, which is idempotent. +# +# Deliberately *not* `--all` on each manifest: that flag means "all packages, and also their local +# path-based dependencies", so `gamut-dng-real-conformance` alone drags the whole `crates/` tree +# back in — 483 of the 518 files such a loop reaches, every one already covered by the `fmt --all` +# above — and reports one drifting file once per dependent crate that can see it, up to 18 times +# for a single file. Iterating the manifests bare visits each crate's own targets (lib, examples, +# tests) exactly once: the same `tooling/` coverage — set-identical, all 35 files across all 15 +# crates — for 35 file-visits instead of 1131. +[tasks.fmt-tooling] +description = "Format the workspace-excluded tooling crates (`fmt --all` stops at the workspace boundary)" +depends = ["ensure-nightly-rustfmt"] +run = "for m in tooling/*/Cargo.toml; do cargo +nightly fmt --manifest-path \"$m\"; done" + +[tasks.fmt-tooling-check] +description = "Check formatting of the workspace-excluded tooling crates" +depends = ["ensure-nightly-rustfmt"] +# Deliberately does not stop at the first drifting crate, so one run names every one of them. +run = "fail=0; for m in tooling/*/Cargo.toml; do cargo +nightly fmt --manifest-path \"$m\" --check || fail=1; done; exit $fail" [tasks.lint] description = "Lint with Clippy (warnings as errors)" diff --git a/tooling/gamut-dng-real-conformance/examples/gaps.rs b/tooling/gamut-dng-real-conformance/examples/gaps.rs index a0b3f449..be2591ec 100644 --- a/tooling/gamut-dng-real-conformance/examples/gaps.rs +++ b/tooling/gamut-dng-real-conformance/examples/gaps.rs @@ -57,7 +57,10 @@ fn main() { println!(" preceded by: {:?}", before.map(|s| s.kind)); println!(" followed by: {:?}", after.map(|s| s.kind)); let peek_end = (end as usize).min(start as usize + 24); - println!(" bytes : {:02x?}", &data[start as usize..peek_end]); + println!( + " bytes : {:02x?}", + &data[start as usize..peek_end] + ); if let Ok(text) = std::str::from_utf8(&data[start as usize..peek_end]) && text.chars().all(|c| c.is_ascii_graphic() || c == '\0') { diff --git a/tooling/gamut-dng-real-conformance/examples/probe.rs b/tooling/gamut-dng-real-conformance/examples/probe.rs index fb51f82c..db2c8072 100644 --- a/tooling/gamut-dng-real-conformance/examples/probe.rs +++ b/tooling/gamut-dng-real-conformance/examples/probe.rs @@ -124,13 +124,20 @@ fn report_decode(data: &[u8]) { SubImageData::Undecoded { compression, chunks, - } => format!("Undecoded(compression={compression}, {} chunks)", chunks.len()), + } => format!( + "Undecoded(compression={compression}, {} chunks)", + chunks.len() + ), SubImageData::Decoded(v) => format!("Decoded({} samples)", v.len()), _ => "Other".to_string(), }; println!( " kind={:?} {}x{} photometric={} bits={} spp={} -> {payload}", - s.kind, s.dimensions.width, s.dimensions.height, s.photometric, s.bits_per_sample, + s.kind, + s.dimensions.width, + s.dimensions.height, + s.photometric, + s.bits_per_sample, s.samples_per_pixel, ); } diff --git a/tooling/gamut-dng-real-conformance/src/lib.rs b/tooling/gamut-dng-real-conformance/src/lib.rs index 05f98b02..b1e1d76c 100644 --- a/tooling/gamut-dng-real-conformance/src/lib.rs +++ b/tooling/gamut-dng-real-conformance/src/lib.rs @@ -114,8 +114,8 @@ pub fn corpus_dir() -> PathBuf { #[must_use] pub fn manifest() -> Manifest { let path = corpus_dir().join("MANIFEST.toml"); - let text = std::fs::read_to_string(&path) - .unwrap_or_else(|e| panic!("read {}: {e}", path.display())); + let text = + std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display())); toml::from_str(&text).unwrap_or_else(|e| panic!("parse {}: {e}", path.display())) } @@ -125,6 +125,10 @@ pub fn manifest() -> Manifest { /// from the workspace, and the hash is needed for exactly one purpose. #[must_use] pub fn sha256(data: &[u8]) -> [u8; 32] { + // FIPS 180-4 round constants and initial hash values, laid out in the spec's own rows. + // Skipped because every element is 11 chars, one over rustfmt's + // `short_array_element_width_threshold`, which would otherwise break them one per line. + #[rustfmt::skip] const K: [u32; 64] = [ 0x428a_2f98, 0x7137_4491, 0xb5c0_fbcf, 0xe9b5_dba5, 0x3956_c25b, 0x59f1_11f1, 0x923f_82a4, 0xab1c_5ed5, 0xd807_aa98, 0x1283_5b01, 0x2431_85be, 0x550c_7dc3, 0x72be_5d74, 0x80de_b1fe, @@ -137,6 +141,7 @@ pub fn sha256(data: &[u8]) -> [u8; 32] { 0x748f_82ee, 0x78a5_636f, 0x84c8_7814, 0x8cc7_0208, 0x90be_fffa, 0xa450_6ceb, 0xbef9_a3f7, 0xc671_78f2, ]; + #[rustfmt::skip] let mut h: [u32; 8] = [ 0x6a09_e667, 0xbb67_ae85, 0x3c6e_f372, 0xa54f_f53a, 0x510e_527f, 0x9b05_688c, 0x1f83_d9ab, 0x5be0_cd19, diff --git a/tooling/gamut-dng-real-conformance/tests/real_corpus.rs b/tooling/gamut-dng-real-conformance/tests/real_corpus.rs index df281289..2fca4986 100644 --- a/tooling/gamut-dng-real-conformance/tests/real_corpus.rs +++ b/tooling/gamut-dng-real-conformance/tests/real_corpus.rs @@ -220,8 +220,13 @@ fn every_real_file_survives_a_preserving_rewrite() { } Err(e) => panic!("{name}: open: {e}"), }; - assert!(expect.rewritable, "{name}: opened, but manifest says it must not"); - let out = rewrite.write().unwrap_or_else(|e| panic!("{name}: write: {e}")); + assert!( + expect.rewritable, + "{name}: opened, but manifest says it must not" + ); + let out = rewrite + .write() + .unwrap_or_else(|e| panic!("{name}: write: {e}")); assert_eq!( maker_note_name(out.maker_note),