1use ::clap::Parser;
2use ::std::{io, fs, env};
3use ::std::process::{Command, Stdio, exit};
4
5#[derive(Parser)]
6#[command(version, about)]
7struct Args {
8 task_names: Vec<String>,
9}
10
11pub fn run() -> io::Result<()> {
12 let Args {
13 task_names,
14 } = {
15 let mut args = env::args().collect::<Vec<_>>();
16 if matches!(args.get(1).map(String::as_str), Some("task" | "metask")) {
17 args.remove(1);
19 }
20 Args::parse_from(args)
21 };
22 if task_names.is_empty() {
23 eprintln!("[cargo-metask] no task names provided.");
24 return Ok(());
25 }
26
27 let cargo_toml = {
28 toml::from_str::<toml::Value>(&fs::read_to_string("Cargo.toml")?)
29 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
30 };
31
32 let task_def = get_task_def(&cargo_toml).ok_or_else(|| io::Error::new(
33 io::ErrorKind::InvalidData,
34 "`{package, workspace}.metadata.tasks` not found"
35 ))?;
36
37 let tasks = {
38 let mut tasks = Vec::with_capacity(task_names.len());
39 for task_name in &task_names {
40 let task = task_def
41 .get(task_name)
42 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, format!("task `{task_name}` not found")))?
43 .as_str()
44 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, format!("task `{task_name}` is not a string")))?;
45 tasks.push(task);
46 }
47 tasks
48 };
49
50 let mut handles = std::collections::VecDeque::with_capacity(tasks.len());
53 #[cfg(not(target_os = "windows"))] {
54 let shell = env::var("SHELL");
55 for task in &tasks {
56 handles.push_back(
57 Command::new(shell.as_deref().unwrap_or("/bin/sh"))
58 .args(["-c", &format!("set -Cue\n{task}")])
59 .stdout(Stdio::inherit())
60 .stderr(Stdio::inherit())
61 .spawn()?
62 );
63 }
64 }
65 #[cfg(target_os = "windows")] {
66 for task in &tasks {
67 handles.push_back(
68 Command::new("cmd")
69 .args(["/C", task])
70 .stdout(Stdio::inherit())
71 .stderr(Stdio::inherit())
72 .spawn()?
73 );
74 }
75 }
76
77 match handles.len() {
78 0 => {
79 Ok(())
80 }
81 1 => {
82 let status = handles.pop_front().unwrap().wait()?;
83 let code = status.code().unwrap_or_else(|| {
84 eprintln!("[cargo-metask] task terminated by signal");
85 1
86 });
87 exit(code);
88 }
89 _ => {
90 let mut error_code = None;
91 while let Some(mut next) = handles.pop_front() {
92 match next.try_wait()? {
93 None => handles.push_back(next),
95
96 Some(status) => match status.code() {
98 Some(code) => {
99 if code != 0 && error_code.is_none() {
100 error_code = Some(code);
101 }
102 }
103 None => {
104 eprintln!("[cargo-metask] task terminated by signal");
105 if error_code.is_none() {
106 error_code = Some(1);
107 }
108 }
109 }
110 }
111
112 std::thread::sleep(std::time::Duration::from_millis(10));
118 }
119 exit(error_code.unwrap_or(0));
120 }
121 }
122}
123
124fn get_task_def(cargo_toml: &toml::Value) -> Option<&toml::Table> {
125 (cargo_toml.get("workspace")).or(cargo_toml.get("package"))?
126 .get("metadata")?
127 .get("tasks")?
128 .as_table()
129}