chore: unify all env vars used (#8151)

## Summary

This PR declares and documents all environment variables that are used
in one way or another in `uv`, either internally, or externally, or
transitively under a common struct.

I think over time as uv has grown there's been many environment
variables introduced. Its harder to know which ones exists, which ones
are missing, what they're used for, or where are they used across the
code. The docs only documents a handful of them, for others you'd have
to dive into the code and inspect across crates to know which crates
they're used on or where they're relevant.

This PR is a starting attempt to unify them, make it easier to discover
which ones we have, and maybe unlock future posibilities in automating
generating documentation for them.

I think we can split out into multiple structs later to better organize,
but given the high influx of PR's and possibly new environment variables
introduced/re-used, it would be hard to try to organize them all now
into their proper namespaced struct while this is all happening given
merge conflicts and/or keeping up to date.

I don't think this has any impact on performance as they all should
still be inlined, although it may affect local build times on changes to
the environment vars as more crates would likely need a rebuild. Lastly,
some of them are declared but not used in the code, for example those in
`build.rs`. I left them declared because I still think it's useful to at
least have a reference.

Did I miss any? Are their initial docs cohesive?

Note, `uv-static` is a terrible name for a new crate, thoughts? Others
considered `uv-vars`, `uv-consts`.

## Test Plan

Existing tests
This commit is contained in:
samypr100 2024-10-14 21:48:13 +00:00 committed by GitHub
parent 0a23be4a6a
commit 01c44af3c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
86 changed files with 1572 additions and 1104 deletions

View file

@ -13,6 +13,7 @@ use uv_cache::Cache;
use uv_fs::which::is_executable;
use uv_fs::Simplified;
use uv_pep440::{Prerelease, Version, VersionSpecifier, VersionSpecifiers};
use uv_static::EnvVars;
use uv_warnings::warn_user_once;
use crate::downloads::PythonDownloadRequest;
@ -344,7 +345,7 @@ fn python_executables_from_installed<'a>(
}
};
env::var_os("UV_TEST_PYTHON_PATH")
env::var_os(EnvVars::UV_TEST_PYTHON_PATH)
.is_none()
.then(|| {
registry_pythons()
@ -401,7 +402,7 @@ fn python_executables<'a>(
) -> Box<dyn Iterator<Item = Result<(PythonSource, PathBuf), Error>> + 'a> {
// Always read from `UV_INTERNAL__PARENT_INTERPRETER` — it could be a system interpreter
let from_parent_interpreter = std::iter::once_with(|| {
std::env::var_os("UV_INTERNAL__PARENT_INTERPRETER")
std::env::var_os(EnvVars::UV_INTERNAL__PARENT_INTERPRETER)
.into_iter()
.map(|path| Ok((PythonSource::ParentInterpreter, PathBuf::from(path))))
})
@ -443,8 +444,8 @@ fn python_executables_from_search_path<'a>(
implementation: Option<&'a ImplementationName>,
) -> impl Iterator<Item = PathBuf> + 'a {
// `UV_TEST_PYTHON_PATH` can be used to override `PATH` to limit Python executable availability in the test suite
let search_path =
env::var_os("UV_TEST_PYTHON_PATH").unwrap_or(env::var_os("PATH").unwrap_or_default());
let search_path = env::var_os(EnvVars::UV_TEST_PYTHON_PATH)
.unwrap_or(env::var_os(EnvVars::PATH).unwrap_or_default());
let version_request = version.unwrap_or(&VersionRequest::Default);
let possible_names: Vec<_> = version_request

View file

