mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Add uvw
as alias for uv
without console window on Windows (#11786)
<!-- Thank you for contributing to uv! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? - Does this pull request include references to any relevant issues? --> ## Summary Related to https://github.com/astral-sh/uv/issues/6801. Currently on Windows, uv itself will always creates a console window, even though the window could be empty if `uv run --gui-script` is used. This is due to it using the [default `console` window subsystem](https://rust-lang.github.io/rfcs/1665-windows-subsystem.html). This PR introduces a wrapper `uvw` that, similar to the existing `uvx`, invokes `uv` with the [`CREATE_NO_WINDOW`](https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags#:~:text=CREATE_NO_WINDOW) process creation flag on Windows, which creates child process without console window. Note that this PR does not alter any behaviors regarding `run --script` and `run --gui-script`. ## Test Plan Built and tested locally by doing something like `uvw run test.py`.
This commit is contained in:
parent
f62836fa04
commit
e5d002beb1
4 changed files with 127 additions and 1 deletions
4
.github/workflows/build-binaries.yml
vendored
4
.github/workflows/build-binaries.yml
vendored
|
@ -234,7 +234,7 @@ jobs:
|
|||
uses: PyO3/maturin-action@44479ae1b6b1a57f561e03add8832e62c185eb17 # v1.48.1
|
||||
with:
|
||||
target: ${{ matrix.platform.target }}
|
||||
args: --release --locked --out dist --features self-update
|
||||
args: --release --locked --out dist --features self-update,windows-gui-bin
|
||||
- name: "Test wheel"
|
||||
if: ${{ !startsWith(matrix.platform.target, 'aarch64') }}
|
||||
shell: bash
|
||||
|
@ -243,6 +243,7 @@ jobs:
|
|||
${{ env.MODULE_NAME }} --help
|
||||
python -m ${{ env.MODULE_NAME }} --help
|
||||
uvx --help
|
||||
uvw --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
|
@ -254,6 +255,7 @@ jobs:
|
|||
ARCHIVE_FILE=uv-${{ matrix.platform.target }}.zip
|
||||
7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/uv.exe
|
||||
7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/uvx.exe
|
||||
7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/uvw.exe
|
||||
sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256
|
||||
- name: "Upload binary"
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
|
|
|
@ -368,3 +368,10 @@ aarch64-unknown-linux-gnu = "2.28"
|
|||
"actions/upload-artifact" = "6027e3dd177782cd8ab9af838c04fd81a07f1d47" # v4.6.2
|
||||
"actions/download-artifact" = "d3f86a106a0bac45b974a628896c90dbdf5c8093" # v4.3.0
|
||||
"actions/attest-build-provenance" = "c074443f1aee8d4aeeae555aebba3282517141b2" #v2.2.3
|
||||
|
||||
[workspace.metadata.dist.binaries]
|
||||
"*" = ["uv", "uvx"]
|
||||
# Add "uvw" binary for Windows targets
|
||||
aarch64-pc-windows-msvc = ["uv", "uvx", "uvw"]
|
||||
i686-pc-windows-msvc = ["uv", "uvx", "uvw"]
|
||||
x86_64-pc-windows-msvc = ["uv", "uvx", "uvw"]
|
||||
|
|
|
@ -178,6 +178,12 @@ python-managed = []
|
|||
slow-tests = []
|
||||
# Includes test cases that require ecosystem packages
|
||||
test-ecosystem = []
|
||||
# Build uvw binary on Windows
|
||||
windows-gui-bin = []
|
||||
|
||||
[package.metadata.dist]
|
||||
dist = true
|
||||
|
||||
[[bin]]
|
||||
name = "uvw"
|
||||
required-features = ["windows-gui-bin"]
|
||||
|
|
111
crates/uv/src/bin/uvw.rs
Normal file
111
crates/uv/src/bin/uvw.rs
Normal file
|
@ -0,0 +1,111 @@
|
|||
#![cfg_attr(windows, windows_subsystem = "windows")]
|
||||
|
||||
use std::convert::Infallible;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, ExitCode, ExitStatus};
|
||||
|
||||
/// Spawns a command exec style.
|
||||
fn exec_spawn(cmd: &mut Command) -> std::io::Result<Infallible> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::process::CommandExt;
|
||||
let err = cmd.exec();
|
||||
Err(err)
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
||||
|
||||
cmd.stdin(std::process::Stdio::inherit());
|
||||
let status = cmd.creation_flags(CREATE_NO_WINDOW).status()?;
|
||||
|
||||
#[allow(clippy::exit)]
|
||||
std::process::exit(status.code().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
/// Assuming the binary is called something like `uvw@1.2.3(.exe)`, compute the `@1.2.3(.exe)` part
|
||||
/// so that we can preferentially find `uv@1.2.3(.exe)`, for folks who like managing multiple
|
||||
/// installs in this way.
|
||||
fn get_uvw_suffix(current_exe: &Path) -> Option<&str> {
|
||||
let os_file_name = current_exe.file_name()?;
|
||||
let file_name_str = os_file_name.to_str()?;
|
||||
file_name_str.strip_prefix("uvw")
|
||||
}
|
||||
|
||||
/// Gets the path to `uv`, given info about `uvw`
|
||||
fn get_uv_path(current_exe_parent: &Path, uvw_suffix: Option<&str>) -> std::io::Result<PathBuf> {
|
||||
// First try to find a matching suffixed `uv`, e.g. `uv@1.2.3(.exe)`
|
||||
let uv_with_suffix = uvw_suffix.map(|suffix| current_exe_parent.join(format!("uv{suffix}")));
|
||||
if let Some(uv_with_suffix) = &uv_with_suffix {
|
||||
#[allow(clippy::print_stderr, reason = "printing a very rare warning")]
|
||||
match uv_with_suffix.try_exists() {
|
||||
Ok(true) => return Ok(uv_with_suffix.to_owned()),
|
||||
Ok(false) => { /* definitely not there, proceed to fallback */ }
|
||||
Err(err) => {
|
||||
// We don't know if `uv@1.2.3` exists, something errored when checking.
|
||||
// We *could* blindly use `uv@1.2.3` in this case, as the code below does, however
|
||||
// in this extremely narrow corner case it's *probably* better to default to `uv`,
|
||||
// since we don't want to mess up existing users who weren't using suffixes?
|
||||
eprintln!(
|
||||
"warning: failed to determine if `{}` exists, trying `uv` instead: {err}",
|
||||
uv_with_suffix.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then just look for good ol' `uv`
|
||||
let uv = current_exe_parent.join(format!("uv{}", std::env::consts::EXE_SUFFIX));
|
||||
// If we are sure the `uv` binary does not exist, display a clearer error message.
|
||||
// If we're not certain if uv exists (try_exists == Err), keep going and hope it works.
|
||||
if matches!(uv.try_exists(), Ok(false)) {
|
||||
let message = if let Some(uv_with_suffix) = uv_with_suffix {
|
||||
format!(
|
||||
"Could not find the `uv` binary at either of:\n {}\n {}",
|
||||
uv_with_suffix.display(),
|
||||
uv.display(),
|
||||
)
|
||||
} else {
|
||||
format!("Could not find the `uv` binary at: {}", uv.display())
|
||||
};
|
||||
Err(std::io::Error::new(std::io::ErrorKind::NotFound, message))
|
||||
} else {
|
||||
Ok(uv)
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> std::io::Result<ExitStatus> {
|
||||
let current_exe = std::env::current_exe()?;
|
||||
let Some(bin) = current_exe.parent() else {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"Could not determine the location of the `uvw` binary",
|
||||
));
|
||||
};
|
||||
let uvw_suffix = get_uvw_suffix(¤t_exe);
|
||||
let uv = get_uv_path(bin, uvw_suffix)?;
|
||||
let args = std::env::args_os()
|
||||
// Skip the `uvw` name
|
||||
.skip(1)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut cmd = Command::new(uv);
|
||||
cmd.args(&args);
|
||||
match exec_spawn(&mut cmd)? {}
|
||||
}
|
||||
|
||||
#[allow(clippy::print_stderr)]
|
||||
fn main() -> ExitCode {
|
||||
let result = run();
|
||||
match result {
|
||||
// Fail with 2 if the status cannot be cast to an exit code
|
||||
Ok(status) => u8::try_from(status.code().unwrap_or(2)).unwrap_or(2).into(),
|
||||
Err(err) => {
|
||||
eprintln!("error: {err}");
|
||||
ExitCode::from(2)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue