diff --git a/crates/uv-python/src/lib.rs b/crates/uv-python/src/lib.rs index 8c429ad5736a..dba4de22ee7a 100644 --- a/crates/uv-python/src/lib.rs +++ b/crates/uv-python/src/lib.rs @@ -14,10 +14,11 @@ pub use crate::prefix::Prefix; pub use crate::python_version::PythonVersion; pub use crate::target::Target; pub use crate::version_files::{ - request_from_version_file, requests_from_version_file, PYTHON_VERSIONS_FILENAME, - PYTHON_VERSION_FILENAME, + request_from_version_file, requests_from_version_file, write_version_file, + PYTHON_VERSIONS_FILENAME, PYTHON_VERSION_FILENAME, }; pub use crate::virtualenv::{Error as VirtualEnvError, PyVenvConfiguration, VirtualEnvironment}; + mod discovery; pub mod downloads; mod environment; @@ -83,19 +84,19 @@ pub enum Error { // TODO(zanieb): We should write a mock interpreter script that works on Windows #[cfg(all(test, unix))] mod tests { - use anyhow::Result; - use indoc::{formatdoc, indoc}; - use std::{ env, ffi::{OsStr, OsString}, path::{Path, PathBuf}, str::FromStr, }; + + use anyhow::Result; + use assert_fs::{fixture::ChildPath, prelude::*, TempDir}; + use indoc::{formatdoc, indoc}; use temp_env::with_vars; use test_log::test; - use assert_fs::{fixture::ChildPath, prelude::*, TempDir}; use uv_cache::Cache; use crate::{ diff --git a/crates/uv-python/src/version_files.rs b/crates/uv-python/src/version_files.rs index 7e9f9db22cc6..4067d1b4dd23 100644 --- a/crates/uv-python/src/version_files.rs +++ b/crates/uv-python/src/version_files.rs @@ -46,6 +46,12 @@ pub async fn request_from_version_file() -> Result, std::i } } +/// Write a version to a .`python-version` file. +pub async fn write_version_file(version: &str) -> Result<(), std::io::Error> { + debug!("Writing Python version `{version}` to `{PYTHON_VERSION_FILENAME}`"); + fs::tokio::write(PYTHON_VERSION_FILENAME, format!("{version}\n")).await +} + async fn read_versions_file() -> Result>, std::io::Error> { match fs::tokio::read_to_string(PYTHON_VERSIONS_FILENAME).await { Ok(content) => { diff --git a/crates/uv/src/commands/python/pin.rs b/crates/uv/src/commands/python/pin.rs index bcb7b81bb88d..eb0639b695b4 100644 --- a/crates/uv/src/commands/python/pin.rs +++ b/crates/uv/src/commands/python/pin.rs @@ -1,15 +1,15 @@ use std::fmt::Write; -use std::path::PathBuf; use anyhow::{bail, Result}; +use owo_colors::OwoColorize; -use tracing::debug; use uv_cache::Cache; use uv_configuration::PreviewMode; use uv_fs::Simplified; use uv_python::{ - requests_from_version_file, EnvironmentPreference, PythonInstallation, PythonPreference, - PythonRequest, PYTHON_VERSION_FILENAME, + request_from_version_file, requests_from_version_file, write_version_file, + EnvironmentPreference, PythonInstallation, PythonPreference, PythonRequest, + PYTHON_VERSION_FILENAME, }; use uv_warnings::warn_user_once; @@ -50,7 +50,7 @@ pub(crate) async fn pin( Ok(python) => Some(python), // If no matching Python version is found, don't fail unless `resolved` was requested Err(uv_python::Error::MissingPython(err)) if !resolved => { - warn_user_once!("{}", err); + warn_user_once!("{err}"); None } Err(err) => return Err(err.into()), @@ -68,16 +68,27 @@ pub(crate) async fn pin( request.to_canonical_string() }; - debug!("Using pin `{}`", output); - let version_file = PathBuf::from(PYTHON_VERSION_FILENAME); - let exists = version_file.exists(); + let existing = request_from_version_file().await.ok().flatten(); + write_version_file(&output).await?; - debug!("Writing pin to {}", version_file.user_display()); - fs_err::write(&version_file, format!("{output}\n"))?; - if exists { - writeln!(printer.stdout(), "Replaced existing pin with `{output}`")?; + if let Some(existing) = existing + .map(|existing| existing.to_canonical_string()) + .filter(|existing| existing != &output) + { + writeln!( + printer.stdout(), + "Updated `{}` from `{}` -> `{}`", + PYTHON_VERSION_FILENAME.cyan(), + existing.green(), + output.green() + )?; } else { - writeln!(printer.stdout(), "Pinned to `{output}`")?; + writeln!( + printer.stdout(), + "Pinned `{}` to `{}`", + PYTHON_VERSION_FILENAME.cyan(), + output.green() + )?; } Ok(ExitStatus::Success) diff --git a/crates/uv/tests/python_pin.rs b/crates/uv/tests/python_pin.rs index 326cfae09ee5..08e00ec0d470 100644 --- a/crates/uv/tests/python_pin.rs +++ b/crates/uv/tests/python_pin.rs @@ -28,7 +28,7 @@ fn python_pin() { success: true exit_code: 0 ----- stdout ----- - Pinned to `any` + Pinned `.python-version` to `any` ----- stderr ----- "###); @@ -57,7 +57,7 @@ fn python_pin() { success: true exit_code: 0 ----- stdout ----- - Replaced existing pin with `3.12` + Updated `.python-version` from `any` -> `3.12` ----- stderr ----- "###); @@ -73,7 +73,7 @@ fn python_pin() { success: true exit_code: 0 ----- stdout ----- - Replaced existing pin with `3.11` + Updated `.python-version` from `3.12` -> `3.11` ----- stderr ----- "###); @@ -89,7 +89,7 @@ fn python_pin() { success: true exit_code: 0 ----- stdout ----- - Replaced existing pin with `cpython` + Updated `.python-version` from `3.11` -> `cpython` ----- stderr ----- "###); @@ -105,7 +105,7 @@ fn python_pin() { success: true exit_code: 0 ----- stdout ----- - Replaced existing pin with `cpython@3.12` + Updated `.python-version` from `cpython` -> `cpython@3.12` ----- stderr ----- "###); @@ -121,7 +121,7 @@ fn python_pin() { success: true exit_code: 0 ----- stdout ----- - Replaced existing pin with `cpython@3.12` + Pinned `.python-version` to `cpython@3.12` ----- stderr ----- "###); @@ -137,7 +137,7 @@ fn python_pin() { success: true exit_code: 0 ----- stdout ----- - Replaced existing pin with `cpython-3.12-any-any-any` + Updated `.python-version` from `cpython@3.12` -> `cpython-3.12-any-any-any` ----- stderr ----- "###); @@ -153,7 +153,7 @@ fn python_pin() { success: true exit_code: 0 ----- stdout ----- - Replaced existing pin with `[PYTHON-3.11]` + Updated `.python-version` from `cpython-3.12-any-any-any` -> `[PYTHON-3.11]` ----- stderr ----- "###); @@ -176,7 +176,7 @@ fn python_pin() { success: true exit_code: 0 ----- stdout ----- - Replaced existing pin with `pypy` + Updated `.python-version` from `[PYTHON-3.11]` -> `pypy` ----- stderr ----- warning: No interpreter found for PyPy in system path @@ -194,14 +194,14 @@ fn python_pin() { #[cfg(unix)] { uv_snapshot!(context.filters(), context.python_pin().arg("3.7"), @r###" - success: true - exit_code: 0 - ----- stdout ----- - Replaced existing pin with `3.7` + success: true + exit_code: 0 + ----- stdout ----- + Updated `.python-version` from `pypy` -> `3.7` - ----- stderr ----- - warning: No interpreter found for Python 3.7 in system path - "###); + ----- stderr ----- + warning: No interpreter found for Python 3.7 in system path + "###); let python_version = fs_err::read_to_string(context.temp_dir.join(PYTHON_VERSION_FILENAME)).unwrap(); @@ -222,7 +222,7 @@ fn python_pin_no_python() { success: true exit_code: 0 ----- stdout ----- - Pinned to `3.12` + Pinned `.python-version` to `3.12` ----- stderr ----- warning: No interpreter found for Python 3.12 in system path @@ -264,7 +264,7 @@ fn python_pin_resolve() { success: true exit_code: 0 ----- stdout ----- - Pinned to `[PYTHON-3.11]` + Pinned `.python-version` to `[PYTHON-3.11]` ----- stderr ----- "###); @@ -284,7 +284,7 @@ fn python_pin_resolve() { success: true exit_code: 0 ----- stdout ----- - Replaced existing pin with `[PYTHON-3.12]` + Updated `.python-version` from `[PYTHON-3.11]` -> `[PYTHON-3.12]` ----- stderr ----- "###); @@ -304,7 +304,7 @@ fn python_pin_resolve() { success: true exit_code: 0 ----- stdout ----- - Replaced existing pin with `[PYTHON-3.11]` + Updated `.python-version` from `[PYTHON-3.12]` -> `[PYTHON-3.11]` ----- stderr ----- "###); @@ -324,7 +324,7 @@ fn python_pin_resolve() { success: true exit_code: 0 ----- stdout ----- - Replaced existing pin with `[PYTHON-3.11]` + Pinned `.python-version` to `[PYTHON-3.11]` ----- stderr ----- "###); @@ -344,7 +344,7 @@ fn python_pin_resolve() { success: true exit_code: 0 ----- stdout ----- - Replaced existing pin with `[PYTHON-3.12]` + Updated `.python-version` from `[PYTHON-3.11]` -> `[PYTHON-3.12]` ----- stderr ----- "###); @@ -364,7 +364,7 @@ fn python_pin_resolve() { success: true exit_code: 0 ----- stdout ----- - Replaced existing pin with `[PYTHON-3.12]` + Pinned `.python-version` to `[PYTHON-3.12]` ----- stderr ----- "###); @@ -389,7 +389,7 @@ fn python_pin_resolve() { success: true exit_code: 0 ----- stdout ----- - Replaced existing pin with `[PYTHON-3.12]` + Pinned `.python-version` to `[PYTHON-3.12]` ----- stderr ----- "###);