Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions crates/uv-preview/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub enum PreviewFeature {
MetadataJson = 1 << 20,
GcsEndpoint = 1 << 21,
AdjustUlimit = 1 << 22,
SpecialCondaEnvNames = 1 << 23,
}

impl PreviewFeature {
Expand Down Expand Up @@ -64,6 +65,7 @@ impl PreviewFeature {
Self::MetadataJson => "metadata-json",
Self::GcsEndpoint => "gcs-endpoint",
Self::AdjustUlimit => "adjust-ulimit",
Self::SpecialCondaEnvNames => "special-conda-env-names",
}
}
}
Expand Down Expand Up @@ -106,6 +108,7 @@ impl FromStr for PreviewFeature {
"target-workspace-discovery" => Self::TargetWorkspaceDiscovery,
"metadata-json" => Self::MetadataJson,
"adjust-ulimit" => Self::AdjustUlimit,
"special-conda-env-names" => Self::SpecialCondaEnvNames,
_ => return Err(PreviewFeatureParseError),
})
}
Expand Down Expand Up @@ -329,5 +332,9 @@ mod tests {
assert_eq!(PreviewFeature::MetadataJson.as_str(), "metadata-json");
assert_eq!(PreviewFeature::GcsEndpoint.as_str(), "gcs-endpoint");
assert_eq!(PreviewFeature::AdjustUlimit.as_str(), "adjust-ulimit");
assert_eq!(
PreviewFeature::SpecialCondaEnvNames.as_str(),
"special-conda-env-names"
);
}
}
15 changes: 8 additions & 7 deletions crates/uv-python/src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,9 @@ pub enum Error {
/// - Discovered virtual environment (e.g. `.venv` in a parent directory)
///
/// Notably, "system" environments are excluded. See [`python_executables_from_installed`].
fn python_executables_from_virtual_environments<'a>()
-> impl Iterator<Item = Result<(PythonSource, PathBuf), Error>> + 'a {
fn python_executables_from_virtual_environments<'a>(
preview: Preview,
) -> impl Iterator<Item = Result<(PythonSource, PathBuf), Error>> + 'a {
let from_active_environment = iter::once_with(|| {
virtualenv_from_env()
.into_iter()
Expand All @@ -296,8 +297,8 @@ fn python_executables_from_virtual_environments<'a>()
.flatten();

// N.B. we prefer the conda environment over discovered virtual environments
let from_conda_environment = iter::once_with(|| {
conda_environment_from_env(CondaEnvironmentKind::Child)
let from_conda_environment = iter::once_with(move || {
conda_environment_from_env(CondaEnvironmentKind::Child, preview)
.into_iter()
.map(virtualenv_python_executable)
.map(|path| Ok((PythonSource::CondaPrefix, path)))
Expand Down Expand Up @@ -524,15 +525,15 @@ fn python_executables<'a>(
.flatten();

// Check if the base conda environment is active
let from_base_conda_environment = iter::once_with(|| {
conda_environment_from_env(CondaEnvironmentKind::Base)
let from_base_conda_environment = iter::once_with(move || {
conda_environment_from_env(CondaEnvironmentKind::Base, preview)
.into_iter()
.map(virtualenv_python_executable)
.map(|path| Ok((PythonSource::BaseCondaPrefix, path)))
})
.flatten();

let from_virtual_environments = python_executables_from_virtual_environments();
let from_virtual_environments = python_executables_from_virtual_environments(preview);
let from_installed =
python_executables_from_installed(version, implementation, platform, preference, preview);

Expand Down
32 changes: 31 additions & 1 deletion crates/uv-python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ mod tests {
use temp_env::with_vars;
use test_log::test;
use uv_client::BaseClientBuilder;
use uv_preview::Preview;
use uv_preview::{Preview, PreviewFeature};
use uv_static::EnvVars;

use uv_cache::Cache;
Expand Down Expand Up @@ -1335,6 +1335,36 @@ mod tests {
"We should not allow the base environment when looking for virtual environments"
);

// With the `special-conda-env-names` preview feature, "base" is not special-cased
// and uses path-based heuristics instead. When the directory name matches the env name,
// it should be treated as a child environment.
let base_dir = context.tempdir.child("base");
TestContext::mock_conda_prefix(&base_dir, "3.12.6")?;
let python = context
.run_with_vars(
&[
(EnvVars::CONDA_PREFIX, Some(base_dir.as_os_str())),
(EnvVars::CONDA_DEFAULT_ENV, Some(&OsString::from("base"))),
(EnvVars::CONDA_ROOT, None),
],
|| {
find_python_installation(
&PythonRequest::Default,
EnvironmentPreference::OnlyVirtual,
PythonPreference::OnlySystem,
&context.cache,
Preview::new(&[PreviewFeature::SpecialCondaEnvNames]),
)
},
)?
.unwrap();

assert_eq!(
python.interpreter().python_full_version().to_string(),
"3.12.6",
"With special-conda-env-names preview, 'base' named env in matching dir should be treated as child"
);

// When environment name matches directory name, it should be treated as a child environment
let myenv_dir = context.tempdir.child("myenv");
TestContext::mock_conda_prefix(&myenv_dir, "3.12.5")?;
Expand Down
18 changes: 12 additions & 6 deletions crates/uv-python/src/virtualenv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::{
use fs_err as fs;
use thiserror::Error;

use uv_preview::{Preview, PreviewFeature};
use uv_pypi_types::Scheme;
use uv_static::EnvVars;

Expand Down Expand Up @@ -85,7 +86,7 @@ impl CondaEnvironmentKind {
/// name, e.g., `base`, which does not match the `CONDA_PREFIX`, e.g., `/usr/local` instead of
/// `/usr/local/conda/envs/<name>`. Note the name `CONDA_DEFAULT_ENV` is misleading, it's the
/// active environment name, not a constant base environment name.
fn from_prefix_path(path: &Path) -> Self {
fn from_prefix_path(path: &Path, preview: Preview) -> Self {
// Pixi never creates true "base" envs and names project envs "default", confusing our
// heuristics, so treat Pixi prefixes as child envs outright.
if is_pixi_environment(path) {
Expand Down Expand Up @@ -113,8 +114,10 @@ impl CondaEnvironmentKind {
// If the environment name is "base" or "root", treat it as a base environment
//
// These are the expected names for the base environment; and is retained for backwards
// compatibility, but in a future breaking release we should remove this special-casing.
if current_env == "base" || current_env == "root" {
// compatibility, but can be removed with the `special-conda-env-names` preview feature.
if !preview.is_enabled(PreviewFeature::SpecialCondaEnvNames)
&& (current_env == "base" || current_env == "root")
{
return Self::Base;
}

Expand Down Expand Up @@ -142,11 +145,14 @@ fn is_pixi_environment(path: &Path) -> bool {
///
/// If `base` is true, the active environment must be the base environment or `None` is returned,
/// and vice-versa.
pub(crate) fn conda_environment_from_env(kind: CondaEnvironmentKind) -> Option<PathBuf> {
pub(crate) fn conda_environment_from_env(
kind: CondaEnvironmentKind,
preview: Preview,
) -> Option<PathBuf> {
let dir = env::var_os(EnvVars::CONDA_PREFIX).filter(|value| !value.is_empty())?;
let path = PathBuf::from(dir);

if kind != CondaEnvironmentKind::from_prefix_path(&path) {
if kind != CondaEnvironmentKind::from_prefix_path(&path, preview) {
return None;
}

Expand Down Expand Up @@ -353,7 +359,7 @@ mod tests {

with_vars(vars, || {
assert_eq!(
CondaEnvironmentKind::from_prefix_path(prefix),
CondaEnvironmentKind::from_prefix_path(prefix, Preview::default()),
CondaEnvironmentKind::Child
);
});
Expand Down
2 changes: 2 additions & 0 deletions crates/uv/tests/it/show_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7930,6 +7930,7 @@ fn preview_features() {
MetadataJson,
GcsEndpoint,
AdjustUlimit,
SpecialCondaEnvNames,
],
},
python_preference: Managed,
Expand Down Expand Up @@ -8188,6 +8189,7 @@ fn preview_features() {
MetadataJson,
GcsEndpoint,
AdjustUlimit,
SpecialCondaEnvNames,
],
},
python_preference: Managed,
Expand Down
Loading