Skip to content

Commit 42eb838

Browse files
committed
refactor deferred command and make it compatible with new commandstate, remove extra caching logic from run and re-structure the changes
1 parent 70d7761 commit 42eb838

File tree

2 files changed

+100
-90
lines changed

2 files changed

+100
-90
lines changed

src/bootstrap/src/utils/exec.rs

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,12 @@
22
//!
33
//! This module provides a structured way to execute and manage commands efficiently,
44
//! ensuring controlled failure handling and output management.
5-
#![allow(warnings)]
65
7-
use std::collections::HashMap;
86
use std::ffi::{OsStr, OsString};
97
use std::fmt::{Debug, Formatter};
10-
use std::hash::{Hash, Hasher};
8+
use std::hash::Hash;
119
use std::path::Path;
1210
use std::process::{Command, CommandArgs, CommandEnvs, ExitStatus, Output, Stdio};
13-
use std::sync::Mutex;
1411

1512
use build_helper::ci::CiEnv;
1613
use build_helper::drop_bomb::DropBomb;
@@ -59,7 +56,7 @@ impl OutputMode {
5956
pub struct CommandCacheKey {
6057
program: OsString,
6158
args: Vec<OsString>,
62-
envs: Vec<(OsString, OsString)>,
59+
envs: Vec<(OsString, Option<OsString>)>,
6360
cwd: Option<PathBuf>,
6461
}
6562

@@ -90,18 +87,14 @@ pub struct BootstrapCommand {
9087
impl<'a> BootstrapCommand {
9188
#[track_caller]
9289
pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
93-
Self { should_cache: true, ..Command::new(program).into() }
90+
Command::new(program).into()
9491
}
9592
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
9693
self.command.arg(arg.as_ref());
9794
self
9895
}
9996

100-
pub fn should_cache(&self) -> bool {
101-
self.should_cache
102-
}
103-
104-
pub fn cache_never(&mut self) -> &mut Self {
97+
pub fn do_not_cache(&mut self) -> &mut Self {
10598
self.should_cache = false;
10699
self
107100
}
@@ -111,9 +104,7 @@ impl<'a> BootstrapCommand {
111104
I: IntoIterator<Item = S>,
112105
S: AsRef<OsStr>,
113106
{
114-
args.into_iter().for_each(|arg| {
115-
self.arg(arg);
116-
});
107+
self.command.args(args);
117108
self
118109
}
119110

@@ -212,7 +203,7 @@ impl<'a> BootstrapCommand {
212203
// command will be handled. Caching must also be avoided here, as the inner command could be
213204
// modified externally without us being aware.
214205
self.mark_as_executed();
215-
self.cache_never();
206+
self.do_not_cache();
216207
&mut self.command
217208
}
218209

@@ -240,7 +231,19 @@ impl<'a> BootstrapCommand {
240231
}
241232

242233
pub fn cache_key(&self) -> Option<CommandCacheKey> {
243-
(!self.should_cache).then(|| self.into())
234+
if !self.should_cache {
235+
return None;
236+
}
237+
let command = &self.command;
238+
Some(CommandCacheKey {
239+
program: command.get_program().into(),
240+
args: command.get_args().map(OsStr::to_os_string).collect(),
241+
envs: command
242+
.get_envs()
243+
.map(|(k, v)| (k.to_os_string(), v.map(|val| val.to_os_string())))
244+
.collect(),
245+
cwd: command.get_current_dir().map(Path::to_path_buf),
246+
})
244247
}
245248
}
246249

