Allow missing `Scripts` directory (#16206)

With the new Python install manager, the `Scripts` directory reported by
Python may not exist.

See https://github.com/astral-sh/uv/pull/16205 for a failing CI run:
https://github.com/astral-sh/uv/actions/runs/18377230241/job/52354460636?pr=16205#step:4:15

Fixes https://github.com/astral-sh/uv/issues/16204
This commit is contained in:
konsti 2025-10-09 16:34:40 +02:00 committed by GitHub
parent f0fbda1001
commit 3e6fe1da86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 41 additions and 22 deletions

View File

@ -1289,6 +1289,30 @@ jobs:
./uv run python -c "" ./uv run python -c ""
./uv run -p 3.13 python -c "" ./uv run -p 3.13 python -c ""
integration-test-windows-python-install-manager:
timeout-minutes: 10
needs: build-binary-windows-x86_64
name: "integration test | windows python install manager"
runs-on: windows-latest
steps:
- name: "Download binary"
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: uv-windows-x86_64-${{ github.sha }}
- name: "Install Python via Python Install manager"
run: |
# https://www.python.org/downloads/release/pymanager-250/
winget install --accept-package-agreements --accept-source-agreements 9NQ7512CXL7T
# Call Python Install Manager's py.exe by full path to avoid legacy py.exe
& "$env:LOCALAPPDATA\Microsoft\WindowsApps\py.exe" install 3.14
# https://github.com/astral-sh/uv/issues/16204
- name: "Check temporary environment creation"
run: |
./uv run -p $env:LOCALAPPDATA\Python\pythoncore-3.14-64\python.exe --with numpy python -c "import sys; print(sys.executable)"
integration-test-pypy-linux: integration-test-pypy-linux:
timeout-minutes: 10 timeout-minutes: 10
needs: build-binary-linux-libc needs: build-binary-linux-libc

View File

@ -2,6 +2,7 @@ use std::borrow::Cow;
use std::env::VarError; use std::env::VarError;
use std::ffi::OsString; use std::ffi::OsString;
use std::fmt::Write; use std::fmt::Write;
use std::io;
use std::io::Read; use std::io::Read;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -1099,7 +1100,12 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
// Copy each entrypoint from the base environments to the ephemeral environment, // Copy each entrypoint from the base environments to the ephemeral environment,
// updating the Python executable target to ensure they run in the ephemeral // updating the Python executable target to ensure they run in the ephemeral
// environment. // environment.
for entry in fs_err::read_dir(interpreter.scripts())? { let scripts = match fs_err::read_dir(interpreter.scripts()) {
Ok(scripts) => scripts,
Err(err) if err.kind() == io::ErrorKind::NotFound => continue,
Err(err) => return Err(err.into()),
};
for entry in scripts {
let entry = entry?; let entry = entry?;
if !entry.file_type()?.is_file() { if !entry.file_type()?.is_file() {
continue; continue;
@ -1200,23 +1206,13 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
"Provide a command or script to invoke with `uv run <command>` or `uv run <script>.py`.\n" "Provide a command or script to invoke with `uv run <command>` or `uv run <script>.py`.\n"
)?; )?;
#[allow(clippy::map_identity)] let scripts = match fs_err::read_dir(interpreter.scripts()) {
let commands = interpreter Ok(scripts) => scripts.into_iter().collect::<Result<Vec<_>, _>>()?,
.scripts() Err(err) if err.kind() == io::ErrorKind::NotFound => Vec::new(),
.read_dir() Err(err) => return Err(err.into()),
.ok() };
.into_iter()
.flatten() let commands = scripts
.map(|entry| match entry {
Ok(entry) => Ok(entry),
Err(err) => {
// If we can't read the entry, fail.
// This could be a symptom of a more serious problem.
warn!("Failed to read entry: {}", err);
Err(err)
}
})
.collect::<Result<Vec<_>, _>>()?
.into_iter() .into_iter()
.filter(|entry| { .filter(|entry| {
entry entry
@ -1226,7 +1222,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
.map(|entry| entry.path()) .map(|entry| entry.path())
.filter(|path| is_executable(path)) .filter(|path| is_executable(path))
.map(|path| { .map(|path| {
if cfg!(windows) let path = if cfg!(windows)
&& path && path
.extension() .extension()
.is_some_and(|exe| exe == std::env::consts::EXE_EXTENSION) .is_some_and(|exe| exe == std::env::consts::EXE_EXTENSION)
@ -1235,9 +1231,8 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
path.with_extension("") path.with_extension("")
} else { } else {
path path
} };
})
.map(|path| {
path.file_name() path.file_name()
.unwrap_or_default() .unwrap_or_default()
.to_string_lossy() .to_string_lossy()