From c28b8a8d83438490fa62a46e3afef1f9df5ca02c Mon Sep 17 00:00:00 2001 From: staticf0x Date: Tue, 15 Jul 2025 16:10:06 +0200 Subject: [PATCH 1/3] Respect .python-version in pip-compile --- crates/uv/src/commands/pip/compile.rs | 44 +++++++++++++++ crates/uv/tests/it/pip_compile.rs | 77 +++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index c407167633f5e..7a955b43c1868 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -1,6 +1,7 @@ use std::collections::{BTreeMap, BTreeSet}; use std::env; use std::ffi::OsStr; +use std::fs; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -156,6 +157,49 @@ pub(crate) async fn pip_compile( } } + // Respect `.python-version` file + if python.is_none() && python_version.is_none() { + let mut version_file: Option = None; + + if PathBuf::from(".python-version").exists() { + // If there is `.python-version` in the current directory, use that. + // This is for cases like `uv pip compile requirements.in`. + version_file = Some(PathBuf::from(".python-version")); + } else if !requirements.is_empty() { + // If there is no `.python-version` present, try to find a sibling + // to the first RequirementsSource, which contains a path. + let file_name = match &requirements[0] { + RequirementsSource::PylockToml(file_name) => Some(file_name), + RequirementsSource::RequirementsTxt(file_name) => Some(file_name), + RequirementsSource::PyprojectToml(file_name) => Some(file_name), + RequirementsSource::SetupPy(file_name) => Some(file_name), + RequirementsSource::SetupCfg(file_name) => Some(file_name), + RequirementsSource::EnvironmentYml(file_name) => Some(file_name), + _ => None, + }; + + if let Some(file_name) = file_name { + let version_file_path = file_name + .as_path() + .parent() + .unwrap() + .join(PathBuf::from(".python-version")); + + if version_file_path.exists() { + version_file = Some(version_file_path); + } + } + } + + if let Some(version_file) = version_file + && let Ok(request) = fs::read_to_string(version_file) + { + if !request.is_empty() { + python = Some(request.trim().to_string()); + } + } + } + // If `--python` / `-p` is a simple Python version request, we treat it as `--python-version` // for backwards compatibility. `-p` was previously aliased to `--python-version` but changed to // `--python` for consistency with the rest of the CLI in v0.6.0. Since we assume metadata is diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index b99be12967c5d..b98870b0b3f01 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -17603,3 +17603,80 @@ fn omit_python_patch_universal() -> Result<()> { Ok(()) } + +#[test] +fn compile_requirements_in_respect_python_version_default() -> Result<()> { + let context = TestContext::new_with_versions(&["3.12", "3.11"]).with_filtered_python_sources(); + + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("requests\nrich ; python_version >= '3.12'\n")?; + + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in"), @r" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in + certifi==2024.2.2 + # via requests + charset-normalizer==3.3.2 + # via requests + idna==3.6 + # via requests + markdown-it-py==3.0.0 + # via rich + mdurl==0.1.2 + # via markdown-it-py + pygments==2.17.2 + # via rich + requests==2.31.0 + # via -r requirements.in + rich==13.7.1 + # via -r requirements.in + urllib3==2.2.1 + # via requests + + ----- stderr ----- + Resolved 9 packages in [TIME] + " + ); + + Ok(()) +} + +#[test] +fn compile_requirements_in_respect_python_version() -> Result<()> { + let context = TestContext::new_with_versions(&["3.12", "3.11"]).with_filtered_python_sources(); + + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("requests\nrich ; python_version >= '3.12'\n")?; + + let python_version = context.temp_dir.child(".python-version"); + python_version.write_str("3.11")?; + + uv_snapshot!(context.filters(), context.pip_compile() + .arg("requirements.in"), @r" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] requirements.in + certifi==2024.2.2 + # via requests + charset-normalizer==3.3.2 + # via requests + idna==3.6 + # via requests + requests==2.31.0 + # via -r requirements.in + urllib3==2.2.1 + # via requests + + ----- stderr ----- + Resolved 5 packages in [TIME] + " + ); + + Ok(()) +} From 403b41cc1a4d34182fffb42b767a21d13e3420f8 Mon Sep 17 00:00:00 2001 From: staticf0x Date: Tue, 15 Jul 2025 17:01:50 +0200 Subject: [PATCH 2/3] Don't use if-let chain --- crates/uv/src/commands/pip/compile.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index 7a955b43c1868..7a73741a83c59 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -191,11 +191,11 @@ pub(crate) async fn pip_compile( } } - if let Some(version_file) = version_file - && let Ok(request) = fs::read_to_string(version_file) - { - if !request.is_empty() { - python = Some(request.trim().to_string()); + if let Some(version_file) = version_file { + if let Ok(request) = fs::read_to_string(version_file) { + if !request.is_empty() { + python = Some(request.trim().to_string()); + } } } } From 0507b50645e0786c51512a4da08661460601e87f Mon Sep 17 00:00:00 2001 From: staticf0x Date: Tue, 15 Jul 2025 17:13:20 +0200 Subject: [PATCH 3/3] Use fs_err and only read the first line --- crates/uv/src/commands/pip/compile.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index 7a73741a83c59..e8b067c6e90fe 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -1,7 +1,7 @@ +use fs_err as fs; use std::collections::{BTreeMap, BTreeSet}; use std::env; use std::ffi::OsStr; -use std::fs; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -194,7 +194,9 @@ pub(crate) async fn pip_compile( if let Some(version_file) = version_file { if let Ok(request) = fs::read_to_string(version_file) { if !request.is_empty() { - python = Some(request.trim().to_string()); + if let Some(first_line) = request.lines().next() { + python = Some(first_line.trim().to_string()); + } } } }