From 82dfab3dfff3c4e52e7240483af0ae9c34fc5827 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Thu, 8 Oct 2026 20:32:29 +0100 Subject: [PATCH] Fix CI: pin Rust 1.99, newer clippy lints, Windows/macOS test portability, NetBSD libexecinfo for cross --- Cross.toml | 11 +++++++++++ DEVELOPMENT.md | 7 ++++--- rust-toolchain.toml | 4 ++++ src/app.rs | 2 +- src/inventory/mod.rs | 2 +- src/scan.rs | 9 ++++++--- src/snapshot/mod.rs | 18 ++++++++++++++---- src/tui/side.rs | 2 +- src/tui/tests.rs | 2 ++ src/tui/views.rs | 8 +++----- 10 files changed, 47 insertions(+), 18 deletions(-) create mode 100644 Cross.toml create mode 100644 rust-toolchain.toml diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 0000000..983c390 --- /dev/null +++ b/Cross.toml @@ -0,0 +1,11 @@ +# The cross image for NetBSD lacks libexecinfo, which Rust's standard library +# links on NetBSD. Take it (and libelf, which it needs) from the official +# NetBSD 9.2 base set, matching the image's libc, checked against the +# release's published SHA512. +[target.x86_64-unknown-netbsd] +pre-build = [ + "curl -fsSL -o /tmp/base.tar.xz https://archive.netbsd.org/pub/NetBSD-archive/NetBSD-9.2/amd64/binary/sets/base.tar.xz", + "echo '961511ec63f4803529aeb057632e2b8cf87e73bfdfed445d7521b607f573245359163b9f6b6a158fa80de7a994cf3727af384ec42ac8d9ccdd59a87197a41b55 /tmp/base.tar.xz' | sha512sum -c", + "tar -xJf /tmp/base.tar.xz -C /usr/local/x86_64-unknown-netbsd --strip-components=2 ./usr/lib/libexecinfo.so ./usr/lib/libexecinfo.so.0 ./usr/lib/libexecinfo.so.0.0 ./usr/lib/libelf.so ./usr/lib/libelf.so.2 ./usr/lib/libelf.so.2.0", + "rm /tmp/base.tar.xz", +] diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 2a397f2..5c47d4e 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,7 +1,8 @@ # Development -Requires a stable Rust toolchain (`rustup`); the dependencies are pinned in -`Cargo.lock`. No C toolchain beyond what Rust itself needs. +Requires `rustup`: `rust-toolchain.toml` pins the compiler (so local builds +match CI), and `Cargo.lock` pins the dependencies. No C toolchain beyond what +Rust itself needs. ```sh make build # target/release/mapsize @@ -27,7 +28,7 @@ binaries under qemu. | Linux | amd64, arm64, 386, arm (v7), riscv64 | `*-unknown-linux-musl` (static) | cargo-zigbuild + zig | | Windows | amd64, arm64 | `x86_64-pc-windows-gnu`, `aarch64-pc-windows-gnullvm` | cargo-zigbuild + zig | | macOS | amd64, arm64 | `*-apple-darwin` | cargo on a Mac (or zigbuild) | -| FreeBSD, NetBSD | amd64 | `x86_64-unknown-{freebsd,netbsd}` | cross + Docker | +| FreeBSD, NetBSD | amd64 | `x86_64-unknown-{freebsd,netbsd}` | cross + Docker (`Cross.toml` adds NetBSD's libexecinfo) | Platform differences live in `src/platform.rs` (and `cfg` blocks where the terminal code needs them); nothing else switches on the OS. diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..5a341ac --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.99.0" +components = ["rustfmt", "clippy"] +profile = "minimal" diff --git a/src/app.rs b/src/app.rs index 21dcd65..380a123 100644 --- a/src/app.rs +++ b/src/app.rs @@ -110,7 +110,7 @@ fn interrupt_cancel() -> Cancel { } } let c = CANCEL.get_or_init(Cancel::new).clone(); - unsafe { libc::signal(libc::SIGINT, on_sigint as libc::sighandler_t) }; + unsafe { libc::signal(libc::SIGINT, on_sigint as *const () as libc::sighandler_t) }; c } diff --git a/src/inventory/mod.rs b/src/inventory/mod.rs index eff8b1d..52c7eaa 100644 --- a/src/inventory/mod.rs +++ b/src/inventory/mod.rs @@ -492,7 +492,7 @@ mod tests { assert_eq!(top.len(), 3); assert_eq!(tr.node(top[0]).size, 99); assert_eq!(tr.node(top[2]).size, 97); - assert_eq!(tr.path_string(top[0]), "/r/d/f"); + assert_eq!(tr.path_string(top[0]).replace('\\', "/"), "/r/d/f"); } #[test] diff --git a/src/scan.rs b/src/scan.rs index 760493d..b9f7ceb 100644 --- a/src/scan.rs +++ b/src/scan.rs @@ -916,15 +916,18 @@ pub(crate) mod tests { fn hostile_names_preserved() { use std::os::unix::ffi::OsStrExt; let root = TempDir::new("names"); - let names: [&[u8]; 6] = [ + #[allow(unused_mut)] + let mut names: Vec<&[u8]> = vec![ "日本語.txt".as_bytes(), "emoji-🎉.png".as_bytes(), b"esc\x1b[31mred", b"nl\nname", b"sp ace", - b"bad\xffutf8", ]; - for n in names { + // APFS rejects names that are not valid UTF-8. + #[cfg(not(target_os = "macos"))] + names.push(b"bad\xffutf8"); + for &n in &names { std::fs::write(root.0.join(std::ffi::OsStr::from_bytes(n)), b"x").unwrap(); } let t = run_scan( diff --git a/src/snapshot/mod.rs b/src/snapshot/mod.rs index 3c4efab..2b8212d 100644 --- a/src/snapshot/mod.rs +++ b/src/snapshot/mod.rs @@ -686,8 +686,11 @@ impl JsonTime { if self.secs >= 0 { UNIX_EPOCH + Duration::new(self.secs as u64, self.nanos) } else { - UNIX_EPOCH - Duration::from_secs(self.secs.unsigned_abs()) - + Duration::from_nanos(self.nanos as u64) + // Windows cannot represent times before 1601 (such as the zero + // time, year 1); those clamp to the Unix epoch. + UNIX_EPOCH + .checked_sub(Duration::from_secs(self.secs.unsigned_abs())) + .map_or(UNIX_EPOCH, |t| t + Duration::from_nanos(self.nanos as u64)) } } @@ -897,7 +900,10 @@ mod tests { assert_eq!(d.removed.len(), 1); assert_eq!(&d.old.node(d.removed[0]).name[..], b"b"); let ch = d.changes(&nw, 10, al); - let paths: HashMap<&str, Status> = ch.iter().map(|c| (c.path.as_str(), c.status)).collect(); + let paths: HashMap = ch + .iter() + .map(|c| (c.path.replace('\\', "/"), c.status)) + .collect(); assert_eq!(paths.get("/srv/a/x.iso"), Some(&Status::Grew), "{ch:?}"); assert_eq!(paths.get("/srv/b"), Some(&Status::Removed), "{ch:?}"); assert_eq!(paths.get("/srv/c"), Some(&Status::Added), "{ch:?}"); @@ -970,6 +976,7 @@ mod tests { Some(JsonTime::default()) ); assert_eq!(JsonTime::default().format(), "0001-01-01T00:00:00Z"); + #[cfg(not(windows))] assert_eq!( JsonTime::from_system(JsonTime::default().to_system()), JsonTime::default() @@ -1010,7 +1017,10 @@ mod tests { .unwrap(); assert_eq!(t.node(iso).size, 10000); assert_eq!(t.ext_name(t.node(iso)), "iso"); - assert!(t.path_string(iso).ends_with("/tree/a/x.iso")); + assert!(t + .path_string(iso) + .replace('\\', "/") + .ends_with("/tree/a/x.iso")); // And it survives a save/load round trip unchanged. let again = load(&saved(&t)[..]).unwrap(); assert_eq!( diff --git a/src/tui/side.rs b/src/tui/side.rs index 3027dfc..7a0150c 100644 --- a/src/tui/side.rs +++ b/src/tui/side.rs @@ -92,7 +92,7 @@ impl Model { .map(|(i, &v)| (Category::from_u8(i as u8), v)) .collect(); let total: i64 = cats.iter().map(|c| c.1).sum(); - cats.sort_by(|a, b| b.1.cmp(&a.1)); + cats.sort_by_key(|c| std::cmp::Reverse(c.1)); for &(c, v) in &cats { if y >= end - 8 { break; diff --git a/src/tui/tests.rs b/src/tui/tests.rs index fa762a8..cabc280 100644 --- a/src/tui/tests.rs +++ b/src/tui/tests.rs @@ -590,6 +590,8 @@ fn nesting_reaches_deep_media() { // Headless, o shows the selected folder in the map instead of launching a // file manager. +// macOS and Windows always have a file manager, so are never headless. +#[cfg(not(any(target_os = "macos", windows)))] #[test] fn reveal_headless_zooms_map() { std::env::set_var("DISPLAY", ""); diff --git a/src/tui/views.rs b/src/tui/views.rs index 34a9e14..d526dd7 100644 --- a/src/tui/views.rs +++ b/src/tui/views.rs @@ -462,11 +462,9 @@ impl TopView { val.insert(id, n.size - n.alloc); } } - TopMode::Hardlinks => { - if n.has(FLAG_HARDLINKED) { - tk.offer(id, n.size); - val.insert(id, n.size); - } + TopMode::Hardlinks if n.has(FLAG_HARDLINKED) => { + tk.offer(id, n.size); + val.insert(id, n.size); } _ => {} }