Skip to content

Commit 6538190

Browse files
committed
exec: Execute batches before they get too long
Fixes sharkdp#410.
1 parent de27835 commit 6538190

File tree

6 files changed

+206
-81
lines changed

6 files changed

+206
-81
lines changed

Cargo.lock

+29-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ version_check = "0.9"
3636

3737
[dependencies]
3838
ansi_term = "0.12"
39+
argmax = "0.3.0"
3940
atty = "0.2"
4041
ignore = "0.4.3"
4142
num_cpus = "1.13"

src/exec/command.rs

+20-11
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::io;
22
use std::io::Write;
3-
use std::process::Command;
43
use std::sync::Mutex;
54

5+
use argmax::Command;
6+
67
use crate::error::print_error;
78
use crate::exit_codes::ExitCode;
89

@@ -50,13 +51,18 @@ impl<'a> OutputBuffer<'a> {
5051
}
5152

5253
/// Executes a command.
53-
pub fn execute_commands<I: Iterator<Item = Command>>(
54+
pub fn execute_commands<I: Iterator<Item = io::Result<Command>>>(
5455
cmds: I,
5556
out_perm: &Mutex<()>,
5657
enable_output_buffering: bool,
5758
) -> ExitCode {
5859
let mut output_buffer = OutputBuffer::new(out_perm);
59-
for mut cmd in cmds {
60+
for result in cmds {
61+
let mut cmd = match result {
62+
Ok(cmd) => cmd,
63+
Err(e) => return handle_cmd_error(None, e),
64+
};
65+
6066
// Spawn the supplied command.
6167
let output = if enable_output_buffering {
6268
cmd.output()
@@ -79,20 +85,23 @@ pub fn execute_commands<I: Iterator<Item = Command>>(
7985
}
8086
Err(why) => {
8187
output_buffer.write();
82-
return handle_cmd_error(&cmd, why);
88+
return handle_cmd_error(Some(&cmd), why);
8389
}
8490
}
8591
}
8692
output_buffer.write();
8793
ExitCode::Success
8894
}
8995

90-
pub fn handle_cmd_error(cmd: &Command, err: io::Error) -> ExitCode {
91-
if err.kind() == io::ErrorKind::NotFound {
92-
print_error(format!("Command not found: {:?}", cmd));
93-
ExitCode::GeneralError
94-
} else {
95-
print_error(format!("Problem while executing command: {}", err));
96-
ExitCode::GeneralError
96+
pub fn handle_cmd_error(cmd: Option<&Command>, err: io::Error) -> ExitCode {
97+
match (cmd, err) {
98+
(Some(cmd), err) if err.kind() == io::ErrorKind::NotFound => {
99+
print_error(format!("Command not found: {:?}", cmd));
100+
ExitCode::GeneralError
101+
}
102+
(_, err) => {
103+
print_error(format!("Problem while executing command: {}", err));
104+
ExitCode::GeneralError
105+
}
97106
}
98107
}

src/exec/job.rs

+1-12
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,6 @@ pub fn batch(
6262
None
6363
}
6464
});
65-
if limit == 0 {
66-
// no limit
67-
return cmd.execute_batch(paths);
68-
}
6965

70-
let mut exit_codes = Vec::new();
71-
let mut peekable = paths.peekable();
72-
while peekable.peek().is_some() {
73-
let limited = peekable.by_ref().take(limit);
74-
let exit_code = cmd.execute_batch(limited);
75-
exit_codes.push(exit_code);
76-
}
77-
merge_exitcodes(exit_codes)
66+
cmd.execute_batch(paths, limit)
7867
}

src/exec/mod.rs

+112-45
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ mod token;
55

66
use std::borrow::Cow;
77
use std::ffi::{OsStr, OsString};
8+
use std::io;
9+
use std::iter;
810
use std::path::{Component, Path, PathBuf, Prefix};
9-
use std::process::{Command, Stdio};
11+
use std::process::Stdio;
1012
use std::sync::{Arc, Mutex};
1113

1214
use anyhow::{bail, Result};
15+
use argmax::Command;
1316
use once_cell::sync::Lazy;
1417
use regex::Regex;
1518

@@ -89,20 +92,117 @@ impl CommandSet {
8992
execute_commands(commands, &out_perm, buffer_output)
9093
}
9194

92-
pub fn execute_batch<I>(&self, paths: I) -> ExitCode
95+
pub fn execute_batch<I>(&self, paths: I, limit: usize) -> ExitCode
9396
where
9497
I: Iterator<Item = PathBuf>,
9598
{
9699
let path_separator = self.path_separator.as_deref();
97-
let mut paths = paths.collect::<Vec<_>>();
98-
paths.sort();
99-
for cmd in &self.commands {
100-
let exit = cmd.generate_and_execute_batch(&paths, path_separator);
101-
if exit != ExitCode::Success {
102-
return exit;
100+
101+
let builders: io::Result<Vec<_>> = self
102+
.commands
103+
.iter()
104+
.map(|c| CommandBuilder::new(c, limit))
105+
.collect();
106+
107+
match builders {
108+
Ok(mut builders) => {
109+
for path in paths {
110+
for builder in &mut builders {
111+
if let Err(e) = builder.push(&path, path_separator) {
112+
return handle_cmd_error(Some(&builder.cmd), e);
113+
}
114+
}
115+
}
116+
117+
for builder in &mut builders {
118+
if let Err(e) = builder.finish() {
119+
return handle_cmd_error(Some(&builder.cmd), e);
120+
}
121+
}
122+
123+
ExitCode::Success
103124
}
125+
Err(e) => handle_cmd_error(None, e),
126+
}
127+
}
128+
}
129+
130+
/// Represents a multi-exec command as it is built.
131+
#[derive(Debug)]
132+
struct CommandBuilder {
133+
pre_args: Vec<OsString>,
134+
path_arg: ArgumentTemplate,
135+
post_args: Vec<OsString>,
136+
cmd: Command,
137+
count: usize,
138+
limit: usize,
139+
}
140+
141+
impl CommandBuilder {
142+
fn new(template: &CommandTemplate, limit: usize) -> io::Result<Self> {
143+
let mut pre_args = vec![];
144+
let mut path_arg = None;
145+
let mut post_args = vec![];
146+
147+
for arg in &template.args {
148+
if arg.has_tokens() {
149+
path_arg = Some(arg.clone());
150+
} else if path_arg == None {
151+
pre_args.push(arg.generate("", None));
152+
} else {
153+
post_args.push(arg.generate("", None));
154+
}
155+
}
156+
157+
let cmd = Self::new_command(&pre_args)?;
158+
159+
Ok(Self {
160+
pre_args,
161+
path_arg: path_arg.unwrap(),
162+
post_args,
163+
cmd,
164+
count: 0,
165+
limit,
166+
})
167+
}
168+
169+
fn new_command(pre_args: &[OsString]) -> io::Result<Command> {
170+
let mut cmd = Command::new(&pre_args[0]);
171+
cmd.stdin(Stdio::inherit());
172+
cmd.stdout(Stdio::inherit());
173+
cmd.stderr(Stdio::inherit());
174+
cmd.try_args(&pre_args[1..])?;
175+
Ok(cmd)
176+
}
177+
178+
fn push(&mut self, path: &Path, separator: Option<&str>) -> io::Result<()> {
179+
if self.limit > 0 && self.count >= self.limit {
180+
self.finish()?;
104181
}
105-
ExitCode::Success
182+
183+
let arg = self.path_arg.generate(path, separator);
184+
if !self
185+
.cmd
186+
.args_would_fit(iter::once(&arg).chain(&self.post_args))
187+
{
188+
self.finish()?;
189+
}
190+
191+
self.cmd.try_arg(arg)?;
192+
self.count += 1;
193+
Ok(())
194+
}
195+
196+
fn finish(&mut self) -> io::Result<()> {
197+
if self.count > 0 {
198+
self.cmd.try_args(&self.post_args)?;
199+
self.cmd.status()?;
200+
201+
self.cmd = Self::new_command(&self.pre_args)?;
202+
self.count = 0;
203+
}
204+
205+
Ok(())
106206
}
107207
}
108208

@@ -192,45 +292,12 @@ impl CommandTemplate {
192292
///
193293
/// Using the internal `args` field, and a supplied `input` variable, a `Command` will be
194294
/// build.
195-
fn generate(&self, input: &Path, path_separator: Option<&str>) -> Command {
295+
fn generate(&self, input: &Path, path_separator: Option<&str>) -> io::Result<Command> {
196296
let mut cmd = Command::new(self.args[0].generate(&input, path_separator));
197297
for arg in &self.args[1..] {
198-
cmd.arg(arg.generate(&input, path_separator));
199-
}
200-
cmd
201-
}
202-
203-
fn generate_and_execute_batch(
204-
&self,
205-
paths: &[PathBuf],
206-
path_separator: Option<&str>,
207-
) -> ExitCode {
208-
let mut cmd = Command::new(self.args[0].generate("", None));
209-
cmd.stdin(Stdio::inherit());
210-
cmd.stdout(Stdio::inherit());
211-
cmd.stderr(Stdio::inherit());
212-
213-
let mut has_path = false;
214-
215-
for arg in &self.args[1..] {
216-
if arg.has_tokens() {
217-
for path in paths {
218-
cmd.arg(arg.generate(path, path_separator));
219-
has_path = true;
220-
}
221-
} else {
222-
cmd.arg(arg.generate("", None));
223-
}
224-
}
225-
226-
if has_path {
227-
match cmd.spawn().and_then(|mut c| c.wait()) {
228-
Ok(_) => ExitCode::Success,
229-
Err(e) => handle_cmd_error(&cmd, e),
230-
}
231-
} else {
232-
ExitCode::Success
298+
cmd.try_arg(arg.generate(&input, path_separator))?;
233299
}
300+
Ok(cmd)
234301
}
235302
}
236303

0 commit comments

Comments
 (0)