Skip to content

Commit 42875e1

Browse files
committed
add command cache key, move to osstring, add should cache to bootstrap command
1 parent 59cb00f commit 42875e1

File tree

2 files changed

+76
-19
lines changed

2 files changed

+76
-19
lines changed

src/bootstrap/src/utils/exec.rs

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#![allow(warnings)]
66

77
use std::collections::HashMap;
8-
use std::ffi::OsStr;
8+
use std::ffi::{OsStr, OsString};
99
use std::fmt::{Debug, Formatter};
1010
use std::hash::{Hash, Hasher};
1111
use std::path::Path;
@@ -55,6 +55,14 @@ impl OutputMode {
5555
}
5656
}
5757

58+
#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
59+
pub struct CommandCacheKey {
60+
program: OsString,
61+
args: Vec<OsString>,
62+
envs: Vec<(OsString, OsString)>,
63+
cwd: Option<PathBuf>,
64+
}
65+
5866
/// Wrapper around `std::process::Command`.
5967
///
6068
/// By default, the command will exit bootstrap if it fails.
@@ -69,33 +77,39 @@ impl OutputMode {
6977
/// [allow_failure]: BootstrapCommand::allow_failure
7078
/// [delay_failure]: BootstrapCommand::delay_failure
7179
pub struct BootstrapCommand {
72-
program: String,
73-
args: Vec<String>,
74-
envs: Vec<(String, String)>,
75-
cwd: Option<PathBuf>,
76-
80+
cache_key: CommandCacheKey,
7781
command: Command,
7882
pub failure_behavior: BehaviorOnFailure,
7983
// Run the command even during dry run
8084
pub run_in_dry_run: bool,
8185
// This field makes sure that each command is executed (or disarmed) before it is dropped,
8286
// to avoid forgetting to execute a command.
8387
drop_bomb: DropBomb,
88+
should_cache: bool,
8489
}
8590

8691
impl<'a> BootstrapCommand {
8792
#[track_caller]
8893
pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
89-
Command::new(program).into()
94+
Self {
95+
should_cache: true,
96+
cache_key: CommandCacheKey {
97+
program: program.as_ref().to_os_string(),
98+
..CommandCacheKey::default()
99+
},
100+
..Command::new(program).into()
101+
}
90102
}
91-
92103
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
93-
let arg_str = arg.as_ref().to_string_lossy().into_owned();
94-
self.args.push(arg_str.clone());
104+
self.cache_key.args.push(arg.as_ref().to_os_string());
95105
self.command.arg(arg.as_ref());
96106
self
97107
}
98108

109+
pub fn should_cache(&self) -> bool {
110+
self.should_cache
111+
}
112+
99113
pub fn args<I, S>(&mut self, args: I) -> &mut Self
100114
where
101115
I: IntoIterator<Item = S>,
@@ -112,9 +126,7 @@ impl<'a> BootstrapCommand {
112126
K: AsRef<OsStr>,
113127
V: AsRef<OsStr>,
114128
{
115-
let key_str = key.as_ref().to_string_lossy().into_owned();
116-
let val_str = val.as_ref().to_string_lossy().into_owned();
117-
self.envs.push((key_str.clone(), val_str.clone()));
129+
self.cache_key.envs.push((key.as_ref().to_os_string(), val.as_ref().to_os_string()));
118130
self.command.env(key, val);
119131
self
120132
}
@@ -133,7 +145,7 @@ impl<'a> BootstrapCommand {
133145
}
134146

135147
pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self {
136-
self.cwd = Some(dir.as_ref().to_path_buf());
148+
self.cache_key.cwd = Some(dir.as_ref().to_path_buf());
137149
self.command.current_dir(dir);
138150
self
139151
}
@@ -205,6 +217,7 @@ impl<'a> BootstrapCommand {
205217
// We don't know what will happen with the returned command, so we need to mark this
206218
// command as executed proactively.
207219
self.mark_as_executed();
220+
self.should_cache = false;
208221
&mut self.command
209222
}
210223

@@ -230,6 +243,10 @@ impl<'a> BootstrapCommand {
230243
self.env("TERM", "xterm").args(["--color", "always"]);
231244
}
232245
}
246+
247+
pub fn cache_key(&self) -> CommandCacheKey {
248+
self.cache_key.clone()
249+
}
233250
}
234251

235252
impl Debug for BootstrapCommand {
@@ -245,10 +262,8 @@ impl From<Command> for BootstrapCommand {
245262
let program = command.get_program().to_owned();
246263

247264
Self {
248-
program: program.clone().into_string().unwrap(),
249-
args: Vec::new(),
250-
envs: Vec::new(),
251-
cwd: None,
265+
cache_key: CommandCacheKey::default(),
266+
should_cache: false,
252267
command,
253268
failure_behavior: BehaviorOnFailure::Exit,
254269
run_in_dry_run: false,

src/bootstrap/src/utils/execution_context.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
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)]
7+
use std::collections::HashMap;
68
use std::panic::Location;
79
use std::process::Child;
810
use std::sync::{Arc, Mutex};
911

1012
use crate::core::config::DryRun;
1113
#[cfg(feature = "tracing")]
1214
use crate::trace_cmd;
15+
use crate::utils::exec::CommandCacheKey;
1316
use crate::{BehaviorOnFailure, BootstrapCommand, CommandOutput, OutputMode, exit};
1417

1518
#[derive(Clone, Default)]
@@ -18,6 +21,26 @@ pub struct ExecutionContext {
1821
verbose: u8,
1922
pub fail_fast: bool,
2023
delayed_failures: Arc<Mutex<Vec<String>>>,
24+
command_cache: Arc<CommandCache>,
25+
}
26+
27+
#[derive(Default)]
28+
pub struct CommandCache {
29+
cache: Mutex<HashMap<CommandCacheKey, CommandOutput>>,
30+
}
31+
32+
impl CommandCache {
33+
pub fn new() -> Self {
34+
Self { cache: Mutex::new(HashMap::new()) }
35+
}
36+
37+
pub fn get(&self, key: &CommandCacheKey) -> Option<CommandOutput> {
38+
self.cache.lock().unwrap().get(key).cloned()
39+
}
40+
41+
pub fn insert(&self, key: CommandCacheKey, output: CommandOutput) {
42+
self.cache.lock().unwrap().insert(key, output);
43+
}
2144
}
2245

2346
impl ExecutionContext {
@@ -123,7 +146,26 @@ impl ExecutionContext {
123146
stdout: OutputMode,
124147
stderr: OutputMode,
125148
) -> CommandOutput {
126-
self.start(command, stdout, stderr).wait_for_output(self)
149+
let cache_key = command.cache_key();
150+
151+
if command.should_cache()
152+
&& let Some(cached_output) = self.command_cache.get(&cache_key)
153+
{
154+
command.mark_as_executed();
155+
if self.dry_run() && !command.run_always {
156+
return CommandOutput::default();
157+
}
158+
self.verbose(|| println!("Cache hit: {:?}", command));
159+
return cached_output;
160+
}
161+
162+
let output = self.start(command, stdout, stderr).wait_for_output(self);
163+
164+
if !self.dry_run() || command.run_always && command.should_cache() {
165+
self.command_cache.insert(cache_key, output.clone());
166+
}
167+
168+
output
127169
}
128170

129171
fn fail(&self, message: &str, output: CommandOutput) -> ! {

0 commit comments

Comments
 (0)