about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorbit-aloo <sshourya17@gmail.com>2025-06-21 13:26:08 +0530
committerbit-aloo <sshourya17@gmail.com>2025-06-27 10:38:06 +0530
commit42875e1a3b5c57b2b5059a26f8371ee3e473eed7 (patch)
treeb87d9a7fdf9882ab3312a21a3971c426ad3ea711 /src
parent59cb00ffbe198c52881750ffb907daed728567e0 (diff)
downloadrust-42875e1a3b5c57b2b5059a26f8371ee3e473eed7.tar.gz
rust-42875e1a3b5c57b2b5059a26f8371ee3e473eed7.zip
add command cache key, move to osstring, add should cache to bootstrap command
Diffstat (limited to 'src')
-rw-r--r--src/bootstrap/src/utils/exec.rs51
-rw-r--r--src/bootstrap/src/utils/execution_context.rs44
2 files changed, 76 insertions, 19 deletions
diff --git a/src/bootstrap/src/utils/exec.rs b/src/bootstrap/src/utils/exec.rs
index 28b8fc2734e..545e059b5cf 100644
--- a/src/bootstrap/src/utils/exec.rs
+++ b/src/bootstrap/src/utils/exec.rs
@@ -5,7 +5,7 @@
 #![allow(warnings)]
 
 use std::collections::HashMap;
-use std::ffi::OsStr;
+use std::ffi::{OsStr, OsString};
 use std::fmt::{Debug, Formatter};
 use std::hash::{Hash, Hasher};
 use std::path::Path;
@@ -55,6 +55,14 @@ impl OutputMode {
     }
 }
 
+#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
+pub struct CommandCacheKey {
+    program: OsString,
+    args: Vec<OsString>,
+    envs: Vec<(OsString, OsString)>,
+    cwd: Option<PathBuf>,
+}
+
 /// Wrapper around `std::process::Command`.
 ///
 /// By default, the command will exit bootstrap if it fails.
@@ -69,11 +77,7 @@ impl OutputMode {
 /// [allow_failure]: BootstrapCommand::allow_failure
 /// [delay_failure]: BootstrapCommand::delay_failure
 pub struct BootstrapCommand {
-    program: String,
-    args: Vec<String>,
-    envs: Vec<(String, String)>,
-    cwd: Option<PathBuf>,
-
+    cache_key: CommandCacheKey,
     command: Command,
     pub failure_behavior: BehaviorOnFailure,
     // Run the command even during dry run
@@ -81,21 +85,31 @@ pub struct BootstrapCommand {
     // This field makes sure that each command is executed (or disarmed) before it is dropped,
     // to avoid forgetting to execute a command.
     drop_bomb: DropBomb,
+    should_cache: bool,
 }
 
 impl<'a> BootstrapCommand {
     #[track_caller]
     pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
-        Command::new(program).into()
+        Self {
+            should_cache: true,
+            cache_key: CommandCacheKey {
+                program: program.as_ref().to_os_string(),
+                ..CommandCacheKey::default()
+            },
+            ..Command::new(program).into()
+        }
     }
-
     pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
-        let arg_str = arg.as_ref().to_string_lossy().into_owned();
-        self.args.push(arg_str.clone());
+        self.cache_key.args.push(arg.as_ref().to_os_string());
         self.command.arg(arg.as_ref());
         self
     }
 
+    pub fn should_cache(&self) -> bool {
+        self.should_cache
+    }
+
     pub fn args<I, S>(&mut self, args: I) -> &mut Self
     where
         I: IntoIterator<Item = S>,
@@ -112,9 +126,7 @@ impl<'a> BootstrapCommand {
         K: AsRef<OsStr>,
         V: AsRef<OsStr>,
     {
-        let key_str = key.as_ref().to_string_lossy().into_owned();
-        let val_str = val.as_ref().to_string_lossy().into_owned();
-        self.envs.push((key_str.clone(), val_str.clone()));
+        self.cache_key.envs.push((key.as_ref().to_os_string(), val.as_ref().to_os_string()));
         self.command.env(key, val);
         self
     }
