diff --git a/crates/rattler_shell/src/activation.rs b/crates/rattler_shell/src/activation.rs index 3d428761d..d7529e5c1 100644 --- a/crates/rattler_shell/src/activation.rs +++ b/crates/rattler_shell/src/activation.rs @@ -12,6 +12,16 @@ use crate::shell::Shell; use indexmap::IndexMap; use rattler_conda_types::Platform; +/// Type of modification done to the `PATH` variable +pub enum PathModificationBehaviour { + /// Replaces the complete path variable with specified paths. + Replace, + /// Appends the new path variables to the path. E.g. '$PATH:/new/path' + Append, + /// Prepends the new path variables to the path. E.g. '/new/path:$PATH' + Prepend, +} + /// A struct that contains the values of the environment variables that are relevant for the activation process. /// The values are stored as strings. Currently, only the `PATH` and `CONDA_PREFIX` environment variables are used. pub struct ActivationVariables { @@ -20,6 +30,9 @@ pub struct ActivationVariables { /// The value of the `PATH` environment variable that contains the paths to the executables pub path: Option>, + + /// The type of behaviour of what should happen with the defined paths. + pub path_modification_behaviour: PathModificationBehaviour, } impl ActivationVariables { @@ -27,9 +40,8 @@ impl ActivationVariables { pub fn from_env() -> Result { Ok(Self { conda_prefix: std::env::var("CONDA_PREFIX").ok().map(PathBuf::from), - path: std::env::var("PATH") - .ok() - .map(|p| std::env::split_paths(&p).collect::>()), + path: None, + path_modification_behaviour: PathModificationBehaviour::Prepend, }) } } @@ -340,7 +352,11 @@ impl Activator { let path = [self.paths.clone(), path].concat(); self.shell_type - .set_path(&mut script, path.as_slice()) + .set_path( + &mut script, + path.as_slice(), + variables.path_modification_behaviour, + ) .map_err(ActivationError::FailedToWriteActivationScript)?; // deliberately not taking care of `CONDA_SHLVL` or any other complications at this point @@ -376,6 +392,9 @@ mod tests { use super::*; use tempdir::TempDir; + #[cfg(unix)] + use crate::activation::PathModificationBehaviour; + #[test] fn test_collect_scripts() { let tdir = TempDir::new("test").unwrap(); @@ -487,7 +506,10 @@ mod tests { } #[cfg(unix)] - fn get_script(shell_type: T) -> String + fn get_script( + shell_type: T, + path_modification_behaviour: PathModificationBehaviour, + ) -> String where T: Clone, { @@ -505,6 +527,7 @@ mod tests { PathBuf::from("/sbin"), PathBuf::from("/usr/local/bin"), ]), + path_modification_behaviour, }) .unwrap(); let prefix = tdir.path().to_str().unwrap(); @@ -515,42 +538,63 @@ mod tests { #[test] #[cfg(unix)] fn test_activation_script_bash() { - let script = get_script(shell::Bash); - insta::assert_snapshot!(script); + let script = get_script(shell::Bash, PathModificationBehaviour::Append); + insta::assert_snapshot!("test_activation_script_bash_append", script); + let script = get_script(shell::Bash, PathModificationBehaviour::Replace); + insta::assert_snapshot!("test_activation_script_bash_replace", script); + let script = get_script(shell::Bash, PathModificationBehaviour::Prepend); + insta::assert_snapshot!("test_activation_script_bash_prepend", script); } #[test] #[cfg(unix)] fn test_activation_script_zsh() { - let script = get_script(shell::Zsh); + let script = get_script(shell::Zsh, PathModificationBehaviour::Append); insta::assert_snapshot!(script); } #[test] #[cfg(unix)] fn test_activation_script_fish() { - let script = get_script(shell::Fish); + let script = get_script(shell::Fish, PathModificationBehaviour::Append); insta::assert_snapshot!(script); } #[test] #[cfg(unix)] fn test_activation_script_powershell() { - let script = get_script(shell::PowerShell::default()); - insta::assert_snapshot!(script); + let script = get_script( + shell::PowerShell::default(), + PathModificationBehaviour::Append, + ); + insta::assert_snapshot!("test_activation_script_powershell_append", script); + let script = get_script( + shell::PowerShell::default(), + PathModificationBehaviour::Prepend, + ); + insta::assert_snapshot!("test_activation_script_powershell_prepend", script); + let script = get_script( + shell::PowerShell::default(), + PathModificationBehaviour::Replace, + ); + insta::assert_snapshot!("test_activation_script_powershell_replace", script); } #[test] #[cfg(unix)] fn test_activation_script_cmd() { - let script = get_script(shell::CmdExe); - insta::assert_snapshot!(script); + let script = get_script(shell::CmdExe, PathModificationBehaviour::Append); + insta::assert_snapshot!("test_activation_script_cmd_append", script); + let script = get_script(shell::CmdExe, PathModificationBehaviour::Replace); + insta::assert_snapshot!("test_activation_script_cmd_replace", script); + let script = get_script(shell::CmdExe, PathModificationBehaviour::Prepend); + insta::assert_snapshot!("test_activation_script_cmd_prepend", script); } #[test] #[cfg(unix)] fn test_activation_script_xonsh() { - let script = get_script(shell::Xonsh); + let script = get_script(shell::Xonsh, PathModificationBehaviour::Append); insta::assert_snapshot!(script); } } diff --git a/crates/rattler_shell/src/shell/mod.rs b/crates/rattler_shell/src/shell/mod.rs index 5c57404a5..5a1137128 100644 --- a/crates/rattler_shell/src/shell/mod.rs +++ b/crates/rattler_shell/src/shell/mod.rs @@ -1,5 +1,6 @@ //! This module contains the [`Shell`] trait and implementations for various shells. +use crate::activation::PathModificationBehaviour; use enum_dispatch::enum_dispatch; use itertools::Itertools; use std::process::Command; @@ -46,9 +47,26 @@ pub trait Shell { } /// Set the PATH variable to the given paths. - fn set_path(&self, f: &mut impl Write, paths: &[PathBuf]) -> std::fmt::Result { - let path = std::env::join_paths(paths).unwrap(); - self.set_env_var(f, "PATH", path.to_str().unwrap()) + fn set_path( + &self, + f: &mut impl Write, + paths: &[PathBuf], + modification_behaviour: PathModificationBehaviour, + ) -> std::fmt::Result { + let mut paths_vec = paths + .iter() + .map(|path| path.to_string_lossy().into_owned()) + .collect_vec(); + // Replace, Append, or Prepend the path variable to the paths. + match modification_behaviour { + PathModificationBehaviour::Replace => (), + PathModificationBehaviour::Append => paths_vec.insert(0, self.format_env_var("PATH")), + PathModificationBehaviour::Prepend => paths_vec.push(self.format_env_var("PATH")), + } + // Create the shell specific list of paths. + let paths_string = paths_vec.join(self.path_seperator()); + + self.set_env_var(f, "PATH", paths_string.as_str()) } /// The extension that shell scripts for this interpreter usually use. @@ -59,6 +77,16 @@ pub trait Shell { /// Constructs a [`Command`] that will execute the specified script by this shell. fn create_run_script_command(&self, path: &Path) -> Command; + + /// Path seperator + fn path_seperator(&self) -> &str { + ":" + } + + /// Format the environment variable for the shell. + fn format_env_var(&self, var_name: &str) -> String { + format!("${{{var_name}}}") + } } /// Convert a native PATH on Windows to a Unix style path usign cygpath. @@ -105,16 +133,35 @@ impl Shell for Bash { writeln!(f, ". \"{}\"", path.to_string_lossy()) } - fn set_path(&self, f: &mut impl Write, paths: &[PathBuf]) -> std::fmt::Result { - let path = std::env::join_paths(paths).unwrap(); - - // check if we are on Windows, and if yes, convert native path to unix for (Git) Bash - if cfg!(windows) { - let path = native_path_to_unix(path.to_str().unwrap()).unwrap(); - return self.set_env_var(f, "PATH", &path); + fn set_path( + &self, + f: &mut impl Write, + paths: &[PathBuf], + modification_behaviour: PathModificationBehaviour, + ) -> std::fmt::Result { + // Put paths in a vector of the correct format. + let mut paths_vec = paths + .iter() + .map(|path| { + // check if we are on Windows, and if yes, convert native path to unix for (Git) Bash + if cfg!(windows) { + native_path_to_unix(path.to_string_lossy().as_ref()).unwrap() + } else { + path.to_string_lossy().into_owned() + } + }) + .collect_vec(); + + // Replace, Append, or Prepend the path variable to the paths. + match modification_behaviour { + PathModificationBehaviour::Replace => (), + PathModificationBehaviour::Prepend => paths_vec.push(self.format_env_var("PATH")), + PathModificationBehaviour::Append => paths_vec.insert(0, self.format_env_var("PATH")), } + // Create the shell specific list of paths. + let paths_string = paths_vec.join(self.path_seperator()); - self.set_env_var(f, "PATH", path.to_str().unwrap()) + self.set_env_var(f, "PATH", paths_string.as_str()) } fn extension(&self) -> &str { @@ -241,6 +288,14 @@ impl Shell for CmdExe { cmd.arg("/D").arg("/C").arg(path); cmd } + + fn path_seperator(&self) -> &str { + ";" + } + + fn format_env_var(&self, var_name: &str) -> String { + format!("%{var_name}%") + } } /// A [`Shell`] implementation for PowerShell. @@ -275,6 +330,14 @@ impl Shell for PowerShell { cmd.arg(path); cmd } + + fn path_seperator(&self) -> &str { + ";" + } + + fn format_env_var(&self, var_name: &str) -> String { + format!("$Env:{var_name}") + } } /// A [`Shell`] implementation for the Fish shell. @@ -453,7 +516,9 @@ impl ShellScript { /// Set the PATH environment variable to the given paths. pub fn set_path(&mut self, paths: &[PathBuf]) -> &mut Self { - self.shell.set_path(&mut self.contents, paths).unwrap(); + self.shell + .set_path(&mut self.contents, paths, PathModificationBehaviour::Append) + .unwrap(); self } diff --git a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_bash.snap b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_bash.snap index a87fb2761..87314c135 100644 --- a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_bash.snap +++ b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_bash.snap @@ -2,7 +2,7 @@ source: crates/rattler_shell/src/activation.rs expression: script --- -export PATH="__PREFIX__/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" +export PATH="__PREFIX__/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:${PATH}" export CONDA_PREFIX="__PREFIX__" . "__PREFIX__/etc/conda/activate.d/script1.sh" diff --git a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_cmd.snap b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_cmd.snap index d7babf23f..b7dceb049 100644 --- a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_cmd.snap +++ b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_cmd.snap @@ -2,6 +2,6 @@ source: crates/rattler_shell/src/activation.rs expression: script --- -@SET "PATH=__PREFIX__/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" +@SET "PATH=%PATH%;__PREFIX__/bin;/usr/bin;/bin;/usr/sbin;/sbin;/usr/local/bin" @SET "CONDA_PREFIX=__PREFIX__" diff --git a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_fish.snap b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_fish.snap index c45add6be..2da3105bf 100644 --- a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_fish.snap +++ b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_fish.snap @@ -2,6 +2,6 @@ source: crates/rattler_shell/src/activation.rs expression: script --- -set -gx PATH "__PREFIX__/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" +set -gx PATH "${PATH}:__PREFIX__/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" set -gx CONDA_PREFIX "__PREFIX__" diff --git a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_powershell.snap b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_powershell.snap index eea35f1ff..4cdfc478f 100644 --- a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_powershell.snap +++ b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_powershell.snap @@ -2,6 +2,6 @@ source: crates/rattler_shell/src/activation.rs expression: script --- -$Env:PATH = "__PREFIX__/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" +$Env:PATH = "$Env:PATH;__PREFIX__/bin;/usr/bin;/bin;/usr/sbin;/sbin;/usr/local/bin" $Env:CONDA_PREFIX = "__PREFIX__" diff --git a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_xonsh.snap b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_xonsh.snap index 0636dfcfc..b3c3812ff 100644 --- a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_xonsh.snap +++ b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_xonsh.snap @@ -2,7 +2,7 @@ source: crates/rattler_shell/src/activation.rs expression: script --- -$PATH = "__PREFIX__/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" +$PATH = "${PATH}:__PREFIX__/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" $CONDA_PREFIX = "__PREFIX__" source-bash "__PREFIX__/etc/conda/activate.d/script1.sh" diff --git a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_zsh.snap b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_zsh.snap index a87fb2761..b1c190206 100644 --- a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_zsh.snap +++ b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__activation_script_zsh.snap @@ -2,7 +2,7 @@ source: crates/rattler_shell/src/activation.rs expression: script --- -export PATH="__PREFIX__/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" +export PATH="${PATH}:__PREFIX__/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" export CONDA_PREFIX="__PREFIX__" . "__PREFIX__/etc/conda/activate.d/script1.sh" diff --git a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_bash_append.snap b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_bash_append.snap new file mode 100644 index 000000000..b1c190206 --- /dev/null +++ b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_bash_append.snap @@ -0,0 +1,8 @@ +--- +source: crates/rattler_shell/src/activation.rs +expression: script +--- +export PATH="${PATH}:__PREFIX__/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" +export CONDA_PREFIX="__PREFIX__" +. "__PREFIX__/etc/conda/activate.d/script1.sh" + diff --git a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_bash_prepend.snap b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_bash_prepend.snap new file mode 100644 index 000000000..87314c135 --- /dev/null +++ b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_bash_prepend.snap @@ -0,0 +1,8 @@ +--- +source: crates/rattler_shell/src/activation.rs +expression: script +--- +export PATH="__PREFIX__/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:${PATH}" +export CONDA_PREFIX="__PREFIX__" +. "__PREFIX__/etc/conda/activate.d/script1.sh" + diff --git a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_bash_replace.snap b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_bash_replace.snap new file mode 100644 index 000000000..a87fb2761 --- /dev/null +++ b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_bash_replace.snap @@ -0,0 +1,8 @@ +--- +source: crates/rattler_shell/src/activation.rs +expression: script +--- +export PATH="__PREFIX__/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin" +export CONDA_PREFIX="__PREFIX__" +. "__PREFIX__/etc/conda/activate.d/script1.sh" + diff --git a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_cmd_append.snap b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_cmd_append.snap new file mode 100644 index 000000000..b7dceb049 --- /dev/null +++ b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_cmd_append.snap @@ -0,0 +1,7 @@ +--- +source: crates/rattler_shell/src/activation.rs +expression: script +--- +@SET "PATH=%PATH%;__PREFIX__/bin;/usr/bin;/bin;/usr/sbin;/sbin;/usr/local/bin" +@SET "CONDA_PREFIX=__PREFIX__" + diff --git a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_cmd_prepend.snap b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_cmd_prepend.snap new file mode 100644 index 000000000..5fc14d1ed --- /dev/null +++ b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_cmd_prepend.snap @@ -0,0 +1,7 @@ +--- +source: crates/rattler_shell/src/activation.rs +expression: script +--- +@SET "PATH=__PREFIX__/bin;/usr/bin;/bin;/usr/sbin;/sbin;/usr/local/bin;%PATH%" +@SET "CONDA_PREFIX=__PREFIX__" + diff --git a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_cmd_replace.snap b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_cmd_replace.snap new file mode 100644 index 000000000..780c72dbf --- /dev/null +++ b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_cmd_replace.snap @@ -0,0 +1,7 @@ +--- +source: crates/rattler_shell/src/activation.rs +expression: script +--- +@SET "PATH=__PREFIX__/bin;/usr/bin;/bin;/usr/sbin;/sbin;/usr/local/bin" +@SET "CONDA_PREFIX=__PREFIX__" + diff --git a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_powershell_append.snap b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_powershell_append.snap new file mode 100644 index 000000000..4cdfc478f --- /dev/null +++ b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_powershell_append.snap @@ -0,0 +1,7 @@ +--- +source: crates/rattler_shell/src/activation.rs +expression: script +--- +$Env:PATH = "$Env:PATH;__PREFIX__/bin;/usr/bin;/bin;/usr/sbin;/sbin;/usr/local/bin" +$Env:CONDA_PREFIX = "__PREFIX__" + diff --git a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_powershell_prepend.snap b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_powershell_prepend.snap new file mode 100644 index 000000000..a4bc12202 --- /dev/null +++ b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_powershell_prepend.snap @@ -0,0 +1,7 @@ +--- +source: crates/rattler_shell/src/activation.rs +expression: script +--- +$Env:PATH = "__PREFIX__/bin;/usr/bin;/bin;/usr/sbin;/sbin;/usr/local/bin;$Env:PATH" +$Env:CONDA_PREFIX = "__PREFIX__" + diff --git a/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_powershell_replace.snap b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_powershell_replace.snap new file mode 100644 index 000000000..8860d9479 --- /dev/null +++ b/crates/rattler_shell/src/snapshots/rattler_shell__activation__tests__test_activation_script_powershell_replace.snap @@ -0,0 +1,7 @@ +--- +source: crates/rattler_shell/src/activation.rs +expression: script +--- +$Env:PATH = "__PREFIX__/bin;/usr/bin;/bin;/usr/sbin;/sbin;/usr/local/bin" +$Env:CONDA_PREFIX = "__PREFIX__" +