@@ -256,7 +259,7 @@ impl From<Command> for BootstrapCommand {
256259
fn from(command: Command) -> Self {
257260
let program = command.get_program().to_owned();
258261
Self {
259-
should_cache: false,
262+
should_cache: true,
260263
command,
261264
failure_behavior: BehaviorOnFailure::Exit,
262265
run_always: false,
@@ -265,21 +268,6 @@ impl From<Command> for BootstrapCommand {
265268
}
266269
}
267270

268-
impl From<&BootstrapCommand> for CommandCacheKey {
269-
fn from(value: &BootstrapCommand) -> Self {
270-
let command = &value.command;
271-
CommandCacheKey {
272-
program: command.get_program().into(),
273-
args: command.get_args().map(OsStr::to_os_string).collect(),
274-
envs: command
275-
.get_envs()
276-
.filter_map(|(k, v_opt)| v_opt.map(|v| (k.to_owned(), v.to_owned())))
277-
.collect(),
278-
cwd: command.get_current_dir().map(Path::to_path_buf),
279-
}
280-
}
281-
}
282-
283271
/// Represents the current status of `BootstrapCommand`.
284272
#[derive(Clone, PartialEq)]
285273
enum CommandStatus {

src/bootstrap/src/utils/execution_context.rs

Lines changed: 80 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
//! This module provides the [`ExecutionContext`] type, which holds global configuration
44
//! relevant during the execution of commands in bootstrap. This includes dry-run
55
//! mode, verbosity level, and behavior on failure.
6-
#![allow(warnings)]
76
use std::collections::HashMap;
87
use std::panic::Location;
98
use std::process::Child;
@@ -37,14 +36,15 @@ enum CommandState<'a> {
3736
stdout: OutputMode,
3837
stderr: OutputMode,
3938
executed_at: &'a Location<'a>,
39+
cache_key: Option<CommandCacheKey>,
4040
},
4141
}
4242

43-
impl CommandCache {
44-
pub fn new() -> Self {
45-
Self { cache: Mutex::new(HashMap::new()) }
46-
}
43+
pub struct DeferredCommand<'a> {
44+
state: CommandState<'a>,
45+
}
4746

47+
impl CommandCache {
4848
pub fn get(&self, key: &CommandCacheKey) -> Option<CommandOutput> {
4949
self.cache.lock().unwrap().get(key).cloned()
5050
}
@@ -122,13 +122,33 @@ impl ExecutionContext {
122122
stdout: OutputMode,
123123
stderr: OutputMode,
124124
) -> DeferredCommand<'a> {
125+
let cache_key = command.cache_key();
126+
127+
if let Some(cached_output) = cache_key.as_ref().and_then(|key| self.command_cache.get(key))
128+
{
129+
command.mark_as_executed();
130+
131+
self.verbose(|| println!("Cache hit: {command:?}"));
132+
133+
return DeferredCommand { state: CommandState::Cached(cached_output) };
134+
}
135+
125136
command.mark_as_executed();
126137

127138
let created_at = command.get_created_location();
128139
let executed_at = std::panic::Location::caller();
129140

130141
if self.dry_run() && !command.run_always {
131-
return DeferredCommand { process: None, stdout, stderr, command, executed_at };
142+
return DeferredCommand {
143+
state: CommandState::Deferred {
144+
process: None,
145+
command,
146+
stdout,
147+
stderr,
148+
executed_at,
149+
cache_key,
150+
},
151+
};
132152
}
133153

134154
#[cfg(feature = "tracing")]
@@ -144,7 +164,16 @@ impl ExecutionContext {
144164

145165
let child = cmd.spawn();
146166

147-
DeferredCommand { process: Some(child), stdout, stderr, command, executed_at }
167+
DeferredCommand {
168+
state: CommandState::Deferred {
169+
process: Some(child),
170+
command,
171+
stdout,
172+
stderr,
173+
executed_at,
174+
cache_key,
175+
},
176+
}
148177
}
149178

150179
/// Execute a command and return its output.
@@ -157,29 +186,7 @@ impl ExecutionContext {
157186
stdout: OutputMode,
158187
stderr: OutputMode,
159188
) -> CommandOutput {
160-
let cache_key = command.cache_key();
161-
162-
if let Some(cached_output) = cache_key.as_ref().and_then(|key| self.command_cache.get(key))
163-
{
164-
command.mark_as_executed();
165-
166-
if self.dry_run() && !command.run_always {
167-
return CommandOutput::default();
168-
}
169-
170-
self.verbose(|| println!("Cache hit: {:?}", command));
171-
return cached_output;
172-
}
173-
174-
let output = self.start(command, stdout, stderr).wait_for_output(self);
175-
176-
if !self.dry_run() || command.run_always {
177-
if let Some(cache_key) = cache_key {
178-
self.command_cache.insert(cache_key, output.clone());
179-
}
180-
}
181-
182-
output
189+
self.start(command, stdout, stderr).wait_for_output(self)
183190
}
184191

185192
fn fail(&self, message: &str, output: CommandOutput) -> ! {
@@ -212,33 +219,50 @@ impl AsRef<ExecutionContext> for ExecutionContext {
212219
}
213220
}
214221

215-
pub struct DeferredCommand<'a> {
216-
process: Option<Result<Child, std::io::Error>>,
217-
command: &'a mut BootstrapCommand,
218-
stdout: OutputMode,
219-
stderr: OutputMode,
220-
executed_at: &'a Location<'a>,
221-
}
222-
223222
impl<'a> DeferredCommand<'a> {
224-
pub fn wait_for_output(mut self, exec_ctx: impl AsRef<ExecutionContext>) -> CommandOutput {
225-
let exec_ctx = exec_ctx.as_ref();
223+
pub fn wait_for_output(self, exec_ctx: impl AsRef<ExecutionContext>) -> CommandOutput {
224+
match self.state {
225+
CommandState::Cached(output) => output,
226+
CommandState::Deferred { process, command, stdout, stderr, executed_at, cache_key } => {
227+
let exec_ctx = exec_ctx.as_ref();
228+
229+
let output =
230+
Self::finish_process(process, command, stdout, stderr, executed_at, exec_ctx);
231+
232+
if (!exec_ctx.dry_run() || command.run_always)
233+
&& let (Some(cache_key), Some(_)) = (&cache_key, output.status())
234+
{
235+
exec_ctx.command_cache.insert(cache_key.clone(), output.clone());
236+
}
237+
238+
output
239+
}
240+
}
241+
}
226242

227-
let process = match self.process.take() {
243+
pub fn finish_process(
244+
mut process: Option<Result<Child, std::io::Error>>,
245+
command: &mut BootstrapCommand,
246+
stdout: OutputMode,
247+
stderr: OutputMode,
248+
executed_at: &'a std::panic::Location<'a>,
249+
exec_ctx: &ExecutionContext,
250+
) -> CommandOutput {
251+
let process = match process.take() {
228252
Some(p) => p,
229253
None => return CommandOutput::default(),
230254
};
231255

232-
let created_at = self.command.get_created_location();
233-
let executed_at = self.executed_at;
256+
let created_at = command.get_created_location();
257+
let executed_at = executed_at;
234258

235259
let mut message = String::new();
236260

237261
let output = match process {
238262
Ok(child) => match child.wait_with_output() {
239263
Ok(result) if result.status.success() => {
240264
// Successful execution
241-
CommandOutput::from_output(result, self.stdout, self.stderr)
265+
CommandOutput::from_output(result, stdout, stderr)
242266
}
243267
Ok(result) => {
244268
// Command ran but failed
@@ -247,20 +271,20 @@ impl<'a> DeferredCommand<'a> {
247271
writeln!(
248272
message,
249273
r#"
250-
Command {:?} did not execute successfully.
274+
Command {command:?} did not execute successfully.
251275
Expected success, got {}
252276
Created at: {created_at}
253277
Executed at: {executed_at}"#,
254-
self.command, result.status,
278+
result.status,
255279
)
256280
.unwrap();
257281

258-
let output = CommandOutput::from_output(result, self.stdout, self.stderr);
282+
let output = CommandOutput::from_output(result, stdout, stderr);
259283

260-
if self.stdout.captures() {
284+
if stdout.captures() {
261285
writeln!(message, "\nSTDOUT ----\n{}", output.stdout().trim()).unwrap();
262286
}
263-
if self.stderr.captures() {
287+
if stderr.captures() {
264288
writeln!(message, "\nSTDERR ----\n{}", output.stderr().trim()).unwrap();
265289
}
266290

@@ -272,13 +296,12 @@ Executed at: {executed_at}"#,
272296

273297
writeln!(
274298
message,
275-
"\n\nCommand {:?} did not execute successfully.\
276-
\nIt was not possible to execute the command: {e:?}",
277-
self.command
299+
"\n\nCommand {command:?} did not execute successfully.\
300+
\nIt was not possible to execute the command: {e:?}"
278301
)
279302
.unwrap();
280303

281-
CommandOutput::did_not_start(self.stdout, self.stderr)
304+
CommandOutput::did_not_start(stdout, stderr)
282305
}
283306
},
284307
Err(e) => {
@@ -287,18 +310,17 @@ Executed at: {executed_at}"#,
287310

288311
writeln!(
289312
message,
290-
"\n\nCommand {:?} did not execute successfully.\
291-
\nIt was not possible to execute the command: {e:?}",
292-
self.command
313+
"\n\nCommand {command:?} did not execute successfully.\
314+
\nIt was not possible to execute the command: {e:?}"
293315
)
294316
.unwrap();
295317

296-
CommandOutput::did_not_start(self.stdout, self.stderr)
318+
CommandOutput::did_not_start(stdout, stderr)
297319
}
298320
};
299321

300322
if !output.is_success() {
301-
match self.command.failure_behavior {
323+
match command.failure_behavior {
302324
BehaviorOnFailure::DelayFail => {
303325
if exec_ctx.fail_fast {
304326
exec_ctx.fail(&message, output);

0 commit comments

Comments
 (0)