From ab1f02e14b42100dc18bb2914158a15a5685029b Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Sun, 12 Apr 2026 14:16:38 +0000 Subject: [PATCH 01/47] skeleton --- Cargo.toml | 2 +- xtask/Cargo.toml | 9 +++++++++ xtask/src/lib.rs | 1 + xtask/src/main.rs | 1 + 4 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 xtask/Cargo.toml create mode 100644 xtask/src/lib.rs create mode 100644 xtask/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index c26429d..c147aa7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ trybuild = "1.0.116" autocfg = {workspace = true } [workspace] -members = ["tests/compilation"] +members = ["tests/compilation", "xtask"] [workspace.dependencies] try_v2 = { path = "."} diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 0000000..9f0dfe9 --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "try_v2_xtasks" +version = "0.0.1" +edition = "2024" +publish = false + +[dependencies] +exit_safely = "0.2.0" +try_v2 = {workspace = true} diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/xtask/src/lib.rs @@ -0,0 +1 @@ + diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 0000000..f328e4d --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1 @@ +fn main() {} From 4dc75553377d7ee3bc18af100799c38b78cebe16 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Sun, 12 Apr 2026 14:35:31 +0000 Subject: [PATCH 02/47] basic shape for main --- xtask/Cargo.toml | 3 ++- xtask/src/main.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 9f0dfe9..a1edffb 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -6,4 +6,5 @@ publish = false [dependencies] exit_safely = "0.2.0" -try_v2 = {workspace = true} +try_v2 = { workspace = true } +clap = { version = "4.6.0", features = ["derive"] } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index f328e4d..4c4930f 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1 +1,47 @@ -fn main() {} +#![feature(never_type)] +#![feature(try_trait_v2)] +#![feature(try_trait_v2_residual)] + +use std::process::Termination as _T; + +use clap::{Parser, Subcommand}; +use exit_safely::Termination; +use try_v2::{Try, Try_ConvertResult}; + +#[derive(Debug, Termination, Try, Try_ConvertResult)] +#[repr(u8)] +#[must_use] +enum Exit { + Ok(T) = 0, + InvocationError(Box) = 2, +} + +impl From for Exit { + fn from(e: clap::Error) -> Self { + Self::InvocationError(Box::new(e)) + } +} + +#[derive(Parser)] +#[command(version)] +struct XTask { + #[command(subcommand)] + command: Command, +} + +#[derive(Subcommand)] +enum Command { + /// git add if all is good + Add, +} + +fn main() -> Exit<()> { + let xtask = XTask::try_parse()?; + + match &xtask.command { + Command::Add => { + fmt()?; + add()?; + } + } +} From cdd462cd9b2bdd0f03c69634661f53a071c1abe5 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Sun, 12 Apr 2026 14:42:21 +0000 Subject: [PATCH 03/47] let main compile --- xtask/src/main.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 4c4930f..9421779 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -40,8 +40,9 @@ fn main() -> Exit<()> { match &xtask.command { Command::Add => { - fmt()?; - add()?; + todo!(); + // fmt()?; + // add()?; } } } From e806f853fe3e16bc794925d3ff282c30cad333e6 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Sun, 12 Apr 2026 14:56:24 +0000 Subject: [PATCH 04/47] skeleton how fmt could be tested --- xtask/Cargo.toml | 4 ++++ xtask/src/lib.rs | 1 - xtask/tests/fixture/Cargo.toml | 5 +++++ xtask/tests/fixture/src/lib.rs | 1 + xtask/tests/test_fmt.rs | 15 +++++++++++++++ 5 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 xtask/tests/fixture/Cargo.toml create mode 100644 xtask/tests/fixture/src/lib.rs create mode 100644 xtask/tests/test_fmt.rs diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index a1edffb..8b9f05c 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -8,3 +8,7 @@ publish = false exit_safely = "0.2.0" try_v2 = { workspace = true } clap = { version = "4.6.0", features = ["derive"] } + +[dev-dependencies] +dircpy = "0.3.20" +tempfile = "3.27.0" diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 8b13789..e69de29 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -1 +0,0 @@ - diff --git a/xtask/tests/fixture/Cargo.toml b/xtask/tests/fixture/Cargo.toml new file mode 100644 index 0000000..94b3224 --- /dev/null +++ b/xtask/tests/fixture/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "try_v2_xtasks_fixture" +version = "0.0.1" +edition = "2024" +publish = false diff --git a/xtask/tests/fixture/src/lib.rs b/xtask/tests/fixture/src/lib.rs new file mode 100644 index 0000000..675dcd1 --- /dev/null +++ b/xtask/tests/fixture/src/lib.rs @@ -0,0 +1 @@ +enum foo {} \ No newline at end of file diff --git a/xtask/tests/test_fmt.rs b/xtask/tests/test_fmt.rs new file mode 100644 index 0000000..5fd633e --- /dev/null +++ b/xtask/tests/test_fmt.rs @@ -0,0 +1,15 @@ +use std::fs; + +use dircpy::copy_dir; +use tempfile::tempdir; + +#[test] +fn fmt_fixture() { + let tmp = tempdir().expect("couldn't create temp dir for test"); + copy_dir("tests/fixture", tmp.path()).expect("couldn't copy fixture"); + let original = fs::read_to_string("tests/fixture/src/lib.rs").unwrap(); + let copied = fs::read_to_string(tmp.path().join("src/lib.rs")).unwrap(); + assert_eq!(original,copied); + fmt(tmp.path()).unwrap(); + assert_ne!(original,copied); +} From 80e95fc5d5fd00e9ca8f7f983cfa91542d49f89b Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Sun, 12 Apr 2026 15:10:53 +0000 Subject: [PATCH 05/47] implement & test fmt --- xtask/src/lib.rs | 5 +++++ xtask/tests/fixture/src/lib.rs | 2 +- xtask/tests/test_fmt.rs | 8 +++++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index e69de29..7569936 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -0,0 +1,5 @@ +use std::{io, path::Path, process::{Command, Output}}; + +pub fn fmt(root: &Path) -> Result { + Command::new("cargo").current_dir(root).arg("fmt").output() +} \ No newline at end of file diff --git a/xtask/tests/fixture/src/lib.rs b/xtask/tests/fixture/src/lib.rs index 675dcd1..66e8ee2 100644 --- a/xtask/tests/fixture/src/lib.rs +++ b/xtask/tests/fixture/src/lib.rs @@ -1 +1 @@ -enum foo {} \ No newline at end of file +enum foo { bar } \ No newline at end of file diff --git a/xtask/tests/test_fmt.rs b/xtask/tests/test_fmt.rs index 5fd633e..c12e702 100644 --- a/xtask/tests/test_fmt.rs +++ b/xtask/tests/test_fmt.rs @@ -2,6 +2,7 @@ use std::fs; use dircpy::copy_dir; use tempfile::tempdir; +use try_v2_xtasks::fmt; #[test] fn fmt_fixture() { @@ -9,7 +10,8 @@ fn fmt_fixture() { copy_dir("tests/fixture", tmp.path()).expect("couldn't copy fixture"); let original = fs::read_to_string("tests/fixture/src/lib.rs").unwrap(); let copied = fs::read_to_string(tmp.path().join("src/lib.rs")).unwrap(); - assert_eq!(original,copied); - fmt(tmp.path()).unwrap(); - assert_ne!(original,copied); + assert_eq!(original, copied); + let _ = fmt(tmp.path()).unwrap(); + let formatted = fs::read_to_string(tmp.path().join("src/lib.rs")).unwrap(); + assert_ne!(original, formatted); } From e22eae8fd9573eaa7f9737a925698fc9f2d850ff Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Sun, 12 Apr 2026 15:14:16 +0000 Subject: [PATCH 06/47] run fmt --- xtask/src/main.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 9421779..43ed6f1 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -2,11 +2,12 @@ #![feature(try_trait_v2)] #![feature(try_trait_v2_residual)] -use std::process::Termination as _T; +use std::{io, path::Path, process::Termination as _T}; use clap::{Parser, Subcommand}; use exit_safely::Termination; use try_v2::{Try, Try_ConvertResult}; +use try_v2_xtasks::fmt; #[derive(Debug, Termination, Try, Try_ConvertResult)] #[repr(u8)] @@ -14,6 +15,7 @@ use try_v2::{Try, Try_ConvertResult}; enum Exit { Ok(T) = 0, InvocationError(Box) = 2, + IO(Box) = 3, } impl From for Exit { @@ -22,6 +24,12 @@ impl From for Exit { } } +impl From for Exit { + fn from(e: io::Error) -> Self { + Self::IO(Box::new(e)) + } +} + #[derive(Parser)] #[command(version)] struct XTask { @@ -40,9 +48,9 @@ fn main() -> Exit<()> { match &xtask.command { Command::Add => { - todo!(); - // fmt()?; + fmt(Path::new("."))?; // add()?; + todo!(); } } } From 3d662fec60628f343d77d442e50805743c6110c6 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Sun, 12 Apr 2026 15:15:38 +0000 Subject: [PATCH 07/47] fmt --- xtask/src/lib.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 7569936..81e25bf 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -1,5 +1,9 @@ -use std::{io, path::Path, process::{Command, Output}}; +use std::{ + io, + path::Path, + process::{Command, Output}, +}; pub fn fmt(root: &Path) -> Result { Command::new("cargo").current_dir(root).arg("fmt").output() -} \ No newline at end of file +} From bc1181926154949e31e8189b1aee86e9cae591dc Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Sun, 12 Apr 2026 15:51:17 +0000 Subject: [PATCH 08/47] check output of fmt then run git add --- xtask/src/lib.rs | 8 ++++++++ xtask/src/main.rs | 28 +++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 81e25bf..ec84dbc 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -7,3 +7,11 @@ use std::{ pub fn fmt(root: &Path) -> Result { Command::new("cargo").current_dir(root).arg("fmt").output() } + +pub fn git_add(root: &Path) -> Result { + Command::new("git") + .current_dir(root) + .arg("add") + .arg(".") + .output() +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 43ed6f1..5a21bd0 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -2,18 +2,23 @@ #![feature(try_trait_v2)] #![feature(try_trait_v2_residual)] -use std::{io, path::Path, process::Termination as _T}; +use std::{ + io, + path::Path, + process::{Output, Termination as _T}, +}; use clap::{Parser, Subcommand}; use exit_safely::Termination; use try_v2::{Try, Try_ConvertResult}; -use try_v2_xtasks::fmt; +use try_v2_xtasks::{fmt, git_add}; #[derive(Debug, Termination, Try, Try_ConvertResult)] #[repr(u8)] #[must_use] enum Exit { Ok(T) = 0, + Error(String) = 1, InvocationError(Box) = 2, IO(Box) = 3, } @@ -30,6 +35,17 @@ impl From for Exit { } } +impl From for Exit<()> { + fn from(output: Output) -> Self { + if output.status.success() { + Self::Ok(()) + } else { + let stderr: String = String::from_utf8(output.stderr).unwrap_or_default(); + Self::Error(stderr) + } + } +} + #[derive(Parser)] #[command(version)] struct XTask { @@ -48,9 +64,11 @@ fn main() -> Exit<()> { match &xtask.command { Command::Add => { - fmt(Path::new("."))?; - // add()?; - todo!(); + let root = Path::new("."); + let fmt = fmt(root)?; + Exit::from(fmt)?; + let git = git_add(root)?; + Exit::from(git) } } } From cd23320ce14ba98af66f4b87831f926c4cef083f Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Sun, 12 Apr 2026 16:38:56 +0000 Subject: [PATCH 09/47] provide feedback if task OK --- xtask/src/lib.rs | 26 +++++++++++++++++++++++--- xtask/src/main.rs | 19 ++++++++----------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index ec84dbc..87d1598 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -4,14 +4,34 @@ use std::{ process::{Command, Output}, }; -pub fn fmt(root: &Path) -> Result { - Command::new("cargo").current_dir(root).arg("fmt").output() +pub struct Cmd { + pub name: &'static str, + pub output: Output, } -pub fn git_add(root: &Path) -> Result { +trait CmdExt { + fn map_into_cmd(self, name: &'static str) -> Result; +} + +impl CmdExt for Result { + fn map_into_cmd(self, name: &'static str) -> Result { + self.map(|output| Cmd { name, output }) + } +} + +pub fn fmt(root: &Path) -> Result { + Command::new("cargo") + .current_dir(root) + .arg("fmt") + .output() + .map_into_cmd("fmt") +} + +pub fn git_add(root: &Path) -> Result { Command::new("git") .current_dir(root) .arg("add") .arg(".") .output() + .map_into_cmd("git add") } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 5a21bd0..ef83771 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -2,16 +2,12 @@ #![feature(try_trait_v2)] #![feature(try_trait_v2_residual)] -use std::{ - io, - path::Path, - process::{Output, Termination as _T}, -}; +use std::{io, path::Path, process::Termination as _T}; use clap::{Parser, Subcommand}; use exit_safely::Termination; use try_v2::{Try, Try_ConvertResult}; -use try_v2_xtasks::{fmt, git_add}; +use try_v2_xtasks::{Cmd, fmt, git_add}; #[derive(Debug, Termination, Try, Try_ConvertResult)] #[repr(u8)] @@ -35,13 +31,14 @@ impl From for Exit { } } -impl From for Exit<()> { - fn from(output: Output) -> Self { - if output.status.success() { +impl From for Exit<()> { + fn from(cmd: Cmd) -> Self { + if cmd.output.status.success() { + println!("{}: OK", cmd.name); Self::Ok(()) } else { - let stderr: String = String::from_utf8(output.stderr).unwrap_or_default(); - Self::Error(stderr) + let stderr = String::from_utf8_lossy(&cmd.output.stderr); + Self::Error(stderr.to_string()) } } } From 4d26d0ee731bdd37e3d06458c8fe018f8cce105d Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Sun, 12 Apr 2026 16:51:33 +0000 Subject: [PATCH 10/47] run clippy in spawned process --- xtask/src/lib.rs | 33 ++++++++++++++++++++++++++++++++- xtask/src/main.rs | 4 +++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 87d1598..a138b24 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -1,7 +1,7 @@ use std::{ io, path::Path, - process::{Command, Output}, + process::{Child, Command, Output, Stdio}, }; pub struct Cmd { @@ -19,6 +19,27 @@ impl CmdExt for Result { } } +pub struct Spawned { + pub name: &'static str, + pub child: Child, +} + +impl Spawned { + pub fn wait(self) -> Result { + self.child.wait_with_output().map_into_cmd(self.name) + } +} + +trait SpawnedExt { + fn map_into_spawned(self, name: &'static str) -> Result; +} + +impl SpawnedExt for Result { + fn map_into_spawned(self, name: &'static str) -> Result { + self.map(|child| Spawned { name, child }) + } +} + pub fn fmt(root: &Path) -> Result { Command::new("cargo") .current_dir(root) @@ -35,3 +56,13 @@ pub fn git_add(root: &Path) -> Result { .output() .map_into_cmd("git add") } + +pub fn clippy(root: &Path) -> Result { + Command::new("cargo") + .current_dir(root) + .arg("clippy") + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .map_into_spawned("clippy") +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index ef83771..9f99804 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -7,7 +7,7 @@ use std::{io, path::Path, process::Termination as _T}; use clap::{Parser, Subcommand}; use exit_safely::Termination; use try_v2::{Try, Try_ConvertResult}; -use try_v2_xtasks::{Cmd, fmt, git_add}; +use try_v2_xtasks::{Cmd, clippy, fmt, git_add}; #[derive(Debug, Termination, Try, Try_ConvertResult)] #[repr(u8)] @@ -64,6 +64,8 @@ fn main() -> Exit<()> { let root = Path::new("."); let fmt = fmt(root)?; Exit::from(fmt)?; + let clippy = clippy(root)?; + Exit::from(clippy.wait()?)?; let git = git_add(root)?; Exit::from(git) } From 29574fd9bd0276adb4d03d384c9a1f67bf2a9cbb Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Sun, 12 Apr 2026 16:53:33 +0000 Subject: [PATCH 11/47] and clippy the tests --- xtask/src/lib.rs | 11 +++++++++++ xtask/src/main.rs | 4 +++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index a138b24..da8777b 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -66,3 +66,14 @@ pub fn clippy(root: &Path) -> Result { .spawn() .map_into_spawned("clippy") } + +pub fn clippy_tests(root: &Path) -> Result { + Command::new("cargo") + .current_dir(root) + .arg("clippy") + .arg("--tests") + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .map_into_spawned("clippy the tests") +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 9f99804..a46b2cd 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -7,7 +7,7 @@ use std::{io, path::Path, process::Termination as _T}; use clap::{Parser, Subcommand}; use exit_safely::Termination; use try_v2::{Try, Try_ConvertResult}; -use try_v2_xtasks::{Cmd, clippy, fmt, git_add}; +use try_v2_xtasks::{Cmd, clippy, clippy_tests, fmt, git_add}; #[derive(Debug, Termination, Try, Try_ConvertResult)] #[repr(u8)] @@ -65,7 +65,9 @@ fn main() -> Exit<()> { let fmt = fmt(root)?; Exit::from(fmt)?; let clippy = clippy(root)?; + let clippy_tests = clippy_tests(root)?; Exit::from(clippy.wait()?)?; + Exit::from(clippy_tests.wait()?)?; let git = git_add(root)?; Exit::from(git) } From 9520f98f609ce9b16df7afa9f06088eadfd19e2e Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Sun, 12 Apr 2026 16:54:58 +0000 Subject: [PATCH 12/47] note that we fail fast --- xtask/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index a46b2cd..03562b1 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -66,6 +66,7 @@ fn main() -> Exit<()> { Exit::from(fmt)?; let clippy = clippy(root)?; let clippy_tests = clippy_tests(root)?; + // This currently fails fast ... we want to collect all errors, then fail at end Exit::from(clippy.wait()?)?; Exit::from(clippy_tests.wait()?)?; let git = git_add(root)?; From 7002b724ebf08a5eb80d63d0f46762c1d485fd1f Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Sun, 12 Apr 2026 17:32:13 +0000 Subject: [PATCH 13/47] something like join tasks --- xtask/src/main.rs | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 03562b1..9b56fbb 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -7,7 +7,7 @@ use std::{io, path::Path, process::Termination as _T}; use clap::{Parser, Subcommand}; use exit_safely::Termination; use try_v2::{Try, Try_ConvertResult}; -use try_v2_xtasks::{Cmd, clippy, clippy_tests, fmt, git_add}; +use try_v2_xtasks::{Cmd, Spawned, clippy, clippy_tests, fmt, git_add}; #[derive(Debug, Termination, Try, Try_ConvertResult)] #[repr(u8)] @@ -43,6 +43,28 @@ impl From for Exit<()> { } } +impl From> for Exit<()> { + fn from(spawns: Vec) -> Self { + let cmds: Vec<_> = spawns + .into_iter() + .map(|spawn| spawn.wait()) + .collect::, _>>()?; + let errors: String = cmds + .into_iter() + .filter_map(|cmd| match Exit::from(cmd) { + Self::Ok(_) => None, + Self::Error(s) => Some(s + "\n"), + _ => unreachable!("cmd always goes to Error"), + }) + .collect(); + if errors.is_empty() { + Self::Ok(()) + } else { + Self::Error(errors) + } + } +} + #[derive(Parser)] #[command(version)] struct XTask { @@ -66,9 +88,8 @@ fn main() -> Exit<()> { Exit::from(fmt)?; let clippy = clippy(root)?; let clippy_tests = clippy_tests(root)?; - // This currently fails fast ... we want to collect all errors, then fail at end - Exit::from(clippy.wait()?)?; - Exit::from(clippy_tests.wait()?)?; + let clippies = vec![clippy, clippy_tests]; + Exit::from(clippies)?; let git = git_add(root)?; Exit::from(git) } From b06b6d621dbb7b1e75237d7eb87588056099a9dc Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Sun, 12 Apr 2026 17:35:10 +0000 Subject: [PATCH 14/47] and run the tests --- xtask/src/lib.rs | 10 ++++++++++ xtask/src/main.rs | 7 ++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index da8777b..9ec304b 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -77,3 +77,13 @@ pub fn clippy_tests(root: &Path) -> Result { .spawn() .map_into_spawned("clippy the tests") } + +pub fn test(root: &Path) -> Result { + Command::new("cargo") + .current_dir(root) + .arg("test") + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .map_into_spawned("tests") +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 9b56fbb..bb18eb8 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -7,7 +7,7 @@ use std::{io, path::Path, process::Termination as _T}; use clap::{Parser, Subcommand}; use exit_safely::Termination; use try_v2::{Try, Try_ConvertResult}; -use try_v2_xtasks::{Cmd, Spawned, clippy, clippy_tests, fmt, git_add}; +use try_v2_xtasks::{Cmd, Spawned, clippy, clippy_tests, fmt, git_add, test}; #[derive(Debug, Termination, Try, Try_ConvertResult)] #[repr(u8)] @@ -88,8 +88,9 @@ fn main() -> Exit<()> { Exit::from(fmt)?; let clippy = clippy(root)?; let clippy_tests = clippy_tests(root)?; - let clippies = vec![clippy, clippy_tests]; - Exit::from(clippies)?; + let tests = test(root)?; + let checks = vec![clippy, clippy_tests, tests]; + Exit::from(checks)?; let git = git_add(root)?; Exit::from(git) } From 13f4cf207e92837c99bc5712caffb7e1431171b7 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 07:02:23 +0000 Subject: [PATCH 15/47] add xtask to default members for test, clippy etc --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index c147aa7..9bcdf64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ autocfg = {workspace = true } [workspace] members = ["tests/compilation", "xtask"] +default-members = ["xtask"] [workspace.dependencies] try_v2 = { path = "."} From c9d1c6d11301f8d5d05fd0403ec408dcdb54f314 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 07:11:13 +0000 Subject: [PATCH 16/47] move commands to module --- xtask/src/commands.rs | 55 +++++++++++++++++++++++++++++++++++++++++ xtask/src/lib.rs | 53 +++------------------------------------ xtask/src/main.rs | 5 +++- xtask/tests/test_fmt.rs | 2 +- 4 files changed, 63 insertions(+), 52 deletions(-) create mode 100644 xtask/src/commands.rs diff --git a/xtask/src/commands.rs b/xtask/src/commands.rs new file mode 100644 index 0000000..1148f96 --- /dev/null +++ b/xtask/src/commands.rs @@ -0,0 +1,55 @@ +use std::{ + io, + path::Path, + process::{Command, Stdio}, +}; + +use crate::{Cmd, CmdExt as _, Spawned, SpawnedExt as _}; + +pub fn fmt(root: &Path) -> Result { + Command::new("cargo") + .current_dir(root) + .arg("fmt") + .output() + .map_into_cmd("fmt") +} + +pub fn git_add(root: &Path) -> Result { + Command::new("git") + .current_dir(root) + .arg("add") + .arg(".") + .output() + .map_into_cmd("git add") +} + +pub fn clippy(root: &Path) -> Result { + Command::new("cargo") + .current_dir(root) + .arg("clippy") + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .map_into_spawned("clippy") +} + +pub fn clippy_tests(root: &Path) -> Result { + Command::new("cargo") + .current_dir(root) + .arg("clippy") + .arg("--tests") + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .map_into_spawned("clippy the tests") +} + +pub fn test(root: &Path) -> Result { + Command::new("cargo") + .current_dir(root) + .arg("test") + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .map_into_spawned("tests") +} diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 9ec304b..fcebaca 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -1,9 +1,10 @@ use std::{ io, - path::Path, - process::{Child, Command, Output, Stdio}, + process::{Child, Output}, }; +pub mod commands; + pub struct Cmd { pub name: &'static str, pub output: Output, @@ -39,51 +40,3 @@ impl SpawnedExt for Result { self.map(|child| Spawned { name, child }) } } - -pub fn fmt(root: &Path) -> Result { - Command::new("cargo") - .current_dir(root) - .arg("fmt") - .output() - .map_into_cmd("fmt") -} - -pub fn git_add(root: &Path) -> Result { - Command::new("git") - .current_dir(root) - .arg("add") - .arg(".") - .output() - .map_into_cmd("git add") -} - -pub fn clippy(root: &Path) -> Result { - Command::new("cargo") - .current_dir(root) - .arg("clippy") - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .map_into_spawned("clippy") -} - -pub fn clippy_tests(root: &Path) -> Result { - Command::new("cargo") - .current_dir(root) - .arg("clippy") - .arg("--tests") - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .map_into_spawned("clippy the tests") -} - -pub fn test(root: &Path) -> Result { - Command::new("cargo") - .current_dir(root) - .arg("test") - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .map_into_spawned("tests") -} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index bb18eb8..9c64198 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -7,7 +7,10 @@ use std::{io, path::Path, process::Termination as _T}; use clap::{Parser, Subcommand}; use exit_safely::Termination; use try_v2::{Try, Try_ConvertResult}; -use try_v2_xtasks::{Cmd, Spawned, clippy, clippy_tests, fmt, git_add, test}; +use try_v2_xtasks::{ + Cmd, Spawned, + commands::{clippy, clippy_tests, fmt, git_add, test}, +}; #[derive(Debug, Termination, Try, Try_ConvertResult)] #[repr(u8)] diff --git a/xtask/tests/test_fmt.rs b/xtask/tests/test_fmt.rs index c12e702..88860d1 100644 --- a/xtask/tests/test_fmt.rs +++ b/xtask/tests/test_fmt.rs @@ -2,7 +2,7 @@ use std::fs; use dircpy::copy_dir; use tempfile::tempdir; -use try_v2_xtasks::fmt; +use try_v2_xtasks::commands::fmt; #[test] fn fmt_fixture() { From 756f2be253f1c54629f2c4b44c111ce9c4f0957d Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 07:15:58 +0000 Subject: [PATCH 17/47] move Exit to lib --- xtask/src/lib.rs | 65 +++++++++++++++++++++++++++++++++++++++++++++- xtask/src/main.rs | 66 ++--------------------------------------------- 2 files changed, 66 insertions(+), 65 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index fcebaca..e388811 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -1,8 +1,15 @@ +#![feature(never_type)] +#![feature(try_trait_v2)] +#![feature(try_trait_v2_residual)] + use std::{ io, - process::{Child, Output}, + process::{Child, Output, Termination as _T}, }; +use exit_safely::Termination; +use try_v2::{Try, Try_ConvertResult}; + pub mod commands; pub struct Cmd { @@ -40,3 +47,59 @@ impl SpawnedExt for Result { self.map(|child| Spawned { name, child }) } } + +#[derive(Debug, Termination, Try, Try_ConvertResult)] +#[repr(u8)] +#[must_use] +pub enum Exit { + Ok(T) = 0, + Error(String) = 1, + InvocationError(Box) = 2, + IO(Box) = 3, +} + +impl From for Exit { + fn from(e: clap::Error) -> Self { + Self::InvocationError(Box::new(e)) + } +} + +impl From for Exit { + fn from(e: io::Error) -> Self { + Self::IO(Box::new(e)) + } +} + +impl From for Exit<()> { + fn from(cmd: Cmd) -> Self { + if cmd.output.status.success() { + println!("{}: OK", cmd.name); + Self::Ok(()) + } else { + let stderr = String::from_utf8_lossy(&cmd.output.stderr); + Self::Error(stderr.to_string()) + } + } +} + +impl From> for Exit<()> { + fn from(spawns: Vec) -> Self { + let cmds: Vec<_> = spawns + .into_iter() + .map(|spawn| spawn.wait()) + .collect::, _>>()?; + let errors: String = cmds + .into_iter() + .filter_map(|cmd| match Exit::from(cmd) { + Self::Ok(_) => None, + Self::Error(s) => Some(s + "\n"), + _ => unreachable!("cmd always goes to Error"), + }) + .collect(); + if errors.is_empty() { + Self::Ok(()) + } else { + Self::Error(errors) + } + } +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 9c64198..f17e235 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,73 +1,11 @@ -#![feature(never_type)] -#![feature(try_trait_v2)] -#![feature(try_trait_v2_residual)] - -use std::{io, path::Path, process::Termination as _T}; +use std::path::Path; use clap::{Parser, Subcommand}; -use exit_safely::Termination; -use try_v2::{Try, Try_ConvertResult}; use try_v2_xtasks::{ - Cmd, Spawned, + Exit, commands::{clippy, clippy_tests, fmt, git_add, test}, }; -#[derive(Debug, Termination, Try, Try_ConvertResult)] -#[repr(u8)] -#[must_use] -enum Exit { - Ok(T) = 0, - Error(String) = 1, - InvocationError(Box) = 2, - IO(Box) = 3, -} - -impl From for Exit { - fn from(e: clap::Error) -> Self { - Self::InvocationError(Box::new(e)) - } -} - -impl From for Exit { - fn from(e: io::Error) -> Self { - Self::IO(Box::new(e)) - } -} - -impl From for Exit<()> { - fn from(cmd: Cmd) -> Self { - if cmd.output.status.success() { - println!("{}: OK", cmd.name); - Self::Ok(()) - } else { - let stderr = String::from_utf8_lossy(&cmd.output.stderr); - Self::Error(stderr.to_string()) - } - } -} - -impl From> for Exit<()> { - fn from(spawns: Vec) -> Self { - let cmds: Vec<_> = spawns - .into_iter() - .map(|spawn| spawn.wait()) - .collect::, _>>()?; - let errors: String = cmds - .into_iter() - .filter_map(|cmd| match Exit::from(cmd) { - Self::Ok(_) => None, - Self::Error(s) => Some(s + "\n"), - _ => unreachable!("cmd always goes to Error"), - }) - .collect(); - if errors.is_empty() { - Self::Ok(()) - } else { - Self::Error(errors) - } - } -} - #[derive(Parser)] #[command(version)] struct XTask { From 9bf4d81b818696cd3de4bf666978a646dac03d18 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 07:53:36 +0000 Subject: [PATCH 18/47] start to store the io::Error in the Cmd --- xtask/src/lib.rs | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index e388811..0343c04 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -12,21 +12,35 @@ use try_v2::{Try, Try_ConvertResult}; pub mod commands; +#[derive(Debug)] pub struct Cmd { pub name: &'static str, pub output: Output, } -trait CmdExt { - fn map_into_cmd(self, name: &'static str) -> Result; +#[derive(Debug)] +pub struct Cmd_ { + pub name: &'static str, + pub result: Result, +} + +trait CmdExt { + fn map_into_cmd(self, name: &'static str) -> Result; + fn into_cmd(self, name: &'static str) -> Cmd_; } -impl CmdExt for Result { - fn map_into_cmd(self, name: &'static str) -> Result { +impl CmdExt for Result { + fn map_into_cmd(self, name: &'static str) -> Result { self.map(|output| Cmd { name, output }) } + + fn into_cmd(self, name: &'static str) -> Cmd_ { + Cmd_ { name, result: self } + } + } +#[derive(Debug)] pub struct Spawned { pub name: &'static str, pub child: Child, @@ -103,3 +117,18 @@ impl From> for Exit<()> { } } } + +#[cfg(test)] +mod tests { + use std::process::Command; + + use super::*; + + #[test] + fn exit_from_404() { + let splat: Cmd_ = Command::new("splat").output().into_cmd("splat"); + assert_eq!(splat.name, "splat"); + dbg!(&splat); + assert!(matches!(splat.result, Result::Err(_))); + } +} \ No newline at end of file From 1737b66e4a79ccbcbe4bb309a8ae0f769c7a457b Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 08:15:45 +0000 Subject: [PATCH 19/47] handle to IOError on Exit::from --- xtask/src/lib.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 0343c04..a88466c 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -70,6 +70,7 @@ pub enum Exit { Error(String) = 1, InvocationError(Box) = 2, IO(Box) = 3, + IO2(String) = 9, } impl From for Exit { @@ -84,6 +85,18 @@ impl From for Exit { } } +impl From for Exit<()> { + fn from(cmd: Cmd_) -> Self { + match cmd.result { + Ok(_) => todo!(), + Err(e) => { + let msg = format!("{} failed: {}", cmd.name, e); + Self::IO2(msg) + }, + } + } +} + impl From for Exit<()> { fn from(cmd: Cmd) -> Self { if cmd.output.status.success() { @@ -128,7 +141,10 @@ mod tests { fn exit_from_404() { let splat: Cmd_ = Command::new("splat").output().into_cmd("splat"); assert_eq!(splat.name, "splat"); - dbg!(&splat); - assert!(matches!(splat.result, Result::Err(_))); + assert!(matches!(splat.result, Result::Err(ref e) if matches!(e.kind(), io::ErrorKind::NotFound))); + let exit: Exit<()> = Exit::from(splat); + let Exit::IO2(ref msg) = exit else {panic!("not an IO2")}; + eprintln!("{}", msg); + assert!(msg.starts_with("splat failed: ")); } } \ No newline at end of file From 0c9731bb39abaf7053e20626badfcc4ecd7ffc48 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 08:21:27 +0000 Subject: [PATCH 20/47] complete from Cmd_ --- xtask/src/lib.rs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index a88466c..c74c14f 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -33,11 +33,10 @@ impl CmdExt for Result { fn map_into_cmd(self, name: &'static str) -> Result { self.map(|output| Cmd { name, output }) } - + fn into_cmd(self, name: &'static str) -> Cmd_ { Cmd_ { name, result: self } } - } #[derive(Debug)] @@ -88,11 +87,19 @@ impl From for Exit { impl From for Exit<()> { fn from(cmd: Cmd_) -> Self { match cmd.result { - Ok(_) => todo!(), + Ok(output) => { + if output.status.success() { + println!("{}: OK", cmd.name); + Self::Ok(()) + } else { + let stderr = String::from_utf8_lossy(&output.stderr); + Self::Error(stderr.to_string()) + } + } Err(e) => { let msg = format!("{} failed: {}", cmd.name, e); Self::IO2(msg) - }, + } } } } @@ -141,10 +148,14 @@ mod tests { fn exit_from_404() { let splat: Cmd_ = Command::new("splat").output().into_cmd("splat"); assert_eq!(splat.name, "splat"); - assert!(matches!(splat.result, Result::Err(ref e) if matches!(e.kind(), io::ErrorKind::NotFound))); + assert!( + matches!(splat.result, Result::Err(ref e) if matches!(e.kind(), io::ErrorKind::NotFound)) + ); let exit: Exit<()> = Exit::from(splat); - let Exit::IO2(ref msg) = exit else {panic!("not an IO2")}; + let Exit::IO2(ref msg) = exit else { + panic!("not an IO2") + }; eprintln!("{}", msg); assert!(msg.starts_with("splat failed: ")); } -} \ No newline at end of file +} From 426eff576a5caa6a311eca97e96345853bfeaa0b Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 08:24:30 +0000 Subject: [PATCH 21/47] return Cmd_ --- xtask/src/commands.rs | 10 +++++----- xtask/src/main.rs | 4 ++-- xtask/tests/test_fmt.rs | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/xtask/src/commands.rs b/xtask/src/commands.rs index 1148f96..cda5ded 100644 --- a/xtask/src/commands.rs +++ b/xtask/src/commands.rs @@ -4,23 +4,23 @@ use std::{ process::{Command, Stdio}, }; -use crate::{Cmd, CmdExt as _, Spawned, SpawnedExt as _}; +use crate::{Cmd_, CmdExt as _, Spawned, SpawnedExt as _}; -pub fn fmt(root: &Path) -> Result { +pub fn fmt(root: &Path) -> Cmd_ { Command::new("cargo") .current_dir(root) .arg("fmt") .output() - .map_into_cmd("fmt") + .into_cmd("fmt") } -pub fn git_add(root: &Path) -> Result { +pub fn git_add(root: &Path) -> Cmd_ { Command::new("git") .current_dir(root) .arg("add") .arg(".") .output() - .map_into_cmd("git add") + .into_cmd("git add") } pub fn clippy(root: &Path) -> Result { diff --git a/xtask/src/main.rs b/xtask/src/main.rs index f17e235..66256cf 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -25,14 +25,14 @@ fn main() -> Exit<()> { match &xtask.command { Command::Add => { let root = Path::new("."); - let fmt = fmt(root)?; + let fmt = fmt(root); Exit::from(fmt)?; let clippy = clippy(root)?; let clippy_tests = clippy_tests(root)?; let tests = test(root)?; let checks = vec![clippy, clippy_tests, tests]; Exit::from(checks)?; - let git = git_add(root)?; + let git = git_add(root); Exit::from(git) } } diff --git a/xtask/tests/test_fmt.rs b/xtask/tests/test_fmt.rs index 88860d1..9117ac3 100644 --- a/xtask/tests/test_fmt.rs +++ b/xtask/tests/test_fmt.rs @@ -11,7 +11,7 @@ fn fmt_fixture() { let original = fs::read_to_string("tests/fixture/src/lib.rs").unwrap(); let copied = fs::read_to_string(tmp.path().join("src/lib.rs")).unwrap(); assert_eq!(original, copied); - let _ = fmt(tmp.path()).unwrap(); + let _ = fmt(tmp.path()); let formatted = fs::read_to_string(tmp.path().join("src/lib.rs")).unwrap(); assert_ne!(original, formatted); } From 00b59b8c026ce18985e4d5bf362a0db639dcacac Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 13:09:25 +0000 Subject: [PATCH 22/47] manual half impl FromIterator (Debug not Display) --- xtask/src/lib.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index c74c14f..ce02871 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -3,6 +3,7 @@ #![feature(try_trait_v2_residual)] use std::{ + fmt::Debug, io, process::{Child, Output, Termination as _T}, }; @@ -72,6 +73,23 @@ pub enum Exit { IO2(String) = 9, } +impl FromIterator> for Exit +where + T: Debug, +{ + fn from_iter>>(iter: I) -> Self { + let mut s = String::new(); + for e in iter { + if let Exit::Ok(_) = e { + // do nothing + } else { + s.push_str(format!("{:?}\n", e).as_str()); + } + } + Self::Error(s) + } +} + impl From for Exit { fn from(e: clap::Error) -> Self { Self::InvocationError(Box::new(e)) @@ -158,4 +176,18 @@ mod tests { eprintln!("{}", msg); assert!(msg.starts_with("splat failed: ")); } + + #[test] + fn collect_exit() { + let exits = [ + Exit::Ok(()), + Exit::Error("one".to_string()), + Exit::IO2("two".to_string()), + Exit::Error("three".to_string()), + ]; + let exit: Exit<()> = exits.into_iter().collect(); + let expected = "one\ntwo\nthree\n"; + dbg!(&exit); + assert!(matches!(exit, Exit::IO2(s) if s == expected)); + } } From 91d4d89b1806287186c9f277727d901c1c6ce234 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 15:44:03 +0000 Subject: [PATCH 23/47] hacky from ITerator --- xtask/src/lib.rs | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index ce02871..e052719 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -73,20 +73,39 @@ pub enum Exit { IO2(String) = 9, } -impl FromIterator> for Exit -where - T: Debug, -{ - fn from_iter>>(iter: I) -> Self { +impl FromIterator> for Exit<()> { + fn from_iter>>(iter: I) -> Self { let mut s = String::new(); + let mut errnos: Vec = vec![]; for e in iter { - if let Exit::Ok(_) = e { - // do nothing - } else { - s.push_str(format!("{:?}\n", e).as_str()); - } + match e { + Exit::Ok(_) => {}, + Exit::Error(ref m) => { + s.push_str(m); + errnos.push(1); + } + Exit::InvocationError(ref error) => { + s.push_str(error.to_string().as_str()); + errnos.push(2); + } + Exit::IO(ref error) => { + s.push_str(error.to_string().as_str()); + errnos.push(3); + } + Exit::IO2(ref m) => { + s.push_str(m); + errnos.push(9); + } + }; + } + match errnos.into_iter().min().expect("at least one exit") { + 0 => todo!(), + 1 => Exit::Error(s), + 2 => todo!(), + 3 => todo!(), + 9 => Exit::IO2(s), + _ => todo!(), } - Self::Error(s) } } From bb13e080598cba08a8aa7d9757fb6ae2abd8dab9 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 15:44:56 +0000 Subject: [PATCH 24/47] store strings not boxed errors --- xtask/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index e052719..eb1364c 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -68,8 +68,8 @@ impl SpawnedExt for Result { pub enum Exit { Ok(T) = 0, Error(String) = 1, - InvocationError(Box) = 2, - IO(Box) = 3, + InvocationError(String) = 2, + IO(String) = 3, IO2(String) = 9, } @@ -111,13 +111,13 @@ impl FromIterator> for Exit<()> { impl From for Exit { fn from(e: clap::Error) -> Self { - Self::InvocationError(Box::new(e)) + Self::InvocationError(e.to_string()) } } impl From for Exit { fn from(e: io::Error) -> Self { - Self::IO(Box::new(e)) + Self::IO(e.to_string()) } } From 7664f69fa5deddad0eff4f4c74ac88af4f37bd46 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 15:46:21 +0000 Subject: [PATCH 25/47] no need for second IO variant --- xtask/src/lib.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index eb1364c..d947500 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -70,7 +70,6 @@ pub enum Exit { Error(String) = 1, InvocationError(String) = 2, IO(String) = 3, - IO2(String) = 9, } impl FromIterator> for Exit<()> { @@ -92,18 +91,13 @@ impl FromIterator> for Exit<()> { s.push_str(error.to_string().as_str()); errnos.push(3); } - Exit::IO2(ref m) => { - s.push_str(m); - errnos.push(9); - } }; } match errnos.into_iter().min().expect("at least one exit") { 0 => todo!(), 1 => Exit::Error(s), - 2 => todo!(), - 3 => todo!(), - 9 => Exit::IO2(s), + 2 => Exit::InvocationError(s), + 3 => Exit::IO(s), _ => todo!(), } } @@ -135,7 +129,7 @@ impl From for Exit<()> { } Err(e) => { let msg = format!("{} failed: {}", cmd.name, e); - Self::IO2(msg) + Self::IO(msg) } } } @@ -189,7 +183,7 @@ mod tests { matches!(splat.result, Result::Err(ref e) if matches!(e.kind(), io::ErrorKind::NotFound)) ); let exit: Exit<()> = Exit::from(splat); - let Exit::IO2(ref msg) = exit else { + let Exit::IO(ref msg) = exit else { panic!("not an IO2") }; eprintln!("{}", msg); @@ -201,12 +195,12 @@ mod tests { let exits = [ Exit::Ok(()), Exit::Error("one".to_string()), - Exit::IO2("two".to_string()), + Exit::IO("two".to_string()), Exit::Error("three".to_string()), ]; let exit: Exit<()> = exits.into_iter().collect(); let expected = "one\ntwo\nthree\n"; dbg!(&exit); - assert!(matches!(exit, Exit::IO2(s) if s == expected)); + assert!(matches!(exit, Exit::IO(s) if s == expected)); } } From f8666f6f3866e7da23f96424e9a1a92ba9696a8b Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 15:47:33 +0000 Subject: [PATCH 26/47] derive PartialEq&Ord --- xtask/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index d947500..8f3d047 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -62,7 +62,7 @@ impl SpawnedExt for Result { } } -#[derive(Debug, Termination, Try, Try_ConvertResult)] +#[derive(Debug, Termination, Try, Try_ConvertResult, PartialEq, PartialOrd)] #[repr(u8)] #[must_use] pub enum Exit { @@ -78,7 +78,7 @@ impl FromIterator> for Exit<()> { let mut errnos: Vec = vec![]; for e in iter { match e { - Exit::Ok(_) => {}, + Exit::Ok(_) => {} Exit::Error(ref m) => { s.push_str(m); errnos.push(1); From 32151a4c875c4c963cb7fceb8c5e98ad4ebcdb77 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 15:54:42 +0000 Subject: [PATCH 27/47] add message function --- xtask/src/lib.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 8f3d047..fcfb38a 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -3,7 +3,7 @@ #![feature(try_trait_v2_residual)] use std::{ - fmt::Debug, + fmt::{Debug, Display}, io, process::{Child, Output, Termination as _T}, }; @@ -72,6 +72,17 @@ pub enum Exit { IO(String) = 3, } +impl Exit<()> { + fn message(&self) -> &str { + match self { + Exit::Ok(_) => "", + Exit::Error(m) => m, + Exit::InvocationError(m) => m, + Exit::IO(m) => m, + } + } +} + impl FromIterator> for Exit<()> { fn from_iter>>(iter: I) -> Self { let mut s = String::new(); From 4c94c7dcefddd65538d0b8f773ca277de1e51071 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 16:06:19 +0000 Subject: [PATCH 28/47] from Iterator looking better --- xtask/src/lib.rs | 50 ++++++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index fcfb38a..3598def 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -3,7 +3,7 @@ #![feature(try_trait_v2_residual)] use std::{ - fmt::{Debug, Display}, + fmt::Debug, io, process::{Child, Output, Termination as _T}, }; @@ -62,7 +62,7 @@ impl SpawnedExt for Result { } } -#[derive(Debug, Termination, Try, Try_ConvertResult, PartialEq, PartialOrd)] +#[derive(Debug, Termination, Try, Try_ConvertResult, PartialEq, PartialOrd, Eq, Ord)] #[repr(u8)] #[must_use] pub enum Exit { @@ -81,36 +81,32 @@ impl Exit<()> { Exit::IO(m) => m, } } + + fn replace_message(self, msg: String) -> Option { + match self { + Exit::Ok(_) => None, + Exit::Error(_) => Some(Exit::Error(msg)), + Exit::InvocationError(_) => Some(Exit::InvocationError(msg)), + Exit::IO(_) => Some(Exit::IO(msg)), + } + } } impl FromIterator> for Exit<()> { fn from_iter>>(iter: I) -> Self { - let mut s = String::new(); - let mut errnos: Vec = vec![]; - for e in iter { - match e { - Exit::Ok(_) => {} - Exit::Error(ref m) => { - s.push_str(m); - errnos.push(1); - } - Exit::InvocationError(ref error) => { - s.push_str(error.to_string().as_str()); - errnos.push(2); - } - Exit::IO(ref error) => { - s.push_str(error.to_string().as_str()); - errnos.push(3); + let mut msg = String::new(); + iter.into_iter() + .filter_map(|e| { + if let Exit::Ok(_) = e { + None + } else { + msg.push_str(e.message()); + Some(e) } - }; - } - match errnos.into_iter().min().expect("at least one exit") { - 0 => todo!(), - 1 => Exit::Error(s), - 2 => Exit::InvocationError(s), - 3 => Exit::IO(s), - _ => todo!(), - } + }) + .min() + .and_then(|e| e.replace_message(msg)) + .unwrap_or(Exit::Ok(())) } } From 78b3c1dcd573ce5d1f26ccb3a0abe214b1b1564c Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 16:08:33 +0000 Subject: [PATCH 29/47] update the test --- xtask/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 3598def..6e33b7a 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -101,6 +101,7 @@ impl FromIterator> for Exit<()> { None } else { msg.push_str(e.message()); + msg.push('\n'); Some(e) } }) @@ -201,13 +202,13 @@ mod tests { fn collect_exit() { let exits = [ Exit::Ok(()), - Exit::Error("one".to_string()), - Exit::IO("two".to_string()), + Exit::IO("one".to_string()), + Exit::Error("two".to_string()), Exit::Error("three".to_string()), ]; let exit: Exit<()> = exits.into_iter().collect(); let expected = "one\ntwo\nthree\n"; dbg!(&exit); - assert!(matches!(exit, Exit::IO(s) if s == expected)); + assert!(matches!(exit, Exit::Error(s) if s == expected)); } } From f299f40bca2ea5c8c180d6d3eec92821d40a2a54 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 16:13:36 +0000 Subject: [PATCH 30/47] add Spawned_ --- xtask/src/lib.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 6e33b7a..4a3be33 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -52,6 +52,22 @@ impl Spawned { } } +#[derive(Debug)] +pub struct Spawned_ { + pub name: &'static str, + pub child: Result, +} + +impl Spawned_ { + pub fn wait(self) -> Cmd_ { + match self.child { + Ok(child) => child.wait_with_output().into_cmd(self.name), + Err(e) => Cmd_ { name: self.name, result: Err(e) }, + } + } +} + + trait SpawnedExt { fn map_into_spawned(self, name: &'static str) -> Result; } From 337da96d568cdf88e9161727e85f9fc988dc35ff Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 16:14:23 +0000 Subject: [PATCH 31/47] SpawnedExt doesnt need to be generic over E --- xtask/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 4a3be33..40e98b1 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -68,12 +68,12 @@ impl Spawned_ { } -trait SpawnedExt { - fn map_into_spawned(self, name: &'static str) -> Result; +trait SpawnedExt { + fn map_into_spawned(self, name: &'static str) -> Result; } -impl SpawnedExt for Result { - fn map_into_spawned(self, name: &'static str) -> Result { +impl SpawnedExt for Result { + fn map_into_spawned(self, name: &'static str) -> Result { self.map(|child| Spawned { name, child }) } } From da23c9199b5facaeecaab3ed2db501048937345d Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 16:15:30 +0000 Subject: [PATCH 32/47] add into_spawned --- xtask/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 40e98b1..11c178a 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -70,12 +70,17 @@ impl Spawned_ { trait SpawnedExt { fn map_into_spawned(self, name: &'static str) -> Result; + fn into_spawned(self, name: &'static str) -> Spawned_; } impl SpawnedExt for Result { fn map_into_spawned(self, name: &'static str) -> Result { self.map(|child| Spawned { name, child }) } + + fn into_spawned(self, name: &'static str) -> Spawned_ { + Spawned_ { name, child: self } + } } #[derive(Debug, Termination, Try, Try_ConvertResult, PartialEq, PartialOrd, Eq, Ord)] From 931ecced9e079f1dc6e6b4eb079d74d0b2827981 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 16:15:40 +0000 Subject: [PATCH 33/47] fmt --- xtask/src/lib.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 11c178a..743b660 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -62,12 +62,14 @@ impl Spawned_ { pub fn wait(self) -> Cmd_ { match self.child { Ok(child) => child.wait_with_output().into_cmd(self.name), - Err(e) => Cmd_ { name: self.name, result: Err(e) }, + Err(e) => Cmd_ { + name: self.name, + result: Err(e), + }, } } } - trait SpawnedExt { fn map_into_spawned(self, name: &'static str) -> Result; fn into_spawned(self, name: &'static str) -> Spawned_; @@ -77,7 +79,7 @@ impl SpawnedExt for Result { fn map_into_spawned(self, name: &'static str) -> Result { self.map(|child| Spawned { name, child }) } - + fn into_spawned(self, name: &'static str) -> Spawned_ { Spawned_ { name, child: self } } From 73c4caba9b9ca2655ec2b236166461f487f3e59a Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 16:20:22 +0000 Subject: [PATCH 34/47] now From Vec Spawned is nice --- xtask/src/commands.rs | 15 +++++++-------- xtask/src/lib.rs | 10 ++++++++++ xtask/src/main.rs | 6 +++--- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/xtask/src/commands.rs b/xtask/src/commands.rs index cda5ded..4ddaac7 100644 --- a/xtask/src/commands.rs +++ b/xtask/src/commands.rs @@ -1,10 +1,9 @@ use std::{ - io, path::Path, process::{Command, Stdio}, }; -use crate::{Cmd_, CmdExt as _, Spawned, SpawnedExt as _}; +use crate::{Cmd_, CmdExt as _, Spawned_, SpawnedExt as _}; pub fn fmt(root: &Path) -> Cmd_ { Command::new("cargo") @@ -23,17 +22,17 @@ pub fn git_add(root: &Path) -> Cmd_ { .into_cmd("git add") } -pub fn clippy(root: &Path) -> Result { +pub fn clippy(root: &Path) -> Spawned_ { Command::new("cargo") .current_dir(root) .arg("clippy") .stderr(Stdio::piped()) .stdout(Stdio::piped()) .spawn() - .map_into_spawned("clippy") + .into_spawned("clippy") } -pub fn clippy_tests(root: &Path) -> Result { +pub fn clippy_tests(root: &Path) -> Spawned_ { Command::new("cargo") .current_dir(root) .arg("clippy") @@ -41,15 +40,15 @@ pub fn clippy_tests(root: &Path) -> Result { .stderr(Stdio::piped()) .stdout(Stdio::piped()) .spawn() - .map_into_spawned("clippy the tests") + .into_spawned("clippy the tests") } -pub fn test(root: &Path) -> Result { +pub fn test(root: &Path) -> Spawned_ { Command::new("cargo") .current_dir(root) .arg("test") .stderr(Stdio::piped()) .stdout(Stdio::piped()) .spawn() - .map_into_spawned("tests") + .into_spawned("tests") } diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 743b660..f786b79 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -200,6 +200,16 @@ impl From> for Exit<()> { } } +impl From> for Exit<()> { + fn from(spawns: Vec) -> Self { + spawns + .into_iter() + .map(|spawn| spawn.wait()) + .map(Exit::from) + .collect() + } +} + #[cfg(test)] mod tests { use std::process::Command; diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 66256cf..26726c6 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -27,9 +27,9 @@ fn main() -> Exit<()> { let root = Path::new("."); let fmt = fmt(root); Exit::from(fmt)?; - let clippy = clippy(root)?; - let clippy_tests = clippy_tests(root)?; - let tests = test(root)?; + let clippy = clippy(root); + let clippy_tests = clippy_tests(root); + let tests = test(root); let checks = vec![clippy, clippy_tests, tests]; Exit::from(checks)?; let git = git_add(root); From edb83c89cfe5ce658885b3dec90bb01dd695b579 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 16:21:21 +0000 Subject: [PATCH 35/47] remove the old Cmd & Spawned --- xtask/src/lib.rs | 62 ------------------------------------------------ 1 file changed, 62 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index f786b79..1ca9dbd 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -13,12 +13,6 @@ use try_v2::{Try, Try_ConvertResult}; pub mod commands; -#[derive(Debug)] -pub struct Cmd { - pub name: &'static str, - pub output: Output, -} - #[derive(Debug)] pub struct Cmd_ { pub name: &'static str, @@ -26,32 +20,15 @@ pub struct Cmd_ { } trait CmdExt { - fn map_into_cmd(self, name: &'static str) -> Result; fn into_cmd(self, name: &'static str) -> Cmd_; } impl CmdExt for Result { - fn map_into_cmd(self, name: &'static str) -> Result { - self.map(|output| Cmd { name, output }) - } - fn into_cmd(self, name: &'static str) -> Cmd_ { Cmd_ { name, result: self } } } -#[derive(Debug)] -pub struct Spawned { - pub name: &'static str, - pub child: Child, -} - -impl Spawned { - pub fn wait(self) -> Result { - self.child.wait_with_output().map_into_cmd(self.name) - } -} - #[derive(Debug)] pub struct Spawned_ { pub name: &'static str, @@ -71,15 +48,10 @@ impl Spawned_ { } trait SpawnedExt { - fn map_into_spawned(self, name: &'static str) -> Result; fn into_spawned(self, name: &'static str) -> Spawned_; } impl SpawnedExt for Result { - fn map_into_spawned(self, name: &'static str) -> Result { - self.map(|child| Spawned { name, child }) - } - fn into_spawned(self, name: &'static str) -> Spawned_ { Spawned_ { name, child: self } } @@ -166,40 +138,6 @@ impl From for Exit<()> { } } -impl From for Exit<()> { - fn from(cmd: Cmd) -> Self { - if cmd.output.status.success() { - println!("{}: OK", cmd.name); - Self::Ok(()) - } else { - let stderr = String::from_utf8_lossy(&cmd.output.stderr); - Self::Error(stderr.to_string()) - } - } -} - -impl From> for Exit<()> { - fn from(spawns: Vec) -> Self { - let cmds: Vec<_> = spawns - .into_iter() - .map(|spawn| spawn.wait()) - .collect::, _>>()?; - let errors: String = cmds - .into_iter() - .filter_map(|cmd| match Exit::from(cmd) { - Self::Ok(_) => None, - Self::Error(s) => Some(s + "\n"), - _ => unreachable!("cmd always goes to Error"), - }) - .collect(); - if errors.is_empty() { - Self::Ok(()) - } else { - Self::Error(errors) - } - } -} - impl From> for Exit<()> { fn from(spawns: Vec) -> Self { spawns From 88a98988e103d78a0c5bd99a75283b8bd4e540a0 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 16:21:36 +0000 Subject: [PATCH 36/47] and rename the new ones --- xtask/src/commands.rs | 12 ++++++------ xtask/src/lib.rs | 32 ++++++++++++++++---------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/xtask/src/commands.rs b/xtask/src/commands.rs index 4ddaac7..0dccfd9 100644 --- a/xtask/src/commands.rs +++ b/xtask/src/commands.rs @@ -3,9 +3,9 @@ use std::{ process::{Command, Stdio}, }; -use crate::{Cmd_, CmdExt as _, Spawned_, SpawnedExt as _}; +use crate::{Cmd, CmdExt as _, Spawned, SpawnedExt as _}; -pub fn fmt(root: &Path) -> Cmd_ { +pub fn fmt(root: &Path) -> Cmd { Command::new("cargo") .current_dir(root) .arg("fmt") @@ -13,7 +13,7 @@ pub fn fmt(root: &Path) -> Cmd_ { .into_cmd("fmt") } -pub fn git_add(root: &Path) -> Cmd_ { +pub fn git_add(root: &Path) -> Cmd { Command::new("git") .current_dir(root) .arg("add") @@ -22,7 +22,7 @@ pub fn git_add(root: &Path) -> Cmd_ { .into_cmd("git add") } -pub fn clippy(root: &Path) -> Spawned_ { +pub fn clippy(root: &Path) -> Spawned { Command::new("cargo") .current_dir(root) .arg("clippy") @@ -32,7 +32,7 @@ pub fn clippy(root: &Path) -> Spawned_ { .into_spawned("clippy") } -pub fn clippy_tests(root: &Path) -> Spawned_ { +pub fn clippy_tests(root: &Path) -> Spawned { Command::new("cargo") .current_dir(root) .arg("clippy") @@ -43,7 +43,7 @@ pub fn clippy_tests(root: &Path) -> Spawned_ { .into_spawned("clippy the tests") } -pub fn test(root: &Path) -> Spawned_ { +pub fn test(root: &Path) -> Spawned { Command::new("cargo") .current_dir(root) .arg("test") diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 1ca9dbd..3869596 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -14,32 +14,32 @@ use try_v2::{Try, Try_ConvertResult}; pub mod commands; #[derive(Debug)] -pub struct Cmd_ { +pub struct Cmd { pub name: &'static str, pub result: Result, } trait CmdExt { - fn into_cmd(self, name: &'static str) -> Cmd_; + fn into_cmd(self, name: &'static str) -> Cmd; } impl CmdExt for Result { - fn into_cmd(self, name: &'static str) -> Cmd_ { - Cmd_ { name, result: self } + fn into_cmd(self, name: &'static str) -> Cmd { + Cmd { name, result: self } } } #[derive(Debug)] -pub struct Spawned_ { +pub struct Spawned { pub name: &'static str, pub child: Result, } -impl Spawned_ { - pub fn wait(self) -> Cmd_ { +impl Spawned { + pub fn wait(self) -> Cmd { match self.child { Ok(child) => child.wait_with_output().into_cmd(self.name), - Err(e) => Cmd_ { + Err(e) => Cmd { name: self.name, result: Err(e), }, @@ -48,12 +48,12 @@ impl Spawned_ { } trait SpawnedExt { - fn into_spawned(self, name: &'static str) -> Spawned_; + fn into_spawned(self, name: &'static str) -> Spawned; } impl SpawnedExt for Result { - fn into_spawned(self, name: &'static str) -> Spawned_ { - Spawned_ { name, child: self } + fn into_spawned(self, name: &'static str) -> Spawned { + Spawned { name, child: self } } } @@ -118,8 +118,8 @@ impl From for Exit { } } -impl From for Exit<()> { - fn from(cmd: Cmd_) -> Self { +impl From for Exit<()> { + fn from(cmd: Cmd) -> Self { match cmd.result { Ok(output) => { if output.status.success() { @@ -138,8 +138,8 @@ impl From for Exit<()> { } } -impl From> for Exit<()> { - fn from(spawns: Vec) -> Self { +impl From> for Exit<()> { + fn from(spawns: Vec) -> Self { spawns .into_iter() .map(|spawn| spawn.wait()) @@ -156,7 +156,7 @@ mod tests { #[test] fn exit_from_404() { - let splat: Cmd_ = Command::new("splat").output().into_cmd("splat"); + let splat: Cmd = Command::new("splat").output().into_cmd("splat"); assert_eq!(splat.name, "splat"); assert!( matches!(splat.result, Result::Err(ref e) if matches!(e.kind(), io::ErrorKind::NotFound)) From d14fe1d5aa95b7adc7937a0b9fd4f28225a6db19 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 16:31:29 +0000 Subject: [PATCH 37/47] handle let_chains stabilisation --- xtask/Cargo.toml | 3 +++ xtask/build.rs | 21 +++++++++++++++++++++ xtask/src/lib.rs | 1 + 3 files changed, 25 insertions(+) create mode 100644 xtask/build.rs diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 8b9f05c..6ec517e 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -12,3 +12,6 @@ clap = { version = "4.6.0", features = ["derive"] } [dev-dependencies] dircpy = "0.3.20" tempfile = "3.27.0" + +[build-dependencies] +autocfg = { workspace = true } diff --git a/xtask/build.rs b/xtask/build.rs new file mode 100644 index 0000000..e446eca --- /dev/null +++ b/xtask/build.rs @@ -0,0 +1,21 @@ +use autocfg::AutoCfg; + +fn main() { + let ac = autocfg::new(); + stable_feature(&ac, "let_chains"); +} + +fn stable_feature(ac: &AutoCfg, feature: &'static str) { + let cfg = format!("stable_{feature}"); + let code = format!( + r#" + #![deny(stable_features)] + #![feature({feature})] + "# + ); + + autocfg::emit_possibility(&cfg); + if ac.probe_raw(&code).is_err() { + autocfg::emit(&cfg); + } +} diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 3869596..6a4f9a9 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(not(stable_let_chains), feature(let_chains))] #![feature(never_type)] #![feature(try_trait_v2)] #![feature(try_trait_v2_residual)] From 34a00a34f6470758d20d19718dff136d58c35722 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 16:33:24 +0000 Subject: [PATCH 38/47] remove unused direct conversion from io::Error --- xtask/src/lib.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 6a4f9a9..8267c0f 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -113,12 +113,6 @@ impl From for Exit { } } -impl From for Exit { - fn from(e: io::Error) -> Self { - Self::IO(e.to_string()) - } -} - impl From for Exit<()> { fn from(cmd: Cmd) -> Self { match cmd.result { From 945c5c8a78071b952982c99f159858207e0fdd97 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 16:36:01 +0000 Subject: [PATCH 39/47] reorder for easier reading --- xtask/src/lib.rs | 88 ++++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 8267c0f..88e4d16 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -14,50 +14,6 @@ use try_v2::{Try, Try_ConvertResult}; pub mod commands; -#[derive(Debug)] -pub struct Cmd { - pub name: &'static str, - pub result: Result, -} - -trait CmdExt { - fn into_cmd(self, name: &'static str) -> Cmd; -} - -impl CmdExt for Result { - fn into_cmd(self, name: &'static str) -> Cmd { - Cmd { name, result: self } - } -} - -#[derive(Debug)] -pub struct Spawned { - pub name: &'static str, - pub child: Result, -} - -impl Spawned { - pub fn wait(self) -> Cmd { - match self.child { - Ok(child) => child.wait_with_output().into_cmd(self.name), - Err(e) => Cmd { - name: self.name, - result: Err(e), - }, - } - } -} - -trait SpawnedExt { - fn into_spawned(self, name: &'static str) -> Spawned; -} - -impl SpawnedExt for Result { - fn into_spawned(self, name: &'static str) -> Spawned { - Spawned { name, child: self } - } -} - #[derive(Debug, Termination, Try, Try_ConvertResult, PartialEq, PartialOrd, Eq, Ord)] #[repr(u8)] #[must_use] @@ -113,6 +69,22 @@ impl From for Exit { } } +#[derive(Debug)] +pub struct Cmd { + pub name: &'static str, + pub result: Result, +} + +trait CmdExt { + fn into_cmd(self, name: &'static str) -> Cmd; +} + +impl CmdExt for Result { + fn into_cmd(self, name: &'static str) -> Cmd { + Cmd { name, result: self } + } +} + impl From for Exit<()> { fn from(cmd: Cmd) -> Self { match cmd.result { @@ -133,6 +105,34 @@ impl From for Exit<()> { } } +#[derive(Debug)] +pub struct Spawned { + pub name: &'static str, + pub child: Result, +} + +impl Spawned { + pub fn wait(self) -> Cmd { + match self.child { + Ok(child) => child.wait_with_output().into_cmd(self.name), + Err(e) => Cmd { + name: self.name, + result: Err(e), + }, + } + } +} + +trait SpawnedExt { + fn into_spawned(self, name: &'static str) -> Spawned; +} + +impl SpawnedExt for Result { + fn into_spawned(self, name: &'static str) -> Spawned { + Spawned { name, child: self } + } +} + impl From> for Exit<()> { fn from(spawns: Vec) -> Self { spawns From 3bbdc44a924b8bcc33091d79742eb4a4f321cb39 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 17:42:49 +0000 Subject: [PATCH 40/47] bump exit_safely to fix msrv --- xtask/Cargo.toml | 2 +- xtask/src/lib.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 6ec517e..ebabac0 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" publish = false [dependencies] -exit_safely = "0.2.0" +exit_safely = "0.2.1" try_v2 = { workspace = true } clap = { version = "4.6.0", features = ["derive"] } diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 88e4d16..59f03f7 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -1,4 +1,3 @@ -#![cfg_attr(not(stable_let_chains), feature(let_chains))] #![feature(never_type)] #![feature(try_trait_v2)] #![feature(try_trait_v2_residual)] From 752c0ac2da617cdf53ab39284643719a7bb8b142 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 17:44:17 +0000 Subject: [PATCH 41/47] don't need build.rs for xtasks --- xtask/build.rs | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 xtask/build.rs diff --git a/xtask/build.rs b/xtask/build.rs deleted file mode 100644 index e446eca..0000000 --- a/xtask/build.rs +++ /dev/null @@ -1,21 +0,0 @@ -use autocfg::AutoCfg; - -fn main() { - let ac = autocfg::new(); - stable_feature(&ac, "let_chains"); -} - -fn stable_feature(ac: &AutoCfg, feature: &'static str) { - let cfg = format!("stable_{feature}"); - let code = format!( - r#" - #![deny(stable_features)] - #![feature({feature})] - "# - ); - - autocfg::emit_possibility(&cfg); - if ac.probe_raw(&code).is_err() { - autocfg::emit(&cfg); - } -} From bb5c6fe859828518ccf3e4ae3db7a3d5617518c0 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 17:46:22 +0000 Subject: [PATCH 42/47] better handling of stable_feature probe --- build.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/build.rs b/build.rs index 15af04d..129592d 100644 --- a/build.rs +++ b/build.rs @@ -15,15 +15,22 @@ fn main() { fn stable_feature(ac: &AutoCfg, feature: &'static str) { let cfg = format!("stable_{feature}"); - let code = format!( + let deny = format!( r#" #![deny(stable_features)] #![feature({feature})] "# ); + let allow = format!( + r#" + #![allow(stable_features)] + #![feature({feature})] + "# + ); + autocfg::emit_possibility(&cfg); - if ac.probe_raw(&code).is_err() { + if ac.probe_raw(&deny).is_err() && ac.probe_raw(&allow).is_ok() { autocfg::emit(&cfg); } } From 98747707888f06a1ebfc1ff25a84ea604eb2a8de Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 17:51:57 +0000 Subject: [PATCH 43/47] add cargo aliases for xtasks --- .cargo/config.toml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..5c6ecc6 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ +[alias] +x = "run --package try_v2_xtasks --" +stage = "run --package try_v2_xtasks -- add" \ No newline at end of file From 52eee1e8b99f6f3587c5b9623330eb6ca8262883 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 17:57:30 +0000 Subject: [PATCH 44/47] check fmt runs OK --- xtask/tests/test_fmt.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/xtask/tests/test_fmt.rs b/xtask/tests/test_fmt.rs index 9117ac3..69f8566 100644 --- a/xtask/tests/test_fmt.rs +++ b/xtask/tests/test_fmt.rs @@ -11,7 +11,14 @@ fn fmt_fixture() { let original = fs::read_to_string("tests/fixture/src/lib.rs").unwrap(); let copied = fs::read_to_string(tmp.path().join("src/lib.rs")).unwrap(); assert_eq!(original, copied); - let _ = fmt(tmp.path()); + let cmd = fmt(tmp.path()); + let output = cmd.result.expect("`cargo fmt` failed to run"); + assert!( + output.status.success(), + "`cargo fmt` exited with status {:?}\nstderr: {}", + output.status, + String::from_utf8_lossy(&output.stderr), + ); let formatted = fs::read_to_string(tmp.path().join("src/lib.rs")).unwrap(); assert_ne!(original, formatted); } From 91e64ff3aa9ca2c8a3802a8370eba6055b25f918 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 17:57:42 +0000 Subject: [PATCH 45/47] dbg tmp path - to help find CI issue --- xtask/tests/test_fmt.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/xtask/tests/test_fmt.rs b/xtask/tests/test_fmt.rs index 69f8566..2372989 100644 --- a/xtask/tests/test_fmt.rs +++ b/xtask/tests/test_fmt.rs @@ -21,4 +21,5 @@ fn fmt_fixture() { ); let formatted = fs::read_to_string(tmp.path().join("src/lib.rs")).unwrap(); assert_ne!(original, formatted); + dbg!(tmp.path()); } From 6b3634399da1a0ddd3565b0c1f26a632d99685a5 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 18:02:27 +0000 Subject: [PATCH 46/47] safer channel settings for stability checks --- .github/workflows/rust-stability.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/rust-stability.yml b/.github/workflows/rust-stability.yml index 2319ccb..5b70505 100644 --- a/.github/workflows/rust-stability.yml +++ b/.github/workflows/rust-stability.yml @@ -26,9 +26,9 @@ jobs: steps: - uses: actions/checkout@v6 - name: update rust - run: rustup update + run: rustup update && rustup default ${{ matrix.channel }} && rustup install --profile default - name: Run tests - run: cargo +${{ matrix.channel }} test --no-fail-fast + run: cargo test --no-fail-fast lint: strategy: @@ -39,9 +39,9 @@ jobs: steps: - uses: actions/checkout@v6 - name: update rust - run: rustup update && rustup component add --toolchain ${{ matrix.channel }} clippy + run: rustup update && rustup default ${{ matrix.channel }} && rustup install --profile default - name: clippy - run: cargo +${{ matrix.channel }} clippy -- --deny warnings + run: cargo clippy -- --deny warnings report: if: ${{ always() }} From bb6379a7d390ea006e64e874656342e8b02eb1fa Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Mon, 13 Apr 2026 18:05:40 +0000 Subject: [PATCH 47/47] try again to set default profile --- .github/workflows/rust-stability.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust-stability.yml b/.github/workflows/rust-stability.yml index 5b70505..1c934a6 100644 --- a/.github/workflows/rust-stability.yml +++ b/.github/workflows/rust-stability.yml @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: update rust - run: rustup update && rustup default ${{ matrix.channel }} && rustup install --profile default + run: rustup set profile default && rustup update && rustup default ${{ matrix.channel }} - name: Run tests run: cargo test --no-fail-fast @@ -39,7 +39,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: update rust - run: rustup update && rustup default ${{ matrix.channel }} && rustup install --profile default + run: rustup set profile default && rustup update && rustup default ${{ matrix.channel }} - name: clippy run: cargo clippy -- --deny warnings