@@ -133,7 +145,7 @@ impl<'a> BootstrapCommand {
     }
 
     pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self {
-        self.cwd = Some(dir.as_ref().to_path_buf());
+        self.cache_key.cwd = Some(dir.as_ref().to_path_buf());
         self.command.current_dir(dir);
         self
     }
@@ -205,6 +217,7 @@ impl<'a> BootstrapCommand {
         // We don't know what will happen with the returned command, so we need to mark this
         // command as executed proactively.
         self.mark_as_executed();
+        self.should_cache = false;
         &mut self.command
     }
 
@@ -230,6 +243,10 @@ impl<'a> BootstrapCommand {
             self.env("TERM", "xterm").args(["--color", "always"]);
         }
     }
+
+    pub fn cache_key(&self) -> CommandCacheKey {
+        self.cache_key.clone()
+    }
 }
 
 impl Debug for BootstrapCommand {
@@ -245,10 +262,8 @@ impl From<Command> for BootstrapCommand {
         let program = command.get_program().to_owned();
 
         Self {
-            program: program.clone().into_string().unwrap(),
-            args: Vec::new(),
-            envs: Vec::new(),
-            cwd: None,
+            cache_key: CommandCacheKey::default(),
+            should_cache: false,
             command,
             failure_behavior: BehaviorOnFailure::Exit,
             run_in_dry_run: false,
diff --git a/src/bootstrap/src/utils/execution_context.rs b/src/bootstrap/src/utils/execution_context.rs
index 66a4be9252e..d7ea556c9ff 100644
--- a/src/bootstrap/src/utils/execution_context.rs
+++ b/src/bootstrap/src/utils/execution_context.rs
@@ -3,6 +3,8 @@
 //! This module provides the [`ExecutionContext`] type, which holds global configuration
 //! relevant during the execution of commands in bootstrap. This includes dry-run
 //! mode, verbosity level, and behavior on failure.
+#![allow(warnings)]
+use std::collections::HashMap;
 use std::panic::Location;
 use std::process::Child;
 use std::sync::{Arc, Mutex};
@@ -10,6 +12,7 @@ use std::sync::{Arc, Mutex};
 use crate::core::config::DryRun;
 #[cfg(feature = "tracing")]
 use crate::trace_cmd;
+use crate::utils::exec::CommandCacheKey;
 use crate::{BehaviorOnFailure, BootstrapCommand, CommandOutput, OutputMode, exit};
 
 #[derive(Clone, Default)]
@@ -18,6 +21,26 @@ pub struct ExecutionContext {
     verbose: u8,
     pub fail_fast: bool,
     delayed_failures: Arc<Mutex<Vec<String>>>,
+    command_cache: Arc<CommandCache>,
+}
+
+#[derive(Default)]
+pub struct CommandCache {
+    cache: Mutex<HashMap<CommandCacheKey, CommandOutput>>,
+}
+
+impl CommandCache {
+    pub fn new() -> Self {
+        Self { cache: Mutex::new(HashMap::new()) }
+    }
+
+    pub fn get(&self, key: &CommandCacheKey) -> Option<CommandOutput> {
+        self.cache.lock().unwrap().get(key).cloned()
+    }
+
+    pub fn insert(&self, key: CommandCacheKey, output: CommandOutput) {
+        self.cache.lock().unwrap().insert(key, output);
+    }
 }
 
 impl ExecutionContext {
@@ -123,7 +146,26 @@ impl ExecutionContext {
         stdout: OutputMode,
         stderr: OutputMode,
     ) -> CommandOutput {
-        self.start(command, stdout, stderr).wait_for_output(self)
+        let cache_key = command.cache_key();
+
+        if command.should_cache()
+            && let Some(cached_output) = self.command_cache.get(&cache_key)
+        {
+            command.mark_as_executed();
+            if self.dry_run() && !command.run_always {
+                return CommandOutput::default();
+            }
+            self.verbose(|| println!("Cache hit: {:?}", command));
+            return cached_output;
+        }
+
+        let output = self.start(command, stdout, stderr).wait_for_output(self);
+
+        if !self.dry_run() || command.run_always && command.should_cache() {
+            self.command_cache.insert(cache_key, output.clone());
+        }
+
+        output
     }
 
     fn fail(&self, message: &str, output: CommandOutput) -> ! {