Skip to content

Commit

Permalink
Merge branch 'astral-sh:main' into uv-pyvenv-cfg-custom-fields
Browse files Browse the repository at this point in the history
  • Loading branch information
samypr100 authored Feb 22, 2024
2 parents 6403150 + 12a96ad commit 8cb7cd5
Show file tree
Hide file tree
Showing 32 changed files with 4,040 additions and 1,431 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: CI

on:
push:
branches: [main]
branches: [ main ]
pull_request:
workflow_dispatch:

Expand Down Expand Up @@ -32,7 +32,7 @@ jobs:
name: "cargo clippy"
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
os: [ ubuntu-latest, windows-latest ]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -113,7 +113,7 @@ jobs:
workspaces: "crates/uv-trampoline"
- name: "Clippy"
working-directory: crates/uv-trampoline
run: cargo clippy --all-features --locked -- -D warnings
run: cargo clippy --all-features --locked --target x86_64-pc-windows-msvc -- -D warnings
- name: "Build"
working-directory: crates/uv-trampoline
run: cargo build --release -Z build-std=core,panic_abort,alloc -Z build-std-features=compiler-builtins-mem --target x86_64-pc-windows-msvc
run: cargo build --release --target x86_64-pc-windows-msvc
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ Install uv with our standalone installers, or from [PyPI](https://pypi.org/proje
# On macOS and Linux.
curl -LsSf https://astral.sh/uv/install.sh | sh

# On Windows (with PowerShell).
irm https://astral.sh/uv/install.ps1 | iex
# On Windows.
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"

# With pip.
pip install uv
Expand Down
32 changes: 17 additions & 15 deletions crates/install-wheel-rs/src/wheel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ use crate::{find_dist_info, Error};
/// `#!/usr/bin/env python`
pub const SHEBANG_PYTHON: &str = "#!/usr/bin/env python";

const LAUNCHER_MAGIC_NUMBER: [u8; 4] = [b'U', b'V', b'U', b'V'];

#[cfg(all(windows, target_arch = "x86_64"))]
const LAUNCHER_X86_64_GUI: &[u8] =
include_bytes!("../../uv-trampoline/trampolines/uv-trampoline-x86_64-gui.exe");
Expand Down Expand Up @@ -281,19 +283,7 @@ fn unpack_wheel_files<R: Read + Seek>(
}

fn get_shebang(location: &InstallLocation<impl AsRef<Path>>) -> String {
let path = location.python().to_string_lossy().to_string();
let path = if cfg!(windows) {
// https://stackoverflow.com/a/50323079
const VERBATIM_PREFIX: &str = r"\\?\";
if let Some(stripped) = path.strip_prefix(VERBATIM_PREFIX) {
stripped.to_string()
} else {
path
}
} else {
path
};
format!("#!{path}")
format!("#!{}", location.python().normalized().display())
}

/// A Windows script is a minimal .exe launcher binary with the python entrypoint script appended as
Expand All @@ -305,6 +295,7 @@ fn get_shebang(location: &InstallLocation<impl AsRef<Path>>) -> String {
pub(crate) fn windows_script_launcher(
launcher_python_script: &str,
is_gui: bool,
installation: &InstallLocation<impl AsRef<Path>>,
) -> Result<Vec<u8>, Error> {
// This method should only be called on Windows, but we avoid `#[cfg(windows)]` to retain
// compilation on all platforms.
Expand Down Expand Up @@ -352,9 +343,20 @@ pub(crate) fn windows_script_launcher(
archive.finish().expect(error_msg);
}

let python = installation.python();
let python_path = python.normalized().to_string_lossy();

let mut launcher: Vec<u8> = Vec::with_capacity(launcher_bin.len() + payload.len());
launcher.extend_from_slice(launcher_bin);
launcher.extend_from_slice(&payload);
launcher.extend_from_slice(python_path.as_bytes());
launcher.extend_from_slice(
&u32::try_from(python_path.as_bytes().len())
.expect("File Path to be smaller than 4GB")
.to_le_bytes(),
);
launcher.extend_from_slice(&LAUNCHER_MAGIC_NUMBER);

Ok(launcher)
}

Expand Down Expand Up @@ -393,7 +395,7 @@ pub(crate) fn write_script_entrypoints(
write_file_recorded(
site_packages,
&entrypoint_relative,
&windows_script_launcher(&launcher_python_script, is_gui)?,
&windows_script_launcher(&launcher_python_script, is_gui, location)?,
record,
)?;
} else {
Expand Down Expand Up @@ -949,7 +951,7 @@ pub fn parse_key_value_file(
///
/// Wheel 1.0: <https://www.python.org/dev/peps/pep-0427/>
#[allow(clippy::too_many_arguments)]
#[instrument(skip_all, fields(name = %filename.name))]
#[instrument(skip_all, fields(name = % filename.name))]
pub fn install_wheel(
location: &InstallLocation<LockedDir>,
reader: impl Read + Seek,
Expand Down
98 changes: 56 additions & 42 deletions crates/uv-interpreter/src/interpreter.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::ffi::{OsStr, OsString};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;

Expand All @@ -16,6 +17,7 @@ use uv_cache::{Cache, CacheBucket, CachedByTimestamp, Freshness, Timestamp};
use uv_fs::write_atomic_sync;

use crate::python_platform::PythonPlatform;
use crate::python_query::try_find_default_python;
use crate::virtual_env::detect_virtual_env;
use crate::{find_requested_python, Error, PythonVersion};

Expand All @@ -35,12 +37,7 @@ impl Interpreter {
/// Detect the interpreter info for the given Python executable.
pub fn query(executable: &Path, platform: &Platform, cache: &Cache) -> Result<Self, Error> {
let info = InterpreterQueryResult::query_cached(executable, cache)?;
debug_assert!(
info.base_prefix == info.base_exec_prefix,
"Not a virtualenv (Python: {}, prefix: {})",
executable.display(),
info.base_prefix.display()
);

debug_assert!(
info.sys_executable.is_absolute(),
"`sys.executable` is not an absolute Python; Python installation is broken: {}",
Expand Down Expand Up @@ -170,38 +167,18 @@ impl Interpreter {

// Look for the requested version with by search for `python{major}.{minor}` in `PATH` on
// Unix and `py --list-paths` on Windows.
if let Some(python_version) = python_version {
if let Some(interpreter) =
find_requested_python(&python_version.string, platform, cache)?
{
if version_matches(&interpreter) {
return Ok(Some(interpreter));
}
}
}
let interpreter = if let Some(python_version) = python_version {
find_requested_python(&python_version.string, platform, cache)?
} else {
try_find_default_python(platform, cache)?
};

// Python discovery failed to find the requested version, maybe the default Python in PATH
// matches?
if cfg!(unix) {
if let Some(executable) = Interpreter::find_executable("python3")? {
debug!("Resolved python3 to {}", executable.display());
let interpreter = Interpreter::query(&executable, &python_platform.0, cache)?;
if version_matches(&interpreter) {
return Ok(Some(interpreter));
}
}
} else if cfg!(windows) {
if let Some(executable) = Interpreter::find_executable("python.exe")? {
let interpreter = Interpreter::query(&executable, &python_platform.0, cache)?;
if version_matches(&interpreter) {
return Ok(Some(interpreter));
}
}
if let Some(interpreter) = interpreter {
debug_assert!(version_matches(&interpreter));
Ok(Some(interpreter))
} else {
unimplemented!("Only Windows and Unix are supported");
Ok(None)
}

Ok(None)
}

/// Find the Python interpreter in `PATH`, respecting `UV_PYTHON_PATH`.
Expand Down Expand Up @@ -324,13 +301,50 @@ pub(crate) struct InterpreterQueryResult {
impl InterpreterQueryResult {
/// Return the resolved [`InterpreterQueryResult`] for the given Python executable.
pub(crate) fn query(interpreter: &Path) -> Result<Self, Error> {
let output = Command::new(interpreter)
.args(["-c", include_str!("get_interpreter_info.py")])
.output()
.map_err(|err| Error::PythonSubcommandLaunch {
interpreter: interpreter.to_path_buf(),
err,
})?;
let script = include_str!("get_interpreter_info.py");
let output = if cfg!(windows)
&& interpreter
.extension()
.is_some_and(|extension| extension == "bat")
{
// Multiline arguments aren't well-supported in batch files and `pyenv-win`, for example, trips over it.
// We work around this batch limitation by passing the script via stdin instead.
// This is somewhat more expensive because we have to spawn a new thread to write the
// stdin to avoid deadlocks in case the child process waits for the parent to read stdout.
// The performance overhead is the reason why we only applies this to batch files.
// https://github.com/pyenv-win/pyenv-win/issues/589
let mut child = Command::new(interpreter)
.arg("-")
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()
.map_err(|err| Error::PythonSubcommandLaunch {
interpreter: interpreter.to_path_buf(),
err,
})?;

let mut stdin = child.stdin.take().unwrap();

// From the Rust documentation:
// If the child process fills its stdout buffer, it may end up
// waiting until the parent reads the stdout, and not be able to
// read stdin in the meantime, causing a deadlock.
// Writing from another thread ensures that stdout is being read
// at the same time, avoiding the problem.
std::thread::spawn(move || {
stdin
.write_all(script.as_bytes())
.expect("failed to write to stdin");
});

child.wait_with_output()
} else {
Command::new(interpreter).arg("-c").arg(script).output()
}
.map_err(|err| Error::PythonSubcommandLaunch {
interpreter: interpreter.to_path_buf(),
err,
})?;

// stderr isn't technically a criterion for success, but i don't know of any cases where there
// should be stderr output and if there is, we want to know
Expand Down
13 changes: 6 additions & 7 deletions crates/uv-interpreter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ use std::ffi::OsString;
use std::io;
use std::path::PathBuf;

use pep440_rs::Version;
use thiserror::Error;

use uv_fs::Normalized;

pub use crate::cfg::Configuration;
pub use crate::interpreter::Interpreter;
pub use crate::python_query::{find_default_python, find_requested_python};
Expand Down Expand Up @@ -43,14 +40,18 @@ pub enum Error {
#[error("Failed to run `py --list-paths` to find Python installations. Is Python installed?")]
PyList(#[source] io::Error),
#[cfg(windows)]
#[error("No Python {0} found through `py --list-paths`. Is Python {0} installed?")]
#[error(
"No Python {0} found through `py --list-paths` or in `PATH`. Is Python {0} installed?"
)]
NoSuchPython(String),
#[cfg(unix)]
#[error("No Python {0} In `PATH`. Is Python {0} installed?")]
NoSuchPython(String),
#[error("Neither `python` nor `python3` are in `PATH`. Is Python installed?")]
NoPythonInstalledUnix,
#[error("Could not find `python.exe` in PATH and `py --list-paths` did not list any Python versions. Is Python installed?")]
#[error(
"Could not find `python.exe` through `py --list-paths` or in 'PATH'. Is Python installed?"
)]
NoPythonInstalledWindows,
#[error("{message}:\n--- stdout:\n{stdout}\n--- stderr:\n{stderr}\n---")]
PythonSubcommandOutput {
Expand All @@ -64,6 +65,4 @@ pub enum Error {
Cfg(#[from] cfg::Error),
#[error("Error finding `{}` in PATH", _0.to_string_lossy())]
WhichError(OsString, #[source] which::Error),
#[error("Interpreter at `{}` has the wrong patch version. Expected: {}, actual: {}", _0.normalized_display(), _1, _2)]
PatchVersionMismatch(PathBuf, String, Version),
}
Loading

0 comments on commit 8cb7cd5

Please sign in to comment.