Skip to content

Commit 5406eca

Browse files
committed
feat: use tokio::process::Command and tokio::fs
addresses #192 This coverts functions that run and capture clang tools' suggestions from blocking to async API.
1 parent 6523512 commit 5406eca

File tree

5 files changed

+140
-115
lines changed

5 files changed

+140
-115
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cpp-linter/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ reqwest = "0.12.24"
2828
semver = "1.0.27"
2929
serde = { version = "1.0.228", features = ["derive"] }
3030
serde_json = "1.0.145"
31-
tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread"] }
31+
tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread", "process", "fs"] }
3232
tokio-macros = "2.5.0"
3333
tokio-stream = "0.1.17"
3434
which = "8.0.0"

cpp-linter/src/clang_tools/clang_format.rs

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
//! This module holds functionality specific to running clang-format and parsing it's
22
//! output.
33
4-
use std::{
5-
fs,
6-
process::Command,
7-
sync::{Arc, Mutex, MutexGuard},
8-
};
4+
use std::sync::{Arc, Mutex};
95

10-
use anyhow::{Context, Result};
6+
use anyhow::{anyhow, Context, Result};
117
use log::Level;
128
use serde::Deserialize;
9+
use tokio::{fs, process::Command};
1310

1411
// project-specific crates/modules
1512
use super::MakeSuggestions;
@@ -81,58 +78,61 @@ pub fn tally_format_advice(files: &[Arc<Mutex<FileObj>>]) -> u64 {
8178
}
8279

