diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a29ae96..ba768a1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -44,7 +44,7 @@ jobs: with: components: clippy, rustfmt override: true - toolchain: 1.53.0 + toolchain: 1.54.0 - uses: Swatinem/rust-cache@v1 diff --git a/README.md b/README.md index d360e24..998bdb9 100644 --- a/README.md +++ b/README.md @@ -648,7 +648,8 @@ foo: | `export` | boolean | Export all variables as environment variables. | | `positional-arguments` | boolean | Pass positional arguments. | | `shell` | `[COMMAND, ARGS…]` | Set the command used to invoke recipes and evaluate backticks. | -| `windows-powershell` | boolean | Use PowerShell on Windows as default shell. | +| `windows-shell` | `[COMMAND, ARGS…]` | Set the command used to invoke recipes and evaluate backticks. | +| `windows-powershell` | boolean | Use PowerShell on Windows as default shell. (Deprecated. Use `windows-shell` instead. | Boolean settings can be written as: @@ -775,6 +776,8 @@ hello: Write-Host "Hello, world!" ``` +See [powershell.just](https://github.com/casey/just/blob/master/examples/powershell.just) for a justfile that uses PowerShell on all platforms. + ##### Windows PowerShell *`set windows-powershell` uses the legacy `powershell.exe` binary, and is no longer recommended. See the `windows-shell` setting above for a more flexible way to control which shell is used on Windows.* @@ -2126,6 +2129,17 @@ foo $argument: This defeats `just`'s ability to catch typos, for example if you type `$argumant`, but works for all possible values of `argument`, including those with double quotes. +### Configuring the Shell + +There are a number of ways to configure the shell for linewise recipes, which are the default when a recipe does not start with a `#!` shebang. Their precedence, from highest to lowest, is: + +1. The `--shell` and `--shell-arg` command line options. Passing either of these will cause `just` to ignore any settings in the current justfile. +2. `set windows-shell := [...]` +3. `set windows-powershell` (deprecated) +4. `set shell := [...]` + +Since `set windows-shell` has higher precedence than `set shell`, you can use `set windows-shell` to pick a shell on Windows, and `set shell` to pick a shell for all other platforms. + Changelog --------- diff --git a/examples/powershell.just b/examples/powershell.just new file mode 100644 index 0000000..7e57c4d --- /dev/null +++ b/examples/powershell.just @@ -0,0 +1,29 @@ +# Cross platform shebang: +shebang := if os() == 'windows' { + 'powershell.exe' +} else { + '/usr/bin/env pwsh' +} + +# Set shell for non-Windows OSs: +set shell := ["powershell", "-c"] + +# Set shell for Windows OSs: +set windows-shell := ["powershell.exe", "-NoLogo", "-Command"] + +# If you have PowerShell Core installed and want to use it, +# use `pwsh.exe` instead of `powershell.exe` + +linewise: + Write-Host "Hello, world!" + +shebang: + #!{{shebang}} + $PSV = $PSVersionTable.PSVersion | % {"$_" -split "\." } + $psver = $PSV[0] + "." + $PSV[1] + if ($PSV[2].Length -lt 4) { + $psver += "." + $PSV[2] + " Core" + } else { + $psver += " Desktop" + } + echo "PowerShell $psver" diff --git a/src/settings.rs b/src/settings.rs index ae3767f..c755a60 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -30,50 +30,48 @@ impl<'src> Settings<'src> { } pub(crate) fn shell_command(&self, config: &Config) -> Command { - let mut cmd = Command::new(self.shell_binary(config)); + let (command, args) = self.shell(config); - cmd.args(self.shell_arguments(config)); + let mut cmd = Command::new(command); + + cmd.args(args); cmd } - pub(crate) fn shell_binary<'a>(&'a self, config: &'a Config) -> &'a str { - let shell_or_args_present = config.shell.is_some() || config.shell_args.is_some(); - - if let (Some(shell), false) = (&self.shell, shell_or_args_present) { - shell.command.cooked.as_ref() - } else if let Some(shell) = &config.shell { - shell - } else if let (true, Some(shell)) = (cfg!(windows), &self.windows_shell) { - shell.command.cooked.as_ref() - } else if cfg!(windows) && self.windows_powershell { - WINDOWS_POWERSHELL_SHELL - } else { - DEFAULT_SHELL - } - } - - pub(crate) fn shell_arguments<'a>(&'a self, config: &'a Config) -> Vec<&'a str> { - let shell_or_args_present = config.shell.is_some() || config.shell_args.is_some(); - - if let (Some(shell), false) = (&self.shell, shell_or_args_present) { - shell - .arguments - .iter() - .map(|argument| argument.cooked.as_ref()) - .collect() - } else if let Some(shell_args) = &config.shell_args { - shell_args.iter().map(String::as_ref).collect() - } else if let (true, Some(shell)) = (cfg!(windows), &self.windows_shell) { - shell - .arguments - .iter() - .map(|argument| argument.cooked.as_ref()) - .collect() - } else if cfg!(windows) && self.windows_powershell { - WINDOWS_POWERSHELL_ARGS.to_vec() - } else { - DEFAULT_SHELL_ARGS.to_vec() + pub(crate) fn shell<'a>(&'a self, config: &'a Config) -> (&'a str, Vec<&'a str>) { + match (&config.shell, &config.shell_args) { + (Some(shell), Some(shell_args)) => (shell, shell_args.iter().map(String::as_ref).collect()), + (Some(shell), None) => (shell, DEFAULT_SHELL_ARGS.to_vec()), + (None, Some(shell_args)) => ( + DEFAULT_SHELL, + shell_args.iter().map(String::as_ref).collect(), + ), + (None, None) => { + if let (true, Some(shell)) = (cfg!(windows), &self.windows_shell) { + ( + shell.command.cooked.as_ref(), + shell + .arguments + .iter() + .map(|argument| argument.cooked.as_ref()) + .collect(), + ) + } else if cfg!(windows) && self.windows_powershell { + (WINDOWS_POWERSHELL_SHELL, WINDOWS_POWERSHELL_ARGS.to_vec()) + } else if let Some(shell) = &self.shell { + ( + shell.command.cooked.as_ref(), + shell + .arguments + .iter() + .map(|argument| argument.cooked.as_ref()) + .collect(), + ) + } else { + (DEFAULT_SHELL, DEFAULT_SHELL_ARGS.to_vec()) + } + } } } } @@ -91,8 +89,7 @@ mod tests { ..testing::config(&[]) }; - assert_eq!(settings.shell_binary(&config), "sh"); - assert_eq!(settings.shell_arguments(&config), vec!["-cu"]); + assert_eq!(settings.shell(&config), ("sh", vec!["-cu"])); } #[test] @@ -106,14 +103,12 @@ mod tests { }; if cfg!(windows) { - assert_eq!(settings.shell_binary(&config), "powershell.exe"); assert_eq!( - settings.shell_arguments(&config), - vec!["-NoLogo", "-Command"] + settings.shell(&config), + ("powershell.exe", vec!["-NoLogo", "-Command"]) ); } else { - assert_eq!(settings.shell_binary(&config), "sh"); - assert_eq!(settings.shell_arguments(&config), vec!["-cu"]); + assert_eq!(settings.shell(&config), ("sh", vec!["-cu"])); } } @@ -128,8 +123,7 @@ mod tests { ..testing::config(&[]) }; - assert_eq!(settings.shell_binary(&config), "lol"); - assert_eq!(settings.shell_arguments(&config), vec!["-nice"]); + assert_eq!(settings.shell(&config), ("lol", vec!["-nice"])); } #[test] @@ -144,8 +138,7 @@ mod tests { ..testing::config(&[]) }; - assert_eq!(settings.shell_binary(&config), "lol"); - assert_eq!(settings.shell_arguments(&config), vec!["-nice"]); + assert_eq!(settings.shell(&config), ("lol", vec!["-nice"])); } #[test] @@ -170,8 +163,7 @@ mod tests { ..testing::config(&[]) }; - assert_eq!(settings.shell_binary(&config), "asdf.exe"); - assert_eq!(settings.shell_arguments(&config), vec!["-nope"]); + assert_eq!(settings.shell(&config), ("asdf.exe", vec!["-nope"])); } #[test] @@ -184,7 +176,7 @@ mod tests { ..testing::config(&[]) }; - assert_eq!(settings.shell_binary(&config), "lol"); + assert_eq!(settings.shell(&config).0, "lol"); } #[test] @@ -198,12 +190,6 @@ mod tests { ..testing::config(&[]) }; - if cfg!(windows) { - assert_eq!(settings.shell_binary(&config), "powershell.exe"); - } else { - assert_eq!(settings.shell_binary(&config), "sh"); - } - - assert_eq!(settings.shell_arguments(&config), vec!["-nice"]); + assert_eq!(settings.shell(&config), ("sh", vec!["-nice"])); } } diff --git a/src/subcommand.rs b/src/subcommand.rs index 1a56e9e..60ab95c 100644 --- a/src/subcommand.rs +++ b/src/subcommand.rs @@ -217,9 +217,10 @@ impl Subcommand { let mut child = match result { Ok(child) => child, Err(io_error) => { + let (shell_binary, shell_arguments) = justfile.settings.shell(config); return Err(Error::ChooserInvoke { - shell_binary: justfile.settings.shell_binary(config).to_owned(), - shell_arguments: justfile.settings.shell_arguments(config).join(" "), + shell_binary: shell_binary.to_owned(), + shell_arguments: shell_arguments.join(" "), chooser, io_error, }); diff --git a/tests/lib.rs b/tests/lib.rs index 1c34bdf..b6fa7a4 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -76,7 +76,5 @@ mod subsequents; mod tempdir; mod undefined_variables; #[cfg(target_family = "windows")] -mod windows_powershell; -#[cfg(target_family = "windows")] mod windows_shell; mod working_directory; diff --git a/tests/windows_powershell.rs b/tests/windows_powershell.rs deleted file mode 100644 index 1a450bd..0000000 --- a/tests/windows_powershell.rs +++ /dev/null @@ -1,18 +0,0 @@ -use super::*; - -#[test] -fn windows_poweshell_setting_uses_powershell() { - Test::new() - .justfile( - r#" - set windows-powershell - - foo: - Write-Output bar - "#, - ) - .shell(false) - .stdout("bar\r\n") - .stderr("Write-Output bar\n") - .run(); -} diff --git a/tests/windows_shell.rs b/tests/windows_shell.rs index 7db6988..be93034 100644 --- a/tests/windows_shell.rs +++ b/tests/windows_shell.rs @@ -6,6 +6,42 @@ fn windows_shell_setting() { .justfile( r#" set windows-shell := ["pwsh.exe", "-NoLogo", "-Command"] + set shell := ["asdfasdfasdfasdf"] + + foo: + Write-Output bar + "#, + ) + .shell(false) + .stdout("bar\r\n") + .stderr("Write-Output bar\n") + .run(); +} + +#[test] +fn windows_powershell_setting_uses_powershell() { + Test::new() + .justfile( + r#" + set windows-powershell + set shell := ["asdfasdfasdfasdf"] + + foo: + Write-Output bar + "#, + ) + .shell(false) + .stdout("bar\r\n") + .stderr("Write-Output bar\n") + .run(); +} + +#[test] +fn windows_poweshell_setting_uses_powershell() { + Test::new() + .justfile( + r#" + set windows-powershell foo: Write-Output bar