run_clang_tidy/cmd/
mod.rs1use std::{io, path, process, str::FromStr};
2
3#[derive(Clone)]
4struct Version {
5 major: u8,
6 minor: u8,
7 patch: u8,
8}
9
10#[derive(Debug)]
11pub enum RunResult {
12 Ok,
13 Err(String),
14 Warn(String),
15}
16
17impl From<&io::Error> for RunResult {
18 fn from(value: &io::Error) -> Self {
19 RunResult::Err(value.to_string())
20 }
21}
22
23impl FromStr for Version {
24 type Err = String;
25
26 fn from_str(s: &str) -> Result<Self, Self::Err> {
27 let re = regex::Regex::new(r".*version ([\d]+)\.([\d]+)\.([\d]+).*").unwrap();
28 let caps = re.captures(s).ok_or("Failed to match version")?;
29
30 Ok(Version {
31 major: caps[1].parse().map_err(|_| "Invalid major version")?,
32 minor: caps[2].parse().map_err(|_| "Invalid minor version")?,
33 patch: caps[3].parse().map_err(|_| "Invalid patch level")?,
34 })
35 }
36}
37
38pub struct Runner {
39 cmd: path::PathBuf,
40 version: Option<Version>,
41}
42
43impl Runner {
44 pub fn new<P>(path: P) -> Runner
45 where
46 P: AsRef<path::Path>,
47 {
48 let cmd = path::PathBuf::from(path.as_ref());
49 Runner { cmd, version: None }
50 }
51
52 fn eval_status(status: process::ExitStatus) -> Result<(), io::Error> {
53 match status.code() {
54 Some(0) => (),
55 Some(code) => {
56 return Err(io::Error::new(
57 io::ErrorKind::Other,
58 format!("Process terminated with code {code}"),
59 ));
60 }
61 None => {
62 return Err(io::Error::new(
63 io::ErrorKind::Interrupted,
64 "Process terminated by signal",
65 ))
66 }
67 };
68 Ok(())
69 }
70
71 pub fn get_version(&self) -> Option<String> {
72 self.version
73 .as_ref()
74 .map(|v| format!("{}.{}.{}", v.major, v.minor, v.patch))
75 }
76
77 pub fn get_path(&self) -> path::PathBuf {
78 self.cmd.clone()
79 }
80
81 pub fn validate(&mut self) -> Result<(), io::Error> {
82 let cmd = process::Command::new(self.cmd.as_path())
83 .arg("--version")
84 .output()?;
85
86 if let Err(err) = Runner::eval_status(cmd.status) {
87 log::error!(
88 "Execution failed:\n{}",
89 String::from_utf8_lossy(&cmd.stderr)
90 );
91 return Err(err);
92 }
93
94 let stdout = String::from_utf8_lossy(&cmd.stdout);
97
98 self.version = Some(stdout.parse::<Version>().map_err(|err| {
99 io::Error::new(
100 io::ErrorKind::Other,
101 format!("Failed to parse --version output {stdout}: {err}"),
102 )
103 })?);
104 Ok(())
105 }
106
107 fn run(mut cmd: process::Command, ignore_warn: bool) -> RunResult {
108 let output = cmd.output();
109 if let Err(err) = &output {
110 return err.into();
111 }
112 let output = output.unwrap();
113
114 let stderr = String::from_utf8_lossy(&output.stderr);
115 let stdout = String::from_utf8_lossy(&output.stdout);
116
117 if let Err(err) = Runner::eval_status(output.status) {
118 if stderr.len() != 0 {
119 return RunResult::Err(format!("{err}\n---\n{stderr}---\n{stdout}"));
120 }
121 return (&err).into();
122 } else if !ignore_warn && !stderr.is_empty() {
123 return RunResult::Warn(format!("warnings encountered\n---\n{stderr}---\n{stdout}"));
124 }
125 RunResult::Ok
126 }
127
128 pub fn run_tidy<P, Q>(&self, file: P, build_root: Q, fix: bool, ignore_warn: bool) -> RunResult
129 where
130 P: AsRef<path::Path>,
131 Q: AsRef<path::Path>,
132 {
133 let mut cmd = process::Command::new(self.cmd.as_path());
134
135 cmd.arg(file.as_ref().as_os_str());
136 cmd.arg(format!("-p={}", build_root.as_ref().to_string_lossy()));
144 if fix {
145 cmd.arg("-fix").arg("-fix-errors");
146 }
147 Runner::run(cmd, ignore_warn)
151 }
152
153 pub fn supports_config_file(&self) -> Result<(), io::Error> {
154 if self.version.is_none() {
155 return Err(io::Error::new(
156 io::ErrorKind::Other,
157 "Unknown version, --config-file requires \
158 clang-format version 12.0.0 or higher",
159 ));
160 }
161
162 let version = self.version.as_ref().unwrap();
163 if version.major < 9u8 {
164 return Err(io::Error::new(
165 io::ErrorKind::Other,
166 format!(
167 "Invalid version {}, --config-file check requires \
168 clang-format version 12.0.0 or higher",
169 self.get_version().unwrap()
170 ),
171 ));
172 }
173
174 Ok(())
175 }
176}
177
178impl Clone for Runner {
179 fn clone(&self) -> Runner {
180 Runner {
181 cmd: path::PathBuf::from(self.cmd.as_path()),
182 version: self.version.clone(),
183 }
184 }
185}