@ -17,6 +17,7 @@ use uv_distribution_filename::{ExtensionError, SourceDistExtension};
use uv_extract::hash::Hasher;
use uv_fs::{rename_with_retry, Simplified};
use uv_pypi_types::{HashAlgorithm, HashDigest};
use uv_static::EnvVars;
use crate::implementation::{
Error as ImplementationError, ImplementationName, LenientImplementationName,
@ -580,11 +581,11 @@ impl ManagedPythonDownload {
fn download_url(&self) -> Result<Url, Error> {
match self.key.implementation {
LenientImplementationName::Known(ImplementationName::CPython) => {
if let Ok(mirror) = std::env::var("UV_PYTHON_INSTALL_MIRROR") {
if let Ok(mirror) = std::env::var(EnvVars::UV_PYTHON_INSTALL_MIRROR) {
let Some(suffix) = self.url.strip_prefix(
"https://github.com/indygreg/python-build-standalone/releases/download/",
) else {
return Err(Error::Mirror("UV_PYTHON_INSTALL_MIRROR", self.url));
return Err(Error::Mirror(EnvVars::UV_PYTHON_INSTALL_MIRROR, self.url));
};
return Ok(Url::parse(
format!("{}/{}", mirror.trim_end_matches('/'), suffix).as_str(),
@ -593,10 +594,10 @@ impl ManagedPythonDownload {
}
LenientImplementationName::Known(ImplementationName::PyPy) => {
if let Ok(mirror) = std::env::var("UV_PYPY_INSTALL_MIRROR") {
if let Ok(mirror) = std::env::var(EnvVars::UV_PYPY_INSTALL_MIRROR) {
let Some(suffix) = self.url.strip_prefix("https://downloads.python.org/pypy/")
else {
return Err(Error::Mirror("UV_PYPY_INSTALL_MIRROR", self.url));
return Err(Error::Mirror(EnvVars::UV_PYPY_INSTALL_MIRROR, self.url));
};
return Ok(Url::parse(
format!("{}/{}", mirror.trim_end_matches('/'), suffix).as_str(),

View file

@ -1,6 +1,9 @@
//! Find requested Python interpreters and query interpreters for information.
use thiserror::Error;
#[cfg(test)]
use uv_static::EnvVars;
pub use crate::discovery::{
find_python_installations, EnvironmentPreference, Error as DiscoveryError, PythonDownloads,
PythonNotFound, PythonPreference, PythonRequest, PythonSource, PythonVariant, VersionRequest,
@ -45,7 +48,7 @@ pub(crate) fn current_dir() -> Result<std::path::PathBuf, std::io::Error> {
#[cfg(test)]
pub(crate) fn current_dir() -> Result<std::path::PathBuf, std::io::Error> {
std::env::var_os("PWD")
std::env::var_os(EnvVars::PWD)
.map(std::path::PathBuf::from)
.map(Ok)
.unwrap_or(std::env::current_dir())

View file

@ -22,6 +22,7 @@ use crate::platform::{Arch, Libc, Os};
use crate::python_version::PythonVersion;
use crate::{PythonRequest, PythonVariant};
use uv_fs::{LockedFile, Simplified};
use uv_static::EnvVars;
#[derive(Error, Debug)]
pub enum Error {
@ -80,7 +81,7 @@ impl ManagedPythonInstallations {
/// 2. A directory in the system-appropriate user-level data directory, e.g., `~/.local/uv/python`
/// 3. A directory in the local data directory, e.g., `./.uv/python`
pub fn from_settings() -> Result<Self, Error> {
if let Some(install_dir) = std::env::var_os("UV_PYTHON_INSTALL_DIR") {
if let Some(install_dir) = std::env::var_os(EnvVars::UV_PYTHON_INSTALL_DIR) {
Ok(Self::from_path(install_dir))
} else {
Ok(Self::from_path(

View file

@ -10,6 +10,7 @@ use std::env;
use std::path::PathBuf;
use std::str::FromStr;
use tracing::debug;
use uv_static::EnvVars;
#[derive(Debug)]
struct MicrosoftStorePython {
@ -91,7 +92,7 @@ const MICROSOFT_STORE_PYTHONS: &[MicrosoftStorePython] = &[
///
/// Effectively a port of <https://github.com/python/cpython/blob/58ce131037ecb34d506a613f21993cde2056f628/PC/launcher2.c#L1744>
pub(crate) fn find_microsoft_store_pythons() -> impl Iterator<Item = WindowsPython> {
let Ok(local_app_data) = env::var("LOCALAPPDATA") else {
let Ok(local_app_data) = env::var(EnvVars::LOCALAPPDATA) else {
debug!("`LOCALAPPDATA` not set, ignoring Microsoft store Pythons");
return Either::Left(std::iter::empty());
};

View file

@ -10,6 +10,7 @@ use assert_fs::{fixture::ChildPath, prelude::*, TempDir};
use indoc::{formatdoc, indoc};
use temp_env::with_vars;
use test_log::test;
use uv_static::EnvVars;
use uv_cache::Cache;
@ -85,13 +86,13 @@ impl TestContext {
let mut run_vars = vec![
// Ensure `PATH` is used
("UV_TEST_PYTHON_PATH", None),
(EnvVars::UV_TEST_PYTHON_PATH, None),
// Ignore active virtual environments (i.e. that the dev is using)
("VIRTUAL_ENV", None),
("PATH", path.as_deref()),
(EnvVars::VIRTUAL_ENV, None),
(EnvVars::PATH, path.as_deref()),
// Use the temporary python directory
(
"UV_PYTHON_INSTALL_DIR",
EnvVars::UV_PYTHON_INSTALL_DIR,
Some(self.installations.root().as_os_str()),
),
// Set a working directory
@ -817,14 +818,15 @@ fn find_best_python_skips_source_without_match() -> Result<()> {
TestContext::mock_venv(&venv, "3.12.0")?;
context.add_python_versions(&["3.10.1"])?;
let python = context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || {
find_best_python_installation(
&PythonRequest::parse("3.10"),
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
let python =
context.run_with_vars(&[(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str()))], || {
find_best_python_installation(
&PythonRequest::parse("3.10"),
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
assert!(
matches!(
python,
@ -846,14 +848,15 @@ fn find_best_python_returns_to_earlier_source_on_fallback() -> Result<()> {
TestContext::mock_venv(&venv, "3.10.1")?;
context.add_python_versions(&["3.10.3"])?;
let python = context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || {
find_best_python_installation(
&PythonRequest::parse("3.10.2"),
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
let python =
context.run_with_vars(&[(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str()))], || {
find_best_python_installation(
&PythonRequest::parse("3.10.2"),
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
assert!(
matches!(
python,
@ -879,14 +882,15 @@ fn find_python_from_active_python() -> Result<()> {
let venv = context.tempdir.child("some-venv");
TestContext::mock_venv(&venv, "3.12.0")?;
let python = context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || {
find_python_installation(
&PythonRequest::Default,
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
let python =
context.run_with_vars(&[(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str()))], || {
find_python_installation(
&PythonRequest::Default,
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
assert_eq!(
python.interpreter().python_full_version().to_string(),
"3.12.0",
@ -903,14 +907,15 @@ fn find_python_from_active_python_prerelease() -> Result<()> {
let venv = context.tempdir.child("some-venv");
TestContext::mock_venv(&venv, "3.13.0rc1")?;
let python = context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || {
find_python_installation(
&PythonRequest::Default,
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
let python =
context.run_with_vars(&[(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str()))], || {
find_python_installation(
&PythonRequest::Default,
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
assert_eq!(
python.interpreter().python_full_version().to_string(),
"3.13.0rc1",
@ -926,8 +931,9 @@ fn find_python_from_conda_prefix() -> Result<()> {
let condaenv = context.tempdir.child("condaenv");
TestContext::mock_conda_prefix(&condaenv, "3.12.0")?;
let python =
context.run_with_vars(&[("CONDA_PREFIX", Some(condaenv.as_os_str()))], || {
let python = context.run_with_vars(
&[(EnvVars::CONDA_PREFIX, Some(condaenv.as_os_str()))],
|| {
// Note this python is not treated as a system interpreter
find_python_installation(
&PythonRequest::Default,
@ -935,7 +941,8 @@ fn find_python_from_conda_prefix() -> Result<()> {
PythonPreference::OnlySystem,
&context.cache,
)
})??;
},
)??;
assert_eq!(
python.interpreter().python_full_version().to_string(),
"3.12.0",
@ -955,8 +962,8 @@ fn find_python_from_conda_prefix_and_virtualenv() -> Result<()> {
let python = context.run_with_vars(
&[
("VIRTUAL_ENV", Some(venv.as_os_str())),
("CONDA_PREFIX", Some(condaenv.as_os_str())),
(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str())),
(EnvVars::CONDA_PREFIX, Some(condaenv.as_os_str())),
],
|| {
find_python_installation(
@ -976,15 +983,17 @@ fn find_python_from_conda_prefix_and_virtualenv() -> Result<()> {
// Put a virtual environment in the working directory
let venv = context.workdir.child(".venv");
TestContext::mock_venv(venv, "3.12.2")?;
let python =
context.run_with_vars(&[("CONDA_PREFIX", Some(condaenv.as_os_str()))], || {
let python = context.run_with_vars(
&[(EnvVars::CONDA_PREFIX, Some(condaenv.as_os_str()))],
|| {
find_python_installation(
&PythonRequest::Default,
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
},
)??;
assert_eq!(
python.interpreter().python_full_version().to_string(),
"3.12.1",
@ -1046,14 +1055,15 @@ fn find_python_skips_broken_active_python() -> Result<()> {
// Delete the pyvenv cfg to break the virtualenv
fs_err::remove_file(venv.join("pyvenv.cfg"))?;
let python = context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || {
find_python_installation(
&PythonRequest::Default,
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
let python =
context.run_with_vars(&[(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str()))], || {
find_python_installation(
&PythonRequest::Default,
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
assert_eq!(
python.interpreter().python_full_version().to_string(),
"3.12.0",
@ -1079,7 +1089,10 @@ fn find_python_from_parent_interpreter() -> Result<()> {
)?;
let python = context.run_with_vars(
&[("UV_INTERNAL__PARENT_INTERPRETER", Some(parent.as_os_str()))],
&[(
EnvVars::UV_INTERNAL__PARENT_INTERPRETER,
Some(parent.as_os_str()),
)],
|| {
find_python_installation(
&PythonRequest::Default,
@ -1101,8 +1114,11 @@ fn find_python_from_parent_interpreter() -> Result<()> {
context.add_python_versions(&["3.12.3"])?;
let python = context.run_with_vars(
&[
("UV_INTERNAL__PARENT_INTERPRETER", Some(parent.as_os_str())),
("VIRTUAL_ENV", Some(venv.as_os_str())),
(
EnvVars::UV_INTERNAL__PARENT_INTERPRETER,
Some(parent.as_os_str()),
),
(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str())),
],
|| {
find_python_installation(
@ -1122,8 +1138,11 @@ fn find_python_from_parent_interpreter() -> Result<()> {
// Test with `EnvironmentPreference::ExplicitSystem`
let python = context.run_with_vars(
&[
("UV_INTERNAL__PARENT_INTERPRETER", Some(parent.as_os_str())),
("VIRTUAL_ENV", Some(venv.as_os_str())),
(
EnvVars::UV_INTERNAL__PARENT_INTERPRETER,
Some(parent.as_os_str()),
),
(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str())),
],
|| {
find_python_installation(
@ -1143,8 +1162,11 @@ fn find_python_from_parent_interpreter() -> Result<()> {
// Test with `EnvironmentPreference::OnlySystem`
let python = context.run_with_vars(
&[
("UV_INTERNAL__PARENT_INTERPRETER", Some(parent.as_os_str())),
("VIRTUAL_ENV", Some(venv.as_os_str())),
(
EnvVars::UV_INTERNAL__PARENT_INTERPRETER,
Some(parent.as_os_str()),
),
(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str())),
],
|| {
find_python_installation(
@ -1164,8 +1186,11 @@ fn find_python_from_parent_interpreter() -> Result<()> {
// Test with `EnvironmentPreference::OnlyVirtual`
let python = context.run_with_vars(
&[
("UV_INTERNAL__PARENT_INTERPRETER", Some(parent.as_os_str())),
("VIRTUAL_ENV", Some(venv.as_os_str())),
(
EnvVars::UV_INTERNAL__PARENT_INTERPRETER,
Some(parent.as_os_str()),
),
(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str())),
],
|| {
find_python_installation(
@ -1200,7 +1225,10 @@ fn find_python_from_parent_interpreter_prerelease() -> Result<()> {
)?;
let python = context.run_with_vars(
&[("UV_INTERNAL__PARENT_INTERPRETER", Some(parent.as_os_str()))],
&[(
EnvVars::UV_INTERNAL__PARENT_INTERPRETER,
Some(parent.as_os_str()),
)],
|| {
find_python_installation(
&PythonRequest::Default,
@ -1227,14 +1255,15 @@ fn find_python_active_python_skipped_if_system_required() -> Result<()> {
context.add_python_versions(&["3.10.0", "3.11.1", "3.12.2"])?;
// Without a specific request
let python = context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || {
find_python_installation(
&PythonRequest::Default,
EnvironmentPreference::OnlySystem,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
let python =
context.run_with_vars(&[(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str()))], || {
find_python_installation(
&PythonRequest::Default,
EnvironmentPreference::OnlySystem,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
assert_eq!(
python.interpreter().python_full_version().to_string(),
"3.10.0",
@ -1242,14 +1271,15 @@ fn find_python_active_python_skipped_if_system_required() -> Result<()> {
);
// With a requested minor version
let python = context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || {
find_python_installation(
&PythonRequest::parse("3.12"),
EnvironmentPreference::OnlySystem,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
let python =
context.run_with_vars(&[(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str()))], || {
find_python_installation(
&PythonRequest::parse("3.12"),
EnvironmentPreference::OnlySystem,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
assert_eq!(
python.interpreter().python_full_version().to_string(),
"3.12.2",
@ -1257,14 +1287,15 @@ fn find_python_active_python_skipped_if_system_required() -> Result<()> {
);
// With a patch version that cannot be python
let result = context.run_with_vars(&[("VIRTUAL_ENV", Some(venv.as_os_str()))], || {
find_python_installation(
&PythonRequest::parse("3.12.3"),
EnvironmentPreference::OnlySystem,
PythonPreference::OnlySystem,
&context.cache,
)
})?;
let result =
context.run_with_vars(&[(EnvVars::VIRTUAL_ENV, Some(venv.as_os_str()))], || {
find_python_installation(
&PythonRequest::parse("3.12.3"),
EnvironmentPreference::OnlySystem,
PythonPreference::OnlySystem,
&context.cache,
)
})?;
assert!(
result.is_err(),
"We should not find an python; got {result:?}"
@ -1293,7 +1324,7 @@ fn find_python_fails_if_no_virtualenv_and_system_not_allowed() -> Result<()> {
// With an invalid virtual environment variable
let result = context.run_with_vars(
&[("VIRTUAL_ENV", Some(context.tempdir.as_os_str()))],
&[(EnvVars::VIRTUAL_ENV, Some(context.tempdir.as_os_str()))],
|| {
find_python_installation(
&PythonRequest::parse("3.12.3"),
@ -1536,15 +1567,17 @@ fn find_python_allows_venv_directory_path() -> Result<()> {
let other_venv = context.tempdir.child("foobar").child(".venv");
TestContext::mock_venv(&other_venv, "3.11.1")?;
context.add_python_versions(&["3.12.2"])?;
let python =
context.run_with_vars(&[("VIRTUAL_ENV", Some(other_venv.as_os_str()))], || {
let python = context.run_with_vars(
&[(EnvVars::VIRTUAL_ENV, Some(other_venv.as_os_str()))],
|| {
find_python_installation(
&PythonRequest::parse(venv.to_str().unwrap()),
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
)
})??;
},
)??;
assert_eq!(
python.interpreter().python_full_version().to_string(),
"3.10.0",

View file

@ -6,6 +6,7 @@ use std::{
use fs_err as fs;
use thiserror::Error;
use uv_pypi_types::Scheme;
use uv_static::EnvVars;
/// The layout of a virtual environment.
#[derive(Debug)]
@ -49,7 +50,7 @@ pub enum Error {
///
/// Supports `VIRTUAL_ENV`.
pub(crate) fn virtualenv_from_env() -> Option<PathBuf> {
if let Some(dir) = env::var_os("VIRTUAL_ENV").filter(|value| !value.is_empty()) {
if let Some(dir) = env::var_os(EnvVars::VIRTUAL_ENV).filter(|value| !value.is_empty()) {
return Some(PathBuf::from(dir));
}
@ -60,7 +61,7 @@ pub(crate) fn virtualenv_from_env() -> Option<PathBuf> {
///
/// Supports `CONDA_PREFIX`.
pub(crate) fn conda_prefix_from_env() -> Option<PathBuf> {
if let Some(dir) = env::var_os("CONDA_PREFIX").filter(|value| !value.is_empty()) {
if let Some(dir) = env::var_os(EnvVars::CONDA_PREFIX).filter(|value| !value.is_empty()) {
return Some(PathBuf::from(dir));
}