8380
/// Run clang-tidy for a specific `file`, then parse and return it's XML output.
84-
pub fn run_clang_format(
85-
file: &mut MutexGuard<FileObj>,
81+
pub async fn run_clang_format(
82+
file: &Arc<Mutex<FileObj>>,
8683
clang_params: &ClangParams,
8784
) -> Result<Vec<(log::Level, String)>> {
88-
let mut cmd = Command::new(clang_params.clang_format_command.as_ref().unwrap());
8985
let mut logs = vec![];
90-
cmd.args(["--style", &clang_params.style]);
91-
let ranges = file.get_ranges(&clang_params.lines_changed_only);
92-
for range in &ranges {
93-
cmd.arg(format!("--lines={}:{}", range.start(), range.end()));
94-
}
95-
let file_name = file.name.to_string_lossy().to_string();
96-
cmd.arg(file.name.to_path_buf().as_os_str());
86+
let program = clang_params.clang_format_command.as_ref().unwrap();
87+
let (file_name, mut args, ranges) = {
88+
let mut args = vec![];
89+
let file = file
90+
.lock()
91+
.map_err(|e| anyhow!("Failed to lock mutex: {e:?}"))?;
92+
args.extend(["--style".to_string(), clang_params.style.clone()]);
93+
let ranges = file.get_ranges(&clang_params.lines_changed_only);
94+
for range in &ranges {
95+
args.push(format!("--lines={}:{}", range.start(), range.end()));
96+
}
97+
let file_name = file.name.to_string_lossy().to_string();
98+
(file_name, args, ranges)
99+
};
100+
let mut cmd = Command::new(program);
101+
cmd.args(&args);
97102
let patched = if !clang_params.format_review {
98103
None
99104
} else {
100105
logs.push((
101106
Level::Info,
102107
format!(
103-
"Getting format fixes with \"{} {}\"",
104-
clang_params
105-
.clang_format_command
106-
.as_ref()
107-
.unwrap()
108-
.to_str()
109-
.unwrap_or_default(),
110-
cmd.get_args()
111-
.map(|a| a.to_string_lossy())
112-
.collect::<Vec<_>>()
113-
.join(" ")
108+
"Getting format fixes with \"{} {} {}\"",
109+
program.to_string_lossy(),
110+
args.join(" "),
111+
&file_name
114112
),
115113
));
114+
cmd.arg(&file_name);
116115
Some(
117116
cmd.output()
117+
.await
118118
.with_context(|| format!("Failed to get fixes from clang-format: {file_name}"))?
119119
.stdout,
120120
)
121121
};
122-
cmd.arg("--output-replacements-xml");
122+
args.extend(["--output-replacements-xml".to_string(), file_name.clone()]);
123+
let mut cmd = Command::new(program);
124+
cmd.args(&args);
123125
logs.push((
124126
log::Level::Info,
125127
format!(
126128
"Running \"{} {}\"",
127-
cmd.get_program().to_string_lossy(),
128-
cmd.get_args()
129-
.map(|x| x.to_string_lossy())
130-
.collect::<Vec<_>>()
131-
.join(" ")
129+
program.to_string_lossy(),
130+
args.join(" ")
132131
),
133132
));
134133
let output = cmd
135134
.output()
135+
.await
136136
.with_context(|| format!("Failed to get replacements from clang-format: {file_name}"))?;
137137
if !output.stderr.is_empty() || !output.status.success() {
138138
logs.push((
@@ -151,14 +151,11 @@ pub fn run_clang_format(
151151
format!("Failed to parse XML output from clang-format for {file_name}")
152152
})?
153153
} else {
154-
FormatAdvice {
155-
replacements: vec![],
156-
patched: None,
157-
}
154+
FormatAdvice::default()
158155
};
159156
format_advice.patched = patched;
160157
if !format_advice.replacements.is_empty() {
161-
let original_contents = fs::read(&file.name).with_context(|| {
158+
let original_contents = fs::read(&file_name).await.with_context(|| {
162159
format!(
163160
"Failed to read file's original content before translating byte offsets: {file_name}",
164161
)
@@ -181,7 +178,12 @@ pub fn run_clang_format(
181178
}
182179
format_advice.replacements = filtered_replacements;
183180
}
184-
file.format_advice = Some(format_advice);
181+
{
182+
let mut file = file
183+
.lock()
184+
.map_err(|e| anyhow!("Failed to lock mutex: {e:?}"))?;
185+
file.format_advice = Some(format_advice);
186+
}
185187
Ok(logs)
186188
}
187189

cpp-linter/src/clang_tools/clang_tidy.rs

Lines changed: 68 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@
33
44
use std::{
55
env::{consts::OS, current_dir},
6-
fs,
76
path::PathBuf,
8-
process::Command,
9-
sync::{Arc, Mutex, MutexGuard},
7+
sync::{Arc, Mutex},
108
};
119

1210
// non-std crates
13-
use anyhow::{Context, Result};
11+
use anyhow::{anyhow, Context, Result};
1412
use regex::Regex;
1513
use serde::Deserialize;
14+
use tokio::{fs, process::Command};
1615

1716
// project-specific modules/crates
1817
use super::MakeSuggestions;
@@ -249,63 +248,73 @@ pub fn tally_tidy_advice(files: &[Arc<Mutex<FileObj>>]) -> u64 {
249248
}
250249

251250
/// Run clang-tidy, then parse and return it's output.
252-
pub fn run_clang_tidy(
253-
file: &mut MutexGuard<FileObj>,
251+
pub async fn run_clang_tidy(
252+
file: &Arc<Mutex<FileObj>>,
254253
clang_params: &ClangParams,
255254
) -> Result<Vec<(log::Level, std::string::String)>> {
256-
let mut cmd = Command::new(clang_params.clang_tidy_command.as_ref().unwrap());
257255
let mut logs = vec![];
258-
if !clang_params.tidy_checks.is_empty() {
259-
cmd.args(["-checks", &clang_params.tidy_checks]);
260-
}
261-
if let Some(db) = &clang_params.database {
262-
cmd.args(["-p", &db.to_string_lossy()]);
263-
}
264-
for arg in &clang_params.extra_args {
265-
cmd.args(["--extra-arg", format!("\"{}\"", arg).as_str()]);
266-
}
267-
let file_name = file.name.to_string_lossy().to_string();
268-
if clang_params.lines_changed_only != LinesChangedOnly::Off {
269-
let ranges = file.get_ranges(&clang_params.lines_changed_only);
270-
if !ranges.is_empty() {
271-
let filter = format!(
272-
"[{{\"name\":{:?},\"lines\":{:?}}}]",
273-
&file_name.replace('/', if OS == "windows" { "\\" } else { "/" }),
274-
ranges
275-
.iter()
276-
.map(|r| [r.start(), r.end()])
277-
.collect::<Vec<_>>()
278-
);
279-
cmd.args(["--line-filter", filter.as_str()]);
256+
let (file_name, mut args) = {
257+
let mut args = vec![];
258+
let file = file
259+
.lock()
260+
.map_err(|e| anyhow!("Failed to lock mutex: {e:?}"))?;
261+
let file_name = file.name.to_string_lossy().to_string();
262+
if !clang_params.tidy_checks.is_empty() {
263+
args.extend(["-checks".to_string(), clang_params.tidy_checks.to_owned()]);
280264
}
281-
}
265+
if let Some(db) = &clang_params.database {
266+
args.extend(["-p".to_string(), db.to_string_lossy().to_string()]);
267+
}
268+
for arg in &clang_params.extra_args {
269+
args.extend(["--extra-arg".to_string(), format!("\"{}\"", arg)]);
270+
}
271+
if clang_params.lines_changed_only != LinesChangedOnly::Off {
272+
let ranges = file.get_ranges(&clang_params.lines_changed_only);
273+
if !ranges.is_empty() {
274+
let filter = format!(
275+
"[{{\"name\":{:?},\"lines\":{:?}}}]",
276+
&file_name.replace('/', if OS == "windows" { "\\" } else { "/" }),
277+
ranges
278+
.iter()
279+
.map(|r| [r.start(), r.end()])
280+
.collect::<Vec<_>>()
281+
);
282+
args.extend(["--line-filter".to_string(), filter]);
283+
}
284+
}
285+
(file_name, args)
286+
};
282287
let original_content = if !clang_params.tidy_review {
283288
None
284289
} else {
285-
cmd.arg("--fix-errors");
286-
Some(fs::read_to_string(&file.name).with_context(|| {
290+
args.push("--fix-errors".to_string());
291+
Some(fs::read_to_string(&file_name).await.with_context(|| {
287292
format!(
288293
"Failed to cache file's original content before applying clang-tidy changes: {}",
289294
file_name.clone()
290295
)
291296
})?)
292297
};
293298
if !clang_params.style.is_empty() {
294-
cmd.args(["--format-style", clang_params.style.as_str()]);
299+
args.extend(["--format-style".to_string(), clang_params.style.to_owned()]);
295300
}
296-
cmd.arg(file.name.to_string_lossy().as_ref());
301+
args.push(file_name.clone());
302+
let program = clang_params.clang_tidy_command.as_ref().unwrap();
303+
let mut cmd = Command::new(program);
304+
cmd.args(&args);
297305
logs.push((
298306
log::Level::Info,
299307
format!(
300308
"Running \"{} {}\"",
301-
cmd.get_program().to_string_lossy(),
302-
cmd.get_args()
303-
.map(|x| x.to_string_lossy())
304-
.collect::<Vec<_>>()
305-
.join(" ")
309+
program.to_string_lossy(),
310+
args.join(" ")
306311
),
307312
));
308-
let output = cmd.output().unwrap();
313+
// ok to unwrap()
314+
let output = cmd
315+
.output()
316+
.await
317+
.with_context(|| format!("Failed to run clang-tidy on file: {}", file_name.clone()))?;
309318
logs.push((
310319
log::Level::Debug,
311320
format!(
@@ -322,22 +331,25 @@ pub fn run_clang_tidy(
322331
),
323332
));
324333
}
325-
file.tidy_advice = Some(parse_tidy_output(
326-
&output.stdout,
327-
&clang_params.database_json,
328-
)?);
334+
let mut tidy_advice = parse_tidy_output(&output.stdout, &clang_params.database_json)?;
329335
if clang_params.tidy_review {
330-
if let Some(tidy_advice) = &mut file.tidy_advice {
331-
// cache file changes in a buffer and restore the original contents for further analysis
332-
tidy_advice.patched =
333-
Some(fs::read(&file_name).with_context(|| {
334-
format!("Failed to read changes from clang-tidy: {file_name}")
335-
})?);
336-
}
336+
// cache file changes in a buffer and restore the original contents for further analysis
337+
tidy_advice.patched = Some(
338+
fs::read(&file_name)
339+
.await
340+
.with_context(|| format!("Failed to read changes from clang-tidy: {file_name}"))?,
341+
);
337342
// original_content is guaranteed to be Some() value at this point
338343
fs::write(&file_name, original_content.unwrap())
344+
.await
339345
.with_context(|| format!("Failed to restore file's original content: {file_name}"))?;
340346
}
347+
{
348+
let mut file = file
349+
.lock()
350+
.map_err(|e| anyhow!("Failed to lock mutex: {e:?}"))?;
351+
file.tidy_advice = Some(tidy_advice);
352+
}
341353
Ok(logs)
342354
}
343355

@@ -420,8 +432,8 @@ mod test {
420432
)
421433
}
422434

423-
#[test]
424-
fn use_extra_args() {
435+
#[tokio::test]
436+
async fn use_extra_args() {
425437
let exe_path = ClangTool::ClangTidy
426438
.get_exe_path(
427439
&RequestedVersion::from_str(
@@ -431,7 +443,7 @@ mod test {
431443
)
432444
.unwrap();
433445
let file = FileObj::new(PathBuf::from("tests/demo/demo.cpp"));
434-
let arc_ref = Arc::new(Mutex::new(file));
446+
let arc_file = Arc::new(Mutex::new(file));
435447
let extra_args = vec!["-std=c++17".to_string(), "-Wall".to_string()];
436448
let clang_params = ClangParams {
437449
style: "".to_string(),
@@ -447,8 +459,8 @@ mod test {
447459
clang_tidy_command: Some(exe_path),
448460
clang_format_command: None,
449461
};
450-
let mut file_lock = arc_ref.lock().unwrap();
451-
let logs = run_clang_tidy(&mut file_lock, &clang_params)
462+
let logs = run_clang_tidy(&arc_file, &clang_params)
463+
.await
452464
.unwrap()
453465
.into_iter()
454466
.filter_map(|(_lvl, msg)| {

0 commit comments

Comments
 (0)