mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Add support for global uv python pin
(#12115)
These changes add support for ``` uv python pin 3.12 --global ``` This adds the specified version to a `.python-version` file in the user-level config directory. uv will now use the user-level version as a fallback if no version is found in the project directory or its ancestors. Closes #4972
This commit is contained in:
parent
b4eabf9a61
commit
797f1fbac0
17 changed files with 366 additions and 16 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4579,6 +4579,7 @@ dependencies = [
|
|||
"uv-client",
|
||||
"uv-configuration",
|
||||
"uv-console",
|
||||
"uv-dirs",
|
||||
"uv-dispatch",
|
||||
"uv-distribution",
|
||||
"uv-distribution-filename",
|
||||
|
|
|
@ -4676,6 +4676,19 @@ pub struct PythonPinArgs {
|
|||
/// `requires-python` constraint.
|
||||
#[arg(long, alias = "no-workspace")]
|
||||
pub no_project: bool,
|
||||
|
||||
/// Update the global Python version pin.
|
||||
///
|
||||
/// Writes the pinned Python version to a `.python-version` file in the uv user configuration
|
||||
/// directory: `XDG_CONFIG_HOME/uv` on Linux/macOS and `%APPDATA%/uv` on Windows.
|
||||
///
|
||||
/// When a local Python version pin is not found in the working directory or an ancestor
|
||||
/// directory, this version will be used instead.
|
||||
///
|
||||
/// Unlike local version pins, this version is used as the default for commands that mutate
|
||||
/// global state, like `uv tool install`.
|
||||
#[arg(long)]
|
||||
pub global: bool,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
|
|
@ -103,6 +103,13 @@ pub fn user_config_dir() -> Option<PathBuf> {
|
|||
.ok()
|
||||
}
|
||||
|
||||
pub fn user_uv_config_dir() -> Option<PathBuf> {
|
||||
user_config_dir().map(|mut path| {
|
||||
path.push("uv");
|
||||
path
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn locate_system_config_xdg(value: Option<&str>) -> Option<PathBuf> {
|
||||
// On Linux and macOS, read the `XDG_CONFIG_DIRS` environment variable.
|
||||
|
|
|
@ -149,8 +149,6 @@ impl Conflicts {
|
|||
}
|
||||
|
||||
let Ok(topo_nodes) = toposort(&graph, None) else {
|
||||
// FIXME: If we hit a cycle, we are currently bailing and waiting for
|
||||
// more detailed cycle detection downstream. Is this what we want?
|
||||
return;
|
||||
};
|
||||
// Propagate canonical items through the graph and populate substitutions.
|
||||
|
|
|
@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
|
|||
use fs_err as fs;
|
||||
use itertools::Itertools;
|
||||
use tracing::debug;
|
||||
use uv_dirs::user_uv_config_dir;
|
||||
use uv_fs::Simplified;
|
||||
|
||||
use crate::PythonRequest;
|
||||
|
@ -69,7 +70,13 @@ impl PythonVersionFile {
|
|||
options: &DiscoveryOptions<'_>,
|
||||
) -> Result<Option<Self>, std::io::Error> {
|
||||
let Some(path) = Self::find_nearest(working_directory, options) else {
|
||||
return Ok(None);
|
||||
// Not found in directory or its ancestors. Looking in user-level config.
|
||||
return Ok(match user_uv_config_dir() {
|
||||
Some(user_dir) => Self::discover_user_config(user_dir, options)
|
||||
.await?
|
||||
.or(None),
|
||||
None => None,
|
||||
});
|
||||
};
|
||||
|
||||
if options.no_config {
|
||||
|
@ -84,6 +91,22 @@ impl PythonVersionFile {
|
|||
Self::try_from_path(path).await
|
||||
}
|
||||
|
||||
pub async fn discover_user_config(
|
||||
user_config_working_directory: impl AsRef<Path>,
|
||||
options: &DiscoveryOptions<'_>,
|
||||
) -> Result<Option<Self>, std::io::Error> {
|
||||
if !options.no_config {
|
||||
if let Some(path) =
|
||||
Self::find_in_directory(user_config_working_directory.as_ref(), options)
|
||||
.into_iter()
|
||||
.find(|path| path.is_file())
|
||||
{
|
||||
return Self::try_from_path(path).await;
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn find_nearest(path: impl AsRef<Path>, options: &DiscoveryOptions<'_>) -> Option<PathBuf> {
|
||||
path.as_ref()
|
||||
.ancestors()
|
||||
|
|
|
@ -347,6 +347,12 @@ impl EnvVars {
|
|||
/// Path to system-level configuration directory on Windows systems.
|
||||
pub const SYSTEMDRIVE: &'static str = "SYSTEMDRIVE";
|
||||
|
||||
/// Path to user-level configuration directory on Windows systems.
|
||||
pub const APPDATA: &'static str = "APPDATA";
|
||||
|
||||
/// Path to root directory of user's profile on Windows systems.
|
||||
pub const USERPROFILE: &'static str = "USERPROFILE";
|
||||
|
||||
/// Path to user-level configuration directory on Unix systems.
|
||||
pub const XDG_CONFIG_HOME: &'static str = "XDG_CONFIG_HOME";
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ uv-cli = { workspace = true }
|
|||
uv-client = { workspace = true }
|
||||
uv-configuration = { workspace = true }
|
||||
uv-console = { workspace = true }
|
||||
uv-dirs = { workspace = true }
|
||||
uv-dispatch = { workspace = true }
|
||||
uv-distribution = { workspace = true }
|
||||
uv-distribution-filename = { workspace = true }
|
||||
|
|
|
@ -7,6 +7,7 @@ use owo_colors::OwoColorize;
|
|||
use tracing::debug;
|
||||
|
||||
use uv_cache::Cache;
|
||||
use uv_dirs::user_uv_config_dir;
|
||||
use uv_fs::Simplified;
|
||||
use uv_python::{
|
||||
EnvironmentPreference, PythonInstallation, PythonPreference, PythonRequest, PythonVersionFile,
|
||||
|
@ -25,6 +26,7 @@ pub(crate) async fn pin(
|
|||
resolved: bool,
|
||||
python_preference: PythonPreference,
|
||||
no_project: bool,
|
||||
global: bool,
|
||||
cache: &Cache,
|
||||
printer: Printer,
|
||||
) -> Result<ExitStatus> {
|
||||
|
@ -43,8 +45,16 @@ pub(crate) async fn pin(
|
|||
}
|
||||
};
|
||||
|
||||
let version_file =
|
||||
PythonVersionFile::discover(project_dir, &VersionFileDiscoveryOptions::default()).await;
|
||||
let version_file = if global {
|
||||
if let Some(path) = user_uv_config_dir() {
|
||||
PythonVersionFile::discover_user_config(path, &VersionFileDiscoveryOptions::default())
|
||||
.await
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
PythonVersionFile::discover(project_dir, &VersionFileDiscoveryOptions::default()).await
|
||||
};
|
||||
|
||||
let Some(request) = request else {
|
||||
// Display the current pinned Python version
|
||||
|
@ -130,8 +140,16 @@ pub(crate) async fn pin(
|
|||
|
||||
let existing = version_file.ok().flatten();
|
||||
// TODO(zanieb): Allow updating the discovered version file with an `--update` flag.
|
||||
let new = PythonVersionFile::new(project_dir.join(PYTHON_VERSION_FILENAME))
|
||||
.with_versions(vec![request]);
|
||||
let new = if global {
|
||||
let Some(config_dir) = user_uv_config_dir() else {
|
||||
return Err(anyhow::anyhow!("No user-level config directory found."));
|
||||
};
|
||||
PythonVersionFile::new(config_dir.join(PYTHON_VERSION_FILENAME))
|
||||
.with_versions(vec![request])
|
||||
} else {
|
||||
PythonVersionFile::new(project_dir.join(PYTHON_VERSION_FILENAME))
|
||||
.with_versions(vec![request])
|
||||
};
|
||||
|
||||
new.write().await?;
|
||||
|
||||
|
|
|
@ -1257,6 +1257,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
args.resolved,
|
||||
globals.python_preference,
|
||||
args.no_project,
|
||||
args.global,
|
||||
&cache,
|
||||
printer,
|
||||
)
|
||||
|
|
|
@ -979,6 +979,7 @@ pub(crate) struct PythonPinSettings {
|
|||
pub(crate) request: Option<String>,
|
||||
pub(crate) resolved: bool,
|
||||
pub(crate) no_project: bool,
|
||||
pub(crate) global: bool,
|
||||
}
|
||||
|
||||
impl PythonPinSettings {
|
||||
|
@ -990,12 +991,14 @@ impl PythonPinSettings {
|
|||
no_resolved,
|
||||
resolved,
|
||||
no_project,
|
||||
global,
|
||||
} = args;
|
||||
|
||||
Self {
|
||||
request,
|
||||
resolved: flag(resolved, no_resolved).unwrap_or(false),
|
||||
no_project,
|
||||
global,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,6 +89,7 @@ pub struct TestContext {
|
|||
pub cache_dir: ChildPath,
|
||||
pub python_dir: ChildPath,
|
||||
pub home_dir: ChildPath,
|
||||
pub user_config_dir: ChildPath,
|
||||
pub bin_dir: ChildPath,
|
||||
pub venv: ChildPath,
|
||||
pub workspace_root: PathBuf,
|
||||
|
@ -357,6 +358,12 @@ impl TestContext {
|
|||
let home_dir = ChildPath::new(root.path()).child("home");
|
||||
fs_err::create_dir_all(&home_dir).expect("Failed to create test home directory");
|
||||
|
||||
let user_config_dir = if cfg!(windows) {
|
||||
ChildPath::new(home_dir.path())
|
||||
} else {
|
||||
ChildPath::new(home_dir.path()).child(".config")
|
||||
};
|
||||
|
||||
// Canonicalize the temp dir for consistent snapshot behavior
|
||||
let canonical_temp_dir = temp_dir.canonicalize().unwrap();
|
||||
let venv = ChildPath::new(canonical_temp_dir.join(".venv"));
|
||||
|
@ -472,6 +479,18 @@ impl TestContext {
|
|||
.into_iter()
|
||||
.map(|pattern| (pattern, "[PYTHON_DIR]/".to_string())),
|
||||
);
|
||||
let mut uv_user_config_dir = PathBuf::from(user_config_dir.path());
|
||||
uv_user_config_dir.push("uv");
|
||||
filters.extend(
|
||||
Self::path_patterns(&uv_user_config_dir)
|
||||
.into_iter()
|
||||
.map(|pattern| (pattern, "[UV_USER_CONFIG_DIR]/".to_string())),
|
||||
);
|
||||
filters.extend(
|
||||
Self::path_patterns(&user_config_dir)
|
||||
.into_iter()
|
||||
.map(|pattern| (pattern, "[USER_CONFIG_DIR]/".to_string())),
|
||||
);
|
||||
filters.extend(
|
||||
Self::path_patterns(&home_dir)
|
||||
.into_iter()
|
||||
|
@ -532,6 +551,7 @@ impl TestContext {
|
|||
cache_dir,
|
||||
python_dir,
|
||||
home_dir,
|
||||
user_config_dir,
|
||||
bin_dir,
|
||||
venv,
|
||||
workspace_root,
|
||||
|
@ -606,6 +626,8 @@ impl TestContext {
|
|||
.env(EnvVars::COLUMNS, "100")
|
||||
.env(EnvVars::PATH, path)
|
||||
.env(EnvVars::HOME, self.home_dir.as_os_str())
|
||||
.env(EnvVars::APPDATA, self.home_dir.as_os_str())
|
||||
.env(EnvVars::USERPROFILE, self.home_dir.as_os_str())
|
||||
.env(EnvVars::UV_PYTHON_INSTALL_DIR, "")
|
||||
// Installations are not allowed by default; see `Self::with_managed_python_dirs`
|
||||
.env(EnvVars::UV_PYTHON_DOWNLOADS, "never")
|
||||
|
@ -616,6 +638,7 @@ impl TestContext {
|
|||
.env(EnvVars::UV_TEST_NO_CLI_PROGRESS, "1")
|
||||
.env_remove(EnvVars::UV_CACHE_DIR)
|
||||
.env_remove(EnvVars::UV_TOOL_BIN_DIR)
|
||||
.env_remove(EnvVars::XDG_CONFIG_HOME)
|
||||
.current_dir(self.temp_dir.path());
|
||||
|
||||
for (key, value) in &self.extra_env {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use crate::common::{uv_snapshot, TestContext};
|
||||
use anyhow::Result;
|
||||
use assert_fs::fixture::{FileWriteStr, PathChild};
|
||||
use assert_fs::fixture::{FileWriteStr, PathChild, PathCreateDir};
|
||||
use insta::assert_snapshot;
|
||||
use uv_python::{
|
||||
platform::{Arch, Os},
|
||||
|
@ -198,6 +200,134 @@ fn python_pin() {
|
|||
}
|
||||
}
|
||||
|
||||
// If there is no project-level `.python-version` file, respect the global pin.
|
||||
#[test]
|
||||
fn python_pin_global_if_no_local() -> Result<()> {
|
||||
let context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"]);
|
||||
let uv = context.user_config_dir.child("uv");
|
||||
uv.create_dir_all()?;
|
||||
|
||||
// Without arguments, we attempt to read the current pin (which does not exist yet)
|
||||
uv_snapshot!(context.filters(), context.python_pin(), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: No pinned Python version found
|
||||
"###);
|
||||
|
||||
// Given an argument, we globally pin to that version
|
||||
uv_snapshot!(context.filters(), context.python_pin().arg("3.11").arg("--global"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Pinned `[UV_USER_CONFIG_DIR]/.python-version` to `3.11`
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// If no local pin, use global.
|
||||
uv_snapshot!(context.filters(), context.python_pin(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
3.11
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// If there is a project-level `.python-version` file, it takes precedence over
|
||||
// the global pin.
|
||||
#[test]
|
||||
fn python_pin_global_use_local_if_available() -> Result<()> {
|
||||
let context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"]);
|
||||
let uv = context.user_config_dir.child("uv");
|
||||
uv.create_dir_all()?;
|
||||
|
||||
// Given an argument, we globally pin to that version
|
||||
uv_snapshot!(context.filters(), context.python_pin().arg("3.12").arg("--global"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Pinned `[UV_USER_CONFIG_DIR]/.python-version` to `3.12`
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// With no local, we get the global pin
|
||||
uv_snapshot!(context.filters(), context.python_pin(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
3.12
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
let mut global_version_path = PathBuf::from(uv.path());
|
||||
global_version_path.push(PYTHON_VERSION_FILENAME);
|
||||
let global_python_version = context.read(&global_version_path);
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(global_python_version, @r###"
|
||||
3.12
|
||||
"###);
|
||||
});
|
||||
|
||||
// Request Python 3.11 for local .python-version
|
||||
uv_snapshot!(context.filters(), context.python_pin().arg("3.11"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Pinned `.python-version` to `3.11`
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// Local should override global
|
||||
uv_snapshot!(context.filters(), context.python_pin(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
3.11
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// We should still be able to check global pin
|
||||
uv_snapshot!(context.filters(), context.python_pin().arg("--global"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
3.12
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// Local .python-version exists and has the right version.
|
||||
let local_python_version = context.read(PYTHON_VERSION_FILENAME);
|
||||
assert_snapshot!(local_python_version, @r###"
|
||||
3.11
|
||||
"###);
|
||||
|
||||
// Global .python-version still exists and has the right version.
|
||||
let global_python_version = context.read(&global_version_path);
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(global_python_version, @r###"
|
||||
3.12
|
||||
"###);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// We do not need a Python interpreter to pin without `--resolved`
|
||||
/// (skip on Windows because the snapshot is different and the behavior is not platform dependent)
|
||||
#[cfg(unix)]
|
||||
|
|
|
@ -2387,7 +2387,6 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
|||
ignore = "Configuration tests are not yet supported on Windows"
|
||||
)]
|
||||
fn resolve_user_configuration() -> anyhow::Result<()> {
|
||||
// Create a temporary directory to store the user configuration.
|
||||
let xdg = assert_fs::TempDir::new().expect("Failed to create temp dir");
|
||||
let uv = xdg.child("uv");
|
||||
let config = uv.child("uv.toml");
|
||||
|
@ -3618,6 +3617,7 @@ fn invalid_conflicts() -> anyhow::Result<()> {
|
|||
#[test]
|
||||
fn valid_conflicts() -> anyhow::Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let xdg = assert_fs::TempDir::new().expect("Failed to create temp dir");
|
||||
let pyproject = context.temp_dir.child("pyproject.toml");
|
||||
|
||||
// Write in `pyproject.toml` schema.
|
||||
|
@ -3632,7 +3632,8 @@ fn valid_conflicts() -> anyhow::Result<()> {
|
|||
[{extra = "x1"}, {extra = "x2"}],
|
||||
]
|
||||
"#})?;
|
||||
uv_snapshot!(context.filters(), add_shared_args(context.lock(), context.temp_dir.path()), @r###"
|
||||
uv_snapshot!(context.filters(), add_shared_args(context.lock(), context.temp_dir.path())
|
||||
.env("XDG_CONFIG_HOME", xdg.path()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
|
|
@ -175,6 +175,111 @@ fn tool_install() {
|
|||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_install_with_global_python() -> Result<()> {
|
||||
let context = TestContext::new_with_versions(&["3.11", "3.12"])
|
||||
.with_filtered_counts()
|
||||
.with_filtered_exe_suffix();
|
||||
let tool_dir = context.temp_dir.child("tools");
|
||||
let bin_dir = context.temp_dir.child("bin");
|
||||
let uv = context.user_config_dir.child("uv");
|
||||
let versions = uv.child(".python-version");
|
||||
versions.write_str("3.11")?;
|
||||
|
||||
// Install a tool
|
||||
uv_snapshot!(context.filters(), context.tool_install()
|
||||
.arg("flask")
|
||||
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
|
||||
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
|
||||
.env(EnvVars::PATH, bin_dir.as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved [N] packages in [TIME]
|
||||
Prepared [N] packages in [TIME]
|
||||
Installed [N] packages in [TIME]
|
||||
+ blinker==1.7.0
|
||||
+ click==8.1.7
|
||||
+ flask==3.0.2
|
||||
+ itsdangerous==2.1.2
|
||||
+ jinja2==3.1.3
|
||||
+ markupsafe==2.1.5
|
||||
+ werkzeug==3.0.1
|
||||
Installed 1 executable: flask
|
||||
"###);
|
||||
|
||||
tool_dir.child("flask").assert(predicate::path::is_dir());
|
||||
assert!(bin_dir
|
||||
.child(format!("flask{}", std::env::consts::EXE_SUFFIX))
|
||||
.exists());
|
||||
|
||||
uv_snapshot!(context.filters(), Command::new("flask").arg("--version").env(EnvVars::PATH, bin_dir.as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Python 3.11.[X]
|
||||
Flask 3.0.2
|
||||
Werkzeug 3.0.1
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// Change global version
|
||||
uv_snapshot!(context.filters(), context.python_pin().arg("3.12").arg("--global"),
|
||||
@r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Updated `[UV_USER_CONFIG_DIR]/.python-version` from `3.11` -> `3.12`
|
||||
|
||||
----- stderr -----
|
||||
"
|
||||
);
|
||||
|
||||
// Install flask again
|
||||
uv_snapshot!(context.filters(), context.tool_install()
|
||||
.arg("flask")
|
||||
.arg("--reinstall")
|
||||
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
|
||||
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
|
||||
.env(EnvVars::PATH, bin_dir.as_os_str()), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved [N] packages in [TIME]
|
||||
Prepared [N] packages in [TIME]
|
||||
Uninstalled [N] packages in [TIME]
|
||||
Installed [N] packages in [TIME]
|
||||
~ blinker==1.7.0
|
||||
~ click==8.1.7
|
||||
~ flask==3.0.2
|
||||
~ itsdangerous==2.1.2
|
||||
~ jinja2==3.1.3
|
||||
~ markupsafe==2.1.5
|
||||
~ werkzeug==3.0.1
|
||||
Installed 1 executable: flask
|
||||
");
|
||||
|
||||
// Currently, when reinstalling a tool we use the original version the tool
|
||||
// was installed with, not the most up-to-date global version
|
||||
uv_snapshot!(context.filters(), Command::new("flask").arg("--version").env(EnvVars::PATH, bin_dir.as_os_str()), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Python 3.11.[X]
|
||||
Flask 3.0.2
|
||||
Werkzeug 3.0.1
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_install_with_editable() -> Result<()> {
|
||||
let context = TestContext::new("3.12")
|
||||
|
@ -1471,7 +1576,7 @@ fn tool_install_uninstallable() {
|
|||
.arg("pyenv")
|
||||
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
|
||||
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
|
||||
.env(EnvVars::PATH, bin_dir.as_os_str()), @r###"
|
||||
.env(EnvVars::PATH, bin_dir.as_os_str()), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
@ -1499,7 +1604,7 @@ fn tool_install_uninstallable() {
|
|||
|
||||
|
||||
hint: This usually indicates a problem with the package or the build environment.
|
||||
"###);
|
||||
");
|
||||
|
||||
// Ensure the tool environment is not created.
|
||||
tool_dir.child("pyenv").assert(predicate::path::missing());
|
||||
|
|
|
@ -52,16 +52,20 @@ This behavior can be
|
|||
### Python version files
|
||||
|
||||
The `.python-version` file can be used to create a default Python version request. uv searches for a
|
||||
`.python-version` file in the working directory and each of its parents. Any of the request formats
|
||||
described above can be used, though use of a version number is recommended for interoperability with
|
||||
other tools.
|
||||
`.python-version` file in the working directory and each of its parents. If none is found, uv will
|
||||
check the user-level configuration directory. Any of the request formats described above can be
|
||||
used, though use of a version number is recommended for interoperability with other tools.
|
||||
|
||||
A `.python-version` file can be created in the current directory with the
|
||||
[`uv python pin`](../reference/cli.md/#uv-python-pin) command.
|
||||
|
||||
A global `.python-version` file can be created in the user configuration directory with the
|
||||
[`uv python pin --global`](../reference/cli.md/#uv-python-pin) command.
|
||||
|
||||
Discovery of `.python-version` files can be disabled with `--no-config`.
|
||||
|
||||
uv will not search for `.python-version` files beyond project or workspace boundaries.
|
||||
uv will not search for `.python-version` files beyond project or workspace boundaries (with the
|
||||
exception of the user configuration directory).
|
||||
|
||||
## Installing a Python version
|
||||
|
||||
|
|
|
@ -407,6 +407,10 @@ Used for trusted publishing via `uv publish`. Contains the oidc token url.
|
|||
|
||||
General proxy for all network requests.
|
||||
|
||||
### `APPDATA`
|
||||
|
||||
Path to user-level configuration directory on Windows systems.
|
||||
|
||||
### `BASH_VERSION`
|
||||
|
||||
Used to detect Bash shell usage.
|
||||
|
@ -567,6 +571,10 @@ Path to system-level configuration directory on Windows systems.
|
|||
|
||||
Use to create the tracing durations file via the `tracing-durations-export` feature.
|
||||
|
||||
### `USERPROFILE`
|
||||
|
||||
Path to root directory of user's profile on Windows systems.
|
||||
|
||||
### `UV`
|
||||
|
||||
The path to the binary that was used to invoke uv.
|
||||
|
|
|
@ -5186,6 +5186,14 @@ uv python pin [OPTIONS] [REQUEST]
|
|||
|
||||
<p>See <code>--project</code> to only change the project root directory.</p>
|
||||
|
||||
</dd><dt id="uv-python-pin--global"><a href="#uv-python-pin--global"><code>--global</code></a></dt><dd><p>Update the global Python version pin.</p>
|
||||
|
||||
<p>Writes the pinned Python version to a <code>.python-version</code> file in the uv user configuration directory: <code>XDG_CONFIG_HOME/uv</code> on Linux/macOS and <code>%APPDATA%/uv</code> on Windows.</p>
|
||||
|
||||
<p>When a local Python version pin is not found in the working directory or an ancestor directory, this version will be used instead.</p>
|
||||
|
||||
<p>Unlike local version pins, this version is used as the default for commands that mutate global state, like <code>uv tool install</code>.</p>
|
||||
|
||||
</dd><dt id="uv-python-pin--help"><a href="#uv-python-pin--help"><code>--help</code></a>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
|
||||
|
||||
</dd><dt id="uv-python-pin--native-tls"><a href="#uv-python-pin--native-tls"><code>--native-tls</code></a></dt><dd><p>Whether to load TLS certificates from the platform’s native certificate store.</p>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue