Skip to content

Commit

Permalink
feat: cmd.exe detection heuristic (#2226)
Browse files Browse the repository at this point in the history
## Summary

Follow up from discussion in #2223

Detect CMD.exe by checking if `PROMPT` env var is set on windows,
otherwise assume it's PowerShell.

Note, this will not work if user modifies their system env vars to
include `PROMPT` by default or if they launch nested PowerShell from
Command Prompt (e.g. `Developer PowerShell for VS 2022`).

## Test Plan

Only tested locally, although we try to add some CI tests that
specifically use CMD.exe

Command Prompt
```
Microsoft Windows [Version 10.0.19044.3086]
(c) Microsoft Corporation. All rights reserved.

Z:\Users\samypr100\dev\uv>Z:\Users\samypr100\.cargo\bin\cargo.exe +stable run --color=always -- venv "Foo Bar"
    Finished dev [unoptimized + debuginfo] target(s) in 0.69s
     Running `target\debug\uv.exe venv "Foo Bar"`
Using Python 3.12.2 interpreter at: Z:\Users\samypr100\AppData\Local\Programs\Python\Python312\python.exe
Creating virtualenv at: Foo Bar
Activate with: "Foo Bar\Scripts\activate"
```

Power Shell
```
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

Try the new cross-platform PowerShell https://aka.ms/pscore6

PS Z:\Users\samypr100\dev\uv>Z:\Users\samypr100\.cargo\bin\cargo.exe +stable run --color=always -- venv "Foo Bar"
    Finished dev [unoptimized + debuginfo] target(s) in 0.63s
     Running `target\debug\uv.exe venv "Foo Bar"`
Using Python 3.12.2 interpreter at: Z:\Users\samypr100\AppData\Local\Programs\Python\Python312\python.exe
Creating virtualenv at: Foo Bar
Activate with: & "Foo Bar\Scripts\activate"
```
  • Loading branch information
samypr100 authored Mar 6, 2024
1 parent 511e32e commit 2ebcef9
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 7 deletions.
24 changes: 18 additions & 6 deletions crates/uv/src/commands/venv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,14 @@ async fn venv_impl(
"source {}",
shlex_posix(path.join("bin").join("activate.csh"))
)),
Some(Shell::Powershell) => Some(shlex_windows(path.join("Scripts").join("activate"))),
Some(Shell::Powershell) => Some(shlex_windows(
path.join("Scripts").join("activate"),
Shell::Powershell,
)),
Some(Shell::Cmd) => Some(shlex_windows(
path.join("Scripts").join("activate"),
Shell::Cmd,
)),
};
if let Some(act) = activation {
writeln!(printer.stderr(), "Activate with: {}", act.green()).into_diagnostic()?;
Expand All @@ -254,15 +261,20 @@ fn shlex_posix(executable: impl AsRef<Path>) -> String {
}
}

/// Quote a path, if necessary, for safe use in `PowerShell`.
fn shlex_windows(executable: impl AsRef<Path>) -> String {
/// Quote a path, if necessary, for safe use in `PowerShell` and `cmd`.
fn shlex_windows(executable: impl AsRef<Path>, shell: Shell) -> String {
// Convert to a display path.
let executable = executable.as_ref().simplified_display().to_string();

// Wrap the executable in quotes (and a `&` invocation) if it contains spaces.
// TODO(charlie): This won't work in `cmd.exe`.
// Wrap the executable in quotes (and a `&` invocation on PowerShell), if it contains spaces.
if executable.contains(' ') {
format!("& \"{executable}\"")
if shell == Shell::Powershell {
// For PowerShell, wrap in a `&` invocation.
format!("& \"{executable}\"")
} else {
// Otherwise, assume `cmd`, which doesn't need the `&`.
format!("\"{executable}\"")
}
} else {
executable
}
Expand Down
11 changes: 10 additions & 1 deletion crates/uv/src/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ pub(crate) enum Shell {
Fish,
/// PowerShell
Powershell,
/// Cmd (Command Prompt)
Cmd,
/// Z SHell (zsh)
Zsh,
/// Nushell
Expand All @@ -34,7 +36,14 @@ impl Shell {
} else if let Some(env_shell) = std::env::var_os("SHELL") {
Shell::from_shell_path(env_shell)
} else if cfg!(windows) {
Some(Shell::Powershell)
// Command Prompt relies on PROMPT for its appearance whereas PowerShell does not.
// See: https://stackoverflow.com/a/66415037.
if std::env::var_os("PROMPT").is_some() {
Some(Shell::Cmd)
} else {
// Fallback to PowerShell if the PROMPT environment variable is not set.
Some(Shell::Powershell)
}
} else {
None
}
Expand Down

0 comments on commit 2ebcef9

Please sign in to comment.