Merge branch 'main' into zb/extra-build-dependencies

This commit is contained in:
Zanie Blue 2025-07-25 14:13:42 -05:00
commit bd55fda3a3
87 changed files with 2610 additions and 570 deletions

View File

@ -28,8 +28,6 @@ jobs:
pattern: wheels_uv-*
path: wheels_uv
merge-multiple: true
- name: Remove wheels unsupported by PyPI
run: rm wheels_uv/*riscv*
- name: Publish to PyPI
run: uv publish -v wheels_uv/*
@ -49,7 +47,5 @@ jobs:
pattern: wheels_uv_build-*
path: wheels_uv_build
merge-multiple: true
- name: Remove wheels unsupported by PyPI
run: rm wheels_uv_build/*riscv*
- name: Publish to PyPI
run: uv publish -v wheels_uv_build/*

View File

@ -3,6 +3,32 @@
<!-- prettier-ignore-start -->
## 0.8.3
### Python
- Add CPython 3.14.0rc1
See the [`python-build-standalone` release notes](https://github.com/astral-sh/python-build-standalone/releases/tag/20250723) for more details.
### Enhancements
- Allow non-standard entrypoint names in `uv_build` ([#14867](https://github.com/astral-sh/uv/pull/14867))
- Publish riscv64 wheels to PyPI ([#14852](https://github.com/astral-sh/uv/pull/14852))
### Bug fixes
- Avoid writing redacted credentials to tool receipt ([#14855](https://github.com/astral-sh/uv/pull/14855))
- Respect `--with` versions over base environment versions ([#14863](https://github.com/astral-sh/uv/pull/14863))
- Respect credentials from all defined indexes ([#14858](https://github.com/astral-sh/uv/pull/14858))
- Fix missed stabilization of removal of registry entry during Python uninstall ([#14859](https://github.com/astral-sh/uv/pull/14859))
- Improve concurrency safety of Python downloads into cache ([#14846](https://github.com/astral-sh/uv/pull/14846))
### Documentation
- Fix typos in `uv_build` reference documentation ([#14853](https://github.com/astral-sh/uv/pull/14853))
- Move the "Cargo" install method further down in docs ([#14842](https://github.com/astral-sh/uv/pull/14842))
## 0.8.2
### Enhancements

46
Cargo.lock generated
View File

@ -759,7 +759,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.59.0",
"windows-sys 0.48.0",
]
[[package]]
@ -1035,23 +1035,23 @@ dependencies = [
[[package]]
name = "dirs"
version = "5.0.1"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.4.1"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.48.0",
"windows-sys 0.60.2",
]
[[package]]
@ -1591,11 +1591,11 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "home"
version = "0.5.9"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -2994,13 +2994,13 @@ dependencies = [
[[package]]
name = "redox_users"
version = "0.4.6"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
dependencies = [
"getrandom 0.2.15",
"libredox",
"thiserror 1.0.69",
"thiserror 2.0.12",
]
[[package]]
@ -3654,9 +3654,9 @@ checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f"
[[package]]
name = "shellexpand"
version = "3.1.0"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b"
checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb"
dependencies = [
"bstr",
"dirs",
@ -4645,7 +4645,7 @@ dependencies = [
[[package]]
name = "uv"
version = "0.8.2"
version = "0.8.3"
dependencies = [
"anstream",
"anyhow",
@ -4656,7 +4656,7 @@ dependencies = [
"base64 0.22.1",
"byteorder",
"clap",
"console 0.15.11",
"console 0.16.0",
"ctrlc",
"dotenvy",
"dunce",
@ -4811,7 +4811,7 @@ dependencies = [
[[package]]
name = "uv-build"
version = "0.8.2"
version = "0.8.3"
dependencies = [
"anyhow",
"uv-build-backend",
@ -5037,6 +5037,7 @@ name = "uv-configuration"
version = "0.0.1"
dependencies = [
"anyhow",
"bitflags 2.9.1",
"clap",
"either",
"fs-err 3.1.1",
@ -5061,13 +5062,14 @@ dependencies = [
"uv-pep508",
"uv-platform-tags",
"uv-static",
"uv-warnings",
]
[[package]]
name = "uv-console"
version = "0.0.1"
dependencies = [
"console 0.15.11",
"console 0.16.0",
]
[[package]]
@ -5705,7 +5707,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"configparser",
"console 0.15.11",
"console 0.16.0",
"fs-err 3.1.1",
"futures",
"rustc-hash",
@ -6006,13 +6008,13 @@ dependencies = [
[[package]]
name = "uv-version"
version = "0.8.2"
version = "0.8.3"
[[package]]
name = "uv-virtualenv"
version = "0.0.4"
dependencies = [
"console 0.15.11",
"console 0.16.0",
"fs-err 3.1.1",
"itertools 0.14.0",
"owo-colors",
@ -6340,7 +6342,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.48.0",
]
[[package]]

View File

@ -93,7 +93,7 @@ cargo-util = { version = "0.2.14" }
clap = { version = "4.5.17", features = ["derive", "env", "string", "wrap_help"] }
clap_complete_command = { version = "0.6.1" }
configparser = { version = "3.1.0" }
console = { version = "0.15.11", default-features = false }
console = { version = "0.16.0", default-features = false, features = ["std"] }
csv = { version = "1.3.0" }
ctrlc = { version = "3.4.5" }
dashmap = { version = "6.1.0" }

View File

@ -87,7 +87,7 @@ mod resolver {
use uv_client::RegistryClient;
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, Constraints, IndexStrategy,
PackageConfigSettings, PreviewMode, SourceStrategy,
PackageConfigSettings, Preview, SourceStrategy,
};
use uv_dispatch::{BuildDispatch, SharedState};
use uv_distribution::DistributionDatabase;
@ -198,7 +198,7 @@ mod resolver {
sources,
workspace_cache,
concurrency,
PreviewMode::Enabled,
Preview::default(),
);
let markers = if universal {

View File

@ -7,7 +7,7 @@ use std::str::FromStr;
use itertools::Itertools;
use serde::Deserialize;
use tracing::{debug, trace};
use tracing::{debug, trace, warn};
use version_ranges::Ranges;
use walkdir::WalkDir;
@ -54,10 +54,6 @@ pub enum ValidationError {
"Entrypoint groups must consist of letters and numbers separated by dots, invalid group: `{0}`"
)]
InvalidGroup(String),
#[error(
"Entrypoint names must consist of letters, numbers, dots, underscores and dashes; invalid name: `{0}`"
)]
InvalidName(String),
#[error("Use `project.scripts` instead of `project.entry-points.console_scripts`")]
ReservedScripts,
#[error("Use `project.gui-scripts` instead of `project.entry-points.gui_scripts`")]
@ -620,12 +616,14 @@ impl PyProjectToml {
let _ = writeln!(writer, "[{group}]");
for (name, object_reference) in entries {
// More strict than the spec, we enforce the recommendation
if !name
.chars()
.all(|c| c.is_alphanumeric() || c == '.' || c == '-' || c == '_')
{
return Err(ValidationError::InvalidName(name.to_string()));
warn!(
"Entrypoint names should consist of letters, numbers, dots, underscores and \
dashes; non-compliant name: `{name}`"
);
}
// TODO(konsti): Validate that the object references are valid Python identifiers.
@ -1403,16 +1401,6 @@ mod tests {
assert_snapshot!(script_error(&contents), @"Entrypoint groups must consist of letters and numbers separated by dots, invalid group: `a@b`");
}
#[test]
fn invalid_entry_point_name() {
let contents = extend_project(indoc! {r#"
[project.scripts]
"a@b" = "bar"
"#
});
assert_snapshot!(script_error(&contents), @"Entrypoint names must consist of letters, numbers, dots, underscores and dashes; invalid name: `a@b`");
}
#[test]
fn invalid_entry_point_conflict_scripts() {
let contents = extend_project(indoc! {r#"

View File

@ -155,7 +155,7 @@ pub struct BuildBackendSettings {
/// with this package as build requirement use the include directory to find additional header
/// files.
/// - `purelib` and `platlib`: Installed to the `site-packages` directory. It is not recommended
/// to uses these two options.
/// to use these two options.
// TODO(konsti): We should show a flat example instead.
// ```toml
// [tool.uv.build-backend.data]
@ -165,7 +165,7 @@ pub struct BuildBackendSettings {
#[option(
default = r#"{}"#,
value_type = "dict[str, str]",
example = r#"data = { "headers": "include/headers", "scripts": "bin" }"#
example = r#"data = { headers = "include/headers", scripts = "bin" }"#
)]
pub data: WheelDataIncludes,
}

View File

@ -29,7 +29,7 @@ use tokio::sync::{Mutex, Semaphore};
use tracing::{Instrument, debug, info_span, instrument, warn};
use uv_cache_key::cache_digest;
use uv_configuration::PreviewMode;
use uv_configuration::Preview;
use uv_configuration::{BuildKind, BuildOutput, ConfigSettings, SourceStrategy};
use uv_distribution::BuildRequires;
use uv_distribution_types::{IndexLocations, Requirement, Resolution};
@ -289,7 +289,7 @@ impl SourceBuild {
mut environment_variables: FxHashMap<OsString, OsString>,
level: BuildOutput,
concurrent_builds: usize,
preview: PreviewMode,
preview: Preview,
) -> Result<Self, Error> {
let temp_dir = build_context.cache().venv_dir()?;

View File

@ -1,6 +1,6 @@
[package]
name = "uv-build"
version = "0.8.2"
version = "0.8.3"
edition.workspace = true
rust-version.workspace = true
homepage.workspace = true

View File

@ -1,6 +1,6 @@
[project]
name = "uv-build"
version = "0.8.2"
version = "0.8.3"
description = "The uv build backend"
authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }]
requires-python = ">=3.8"

View File

@ -11,8 +11,8 @@ use clap::{Args, Parser, Subcommand};
use uv_cache::CacheArgs;
use uv_configuration::{
ConfigSettingEntry, ConfigSettingPackageEntry, ExportFormat, IndexStrategy,
KeyringProviderType, PackageNameSpecifier, ProjectBuildBackend, TargetTriple, TrustedHost,
TrustedPublishing, VersionControlSystem,
KeyringProviderType, PackageNameSpecifier, PreviewFeatures, ProjectBuildBackend, TargetTriple,
TrustedHost, TrustedPublishing, VersionControlSystem,
};
use uv_distribution_types::{Index, IndexUrl, Origin, PipExtraIndex, PipFindLinks, PipIndex};
use uv_normalize::{ExtraName, GroupName, PackageName, PipGroupName};
@ -273,7 +273,7 @@ pub struct GlobalArgs {
)]
pub allow_insecure_host: Option<Vec<Maybe<TrustedHost>>>,
/// Whether to enable experimental, preview features.
/// Whether to enable all experimental preview features.
///
/// Preview features may change without warning.
#[arg(global = true, long, hide = true, env = EnvVars::UV_PREVIEW, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_preview"))]
@ -282,6 +282,25 @@ pub struct GlobalArgs {
#[arg(global = true, long, overrides_with("preview"), hide = true)]
pub no_preview: bool,
/// Enable experimental preview features.
///
/// Preview features may change without warning.
///
/// Use comma-separated values or pass multiple times to enable multiple features.
///
/// The following features are available: `python-install-default`, `python-upgrade`,
/// `json-output`, `pylock`, `add-bounds`.
#[arg(
global = true,
long = "preview-features",
env = EnvVars::UV_PREVIEW_FEATURES,
value_delimiter = ',',
hide = true,
alias = "preview-feature",
value_enum,
)]
pub preview_features: Vec<PreviewFeatures>,
/// Avoid discovering a `pyproject.toml` or `uv.toml` file.
///
/// Normally, configuration files are discovered in the current directory,

View File

@ -27,7 +27,9 @@ uv-pep440 = { workspace = true }
uv-pep508 = { workspace = true, features = ["schemars"] }
uv-platform-tags = { workspace = true }
uv-static = { workspace = true }
uv-warnings = { workspace = true }
bitflags = { workspace = true }
clap = { workspace = true, features = ["derive"], optional = true }
either = { workspace = true }
fs-err = { workspace = true }

View File

@ -1,37 +1,250 @@
use std::fmt::{Display, Formatter};
use std::{
fmt::{Display, Formatter},
str::FromStr,
};
use thiserror::Error;
use uv_warnings::warn_user_once;
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct PreviewFeatures: u32 {
const PYTHON_INSTALL_DEFAULT = 1 << 0;
const PYTHON_UPGRADE = 1 << 1;
const JSON_OUTPUT = 1 << 2;
const PYLOCK = 1 << 3;
const ADD_BOUNDS = 1 << 4;
const EXTRA_BUILD_DEPENDENCIES = 1 << 5;
}
}
impl PreviewFeatures {
/// Returns the string representation of a single preview feature flag.
///
/// Panics if given a combination of flags.
fn flag_as_str(self) -> &'static str {
match self {
Self::PYTHON_INSTALL_DEFAULT => "python-install-default",
Self::PYTHON_UPGRADE => "python-upgrade",
Self::JSON_OUTPUT => "json-output",
Self::PYLOCK => "pylock",
Self::ADD_BOUNDS => "add-bounds",
Self::EXTRA_BUILD_DEPENDENCIES => "extra-build-dependencies",
_ => panic!("`flag_as_str` can only be used for exactly one feature flag"),
}
}
}
impl Display for PreviewFeatures {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.is_empty() {
write!(f, "none")
} else {
let features: Vec<&str> = self.iter().map(PreviewFeatures::flag_as_str).collect();
write!(f, "{}", features.join(","))
}
}
}
#[derive(Debug, Error, Clone)]
pub enum PreviewFeaturesParseError {
#[error("Empty string in preview features: {0}")]
Empty(String),
}
impl FromStr for PreviewFeatures {
type Err = PreviewFeaturesParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut flags = PreviewFeatures::empty();
for part in s.split(',') {
let part = part.trim();
if part.is_empty() {
return Err(PreviewFeaturesParseError::Empty(
"Empty string in preview features".to_string(),
));
}
let flag = match part {
"python-install-default" => Self::PYTHON_INSTALL_DEFAULT,
"python-upgrade" => Self::PYTHON_UPGRADE,
"json-output" => Self::JSON_OUTPUT,
"pylock" => Self::PYLOCK,
"add-bounds" => Self::ADD_BOUNDS,
"extra-build-dependencies" => Self::EXTRA_BUILD_DEPENDENCIES,
_ => {
warn_user_once!("Unknown preview feature: `{part}`");
continue;
}
};
flags |= flag;
}
Ok(flags)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PreviewMode {
#[default]
Disabled,
Enabled,
pub struct Preview {
flags: PreviewFeatures,
}
impl PreviewMode {
pub fn is_enabled(&self) -> bool {
matches!(self, Self::Enabled)
impl Preview {
pub fn new(flags: PreviewFeatures) -> Self {
Self { flags }
}
pub fn is_disabled(&self) -> bool {
matches!(self, Self::Disabled)
pub fn all() -> Self {
Self::new(PreviewFeatures::all())
}
}
impl From<bool> for PreviewMode {
fn from(version: bool) -> Self {
if version {
PreviewMode::Enabled
} else {
PreviewMode::Disabled
pub fn from_args(
preview: bool,
no_preview: bool,
preview_features: &[PreviewFeatures],
) -> Self {
if no_preview {
return Self::default();
}
if preview {
return Self::all();
}
let mut flags = PreviewFeatures::empty();
for features in preview_features {
flags |= *features;
}
Self { flags }
}
pub fn is_enabled(&self, flag: PreviewFeatures) -> bool {
self.flags.contains(flag)
}
}
impl Display for PreviewMode {
impl Display for Preview {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Disabled => write!(f, "disabled"),
Self::Enabled => write!(f, "enabled"),
if self.flags.is_empty() {
write!(f, "disabled")
} else if self.flags == PreviewFeatures::all() {
write!(f, "enabled")
} else {
write!(f, "{}", self.flags)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_preview_features_from_str() {
// Test single feature
let features = PreviewFeatures::from_str("python-install-default").unwrap();
assert_eq!(features, PreviewFeatures::PYTHON_INSTALL_DEFAULT);
// Test multiple features
let features = PreviewFeatures::from_str("python-upgrade,json-output").unwrap();
assert!(features.contains(PreviewFeatures::PYTHON_UPGRADE));
assert!(features.contains(PreviewFeatures::JSON_OUTPUT));
assert!(!features.contains(PreviewFeatures::PYLOCK));
// Test with whitespace
let features = PreviewFeatures::from_str("pylock , add-bounds").unwrap();
assert!(features.contains(PreviewFeatures::PYLOCK));
assert!(features.contains(PreviewFeatures::ADD_BOUNDS));
// Test empty string error
assert!(PreviewFeatures::from_str("").is_err());
assert!(PreviewFeatures::from_str("pylock,").is_err());
assert!(PreviewFeatures::from_str(",pylock").is_err());
// Test unknown feature (should be ignored with warning)
let features = PreviewFeatures::from_str("unknown-feature,pylock").unwrap();
assert!(features.contains(PreviewFeatures::PYLOCK));
assert_eq!(features.bits().count_ones(), 1);
}
#[test]
fn test_preview_features_display() {
// Test empty
let features = PreviewFeatures::empty();
assert_eq!(features.to_string(), "none");
// Test single feature
let features = PreviewFeatures::PYTHON_INSTALL_DEFAULT;
assert_eq!(features.to_string(), "python-install-default");
// Test multiple features
let features = PreviewFeatures::PYTHON_UPGRADE | PreviewFeatures::JSON_OUTPUT;
assert_eq!(features.to_string(), "python-upgrade,json-output");
}
#[test]
fn test_preview_display() {
// Test disabled
let preview = Preview::default();
assert_eq!(preview.to_string(), "disabled");
// Test enabled (all features)
let preview = Preview::all();
assert_eq!(preview.to_string(), "enabled");
// Test specific features
let preview = Preview::new(PreviewFeatures::PYTHON_UPGRADE | PreviewFeatures::PYLOCK);
assert_eq!(preview.to_string(), "python-upgrade,pylock");
}
#[test]
fn test_preview_from_args() {
// Test no_preview
let preview = Preview::from_args(true, true, &[]);
assert_eq!(preview.to_string(), "disabled");
// Test preview (all features)
let preview = Preview::from_args(true, false, &[]);
assert_eq!(preview.to_string(), "enabled");
// Test specific features
let features = vec![
PreviewFeatures::PYTHON_UPGRADE,
PreviewFeatures::JSON_OUTPUT,
];
let preview = Preview::from_args(false, false, &features);
assert!(preview.is_enabled(PreviewFeatures::PYTHON_UPGRADE));
assert!(preview.is_enabled(PreviewFeatures::JSON_OUTPUT));
assert!(!preview.is_enabled(PreviewFeatures::PYLOCK));
}
#[test]
fn test_as_str_single_flags() {
assert_eq!(
PreviewFeatures::PYTHON_INSTALL_DEFAULT.flag_as_str(),
"python-install-default"
);
assert_eq!(
PreviewFeatures::PYTHON_UPGRADE.flag_as_str(),
"python-upgrade"
);
assert_eq!(PreviewFeatures::JSON_OUTPUT.flag_as_str(), "json-output");
assert_eq!(PreviewFeatures::PYLOCK.flag_as_str(), "pylock");
assert_eq!(PreviewFeatures::ADD_BOUNDS.flag_as_str(), "add-bounds");
assert_eq!(
PreviewFeatures::EXTRA_BUILD_DEPENDENCIES.flag_as_str(),
"extra-build-dependencies"
);
}
#[test]
#[should_panic(expected = "`flag_as_str` can only be used for exactly one feature flag")]
fn test_as_str_multiple_flags_panics() {
let features = PreviewFeatures::PYTHON_UPGRADE | PreviewFeatures::JSON_OUTPUT;
let _ = features.flag_as_str();
}
}

View File

@ -4,7 +4,7 @@ use clap::Parser;
use tracing::info;
use uv_cache::{Cache, CacheArgs};
use uv_configuration::{Concurrency, PreviewMode};
use uv_configuration::{Concurrency, Preview};
use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest};
#[derive(Parser)]
@ -26,7 +26,7 @@ pub(crate) async fn compile(args: CompileArgs) -> anyhow::Result<()> {
&PythonRequest::default(),
EnvironmentPreference::OnlyVirtual,
&cache,
PreviewMode::Disabled,
Preview::default(),
)?
.into_interpreter();
interpreter.sys_executable().to_path_buf()

View File

@ -18,7 +18,7 @@ use uv_cache::Cache;
use uv_client::RegistryClient;
use uv_configuration::{
BuildKind, BuildOptions, ConfigSettings, Constraints, IndexStrategy, PackageConfigSettings,
PreviewMode, Reinstall, SourceStrategy,
Preview, Reinstall, SourceStrategy,
};
use uv_configuration::{BuildOutput, Concurrency};
use uv_distribution::DistributionDatabase;
@ -101,7 +101,7 @@ pub struct BuildDispatch<'a> {
sources: SourceStrategy,
workspace_cache: WorkspaceCache,
concurrency: Concurrency,
preview: PreviewMode,
preview: Preview,
}
impl<'a> BuildDispatch<'a> {
@ -126,7 +126,7 @@ impl<'a> BuildDispatch<'a> {
sources: SourceStrategy,
workspace_cache: WorkspaceCache,
concurrency: Concurrency,
preview: PreviewMode,
preview: Preview,
) -> Self {
Self {
client,

View File

@ -203,7 +203,11 @@ impl serde::ser::Serialize for IndexUrl {
where
S: serde::ser::Serializer,
{
self.to_string().serialize(serializer)
match self {
Self::Pypi(url) => url.without_credentials().serialize(serializer),
Self::Url(url) => url.without_credentials().serialize(serializer),
Self::Path(url) => url.without_credentials().serialize(serializer),
}
}
}
@ -396,14 +400,17 @@ impl<'a> IndexLocations {
///
/// This includes explicit indexes, implicit indexes, flat indexes, and the default index.
///
/// The indexes will be returned in the order in which they were defined, such that the
/// last-defined index is the last item in the vector.
/// The indexes will be returned in the reverse of the order in which they were defined, such
/// that the last-defined index is the first item in the vector.
pub fn allowed_indexes(&'a self) -> Vec<&'a Index> {
if self.no_index {
self.flat_index.iter().rev().collect()
} else {
let mut indexes = vec![];
// TODO(charlie): By only yielding the first default URL, we'll drop credentials if,
// e.g., an authenticated default URL is provided in a configuration file, but an
// unauthenticated default URL is present in the receipt.
let mut seen = FxHashSet::default();
let mut default = false;
for index in {
@ -429,9 +436,29 @@ impl<'a> IndexLocations {
}
}
/// Return a vector containing all known [`Index`] entries.
///
/// This includes explicit indexes, implicit indexes, flat indexes, and default indexes;
/// in short, it includes all defined indexes, even if they're overridden by some other index
/// definition.
///
/// The indexes will be returned in the reverse of the order in which they were defined, such
/// that the last-defined index is the first item in the vector.
pub fn known_indexes(&'a self) -> impl Iterator<Item = &'a Index> {
if self.no_index {
Either::Left(self.flat_index.iter().rev())
} else {
Either::Right(
std::iter::once(&*DEFAULT_INDEX)
.chain(self.flat_index.iter().rev())
.chain(self.indexes.iter().rev()),
)
}
}
/// Add all authenticated sources to the cache.
pub fn cache_index_credentials(&self) {
for index in self.allowed_indexes() {
for index in self.known_indexes() {
if let Some(credentials) = index.credentials() {
let credentials = Arc::new(credentials);
uv_auth::store_credentials(index.raw_url(), credentials.clone());

View File

@ -91,13 +91,21 @@ impl CompatibleDist<'_> {
}
}
// For installable distributions, return the prioritized distribution it was derived from.
pub fn prioritized(&self) -> Option<&PrioritizedDist> {
match self {
CompatibleDist::InstalledDist(_) => None,
CompatibleDist::SourceDist { prioritized, .. }
| CompatibleDist::CompatibleWheel { prioritized, .. }
| CompatibleDist::IncompatibleWheel { prioritized, .. } => Some(prioritized),
}
}
/// Return the set of supported platform the distribution, in terms of their markers.
pub fn implied_markers(&self) -> MarkerTree {
match self {
CompatibleDist::InstalledDist(_) => MarkerTree::TRUE,
CompatibleDist::SourceDist { prioritized, .. } => prioritized.0.markers,
CompatibleDist::CompatibleWheel { prioritized, .. } => prioritized.0.markers,
CompatibleDist::IncompatibleWheel { prioritized, .. } => prioritized.0.markers,
match self.prioritized() {
Some(prioritized) => prioritized.0.markers,
None => MarkerTree::TRUE,
}
}
}

View File

@ -8,7 +8,7 @@ use std::{env, io, iter};
use std::{path::Path, path::PathBuf, str::FromStr};
use thiserror::Error;
use tracing::{debug, instrument, trace};
use uv_configuration::PreviewMode;
use uv_configuration::Preview;
use which::{which, which_all};
use uv_cache::Cache;
@ -335,7 +335,7 @@ fn python_executables_from_installed<'a>(
implementation: Option<&'a ImplementationName>,
platform: PlatformRequest,
preference: PythonPreference,
preview: PreviewMode,
preview: Preview,
) -> Box<dyn Iterator<Item = Result<(PythonSource, PathBuf), Error>> + 'a> {
let from_managed_installations = iter::once_with(move || {
ManagedPythonInstallations::from_settings(None)
@ -485,7 +485,7 @@ fn python_executables<'a>(
platform: PlatformRequest,
environments: EnvironmentPreference,
preference: PythonPreference,
preview: PreviewMode,
preview: Preview,
) -> 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 = iter::once_with(|| {
@ -705,7 +705,7 @@ fn python_interpreters<'a>(
environments: EnvironmentPreference,
preference: PythonPreference,
cache: &'a Cache,
preview: PreviewMode,
preview: Preview,
) -> impl Iterator<Item = Result<(PythonSource, Interpreter), Error>> + 'a {
python_interpreters_from_executables(
// Perform filtering on the discovered executables based on their source. This avoids
@ -1053,7 +1053,7 @@ pub fn find_python_installations<'a>(
environments: EnvironmentPreference,
preference: PythonPreference,
cache: &'a Cache,
preview: PreviewMode,
preview: Preview,
) -> Box<dyn Iterator<Item = Result<FindPythonResult, Error>> + 'a> {
let sources = DiscoveryPreferences {
python_preference: preference,
@ -1254,7 +1254,7 @@ pub(crate) fn find_python_installation(
environments: EnvironmentPreference,
preference: PythonPreference,
cache: &Cache,
preview: PreviewMode,
preview: Preview,
) -> Result<FindPythonResult, Error> {
let installations =
find_python_installations(request, environments, preference, cache, preview);
@ -1353,7 +1353,7 @@ pub(crate) fn find_best_python_installation(
environments: EnvironmentPreference,
preference: PythonPreference,
cache: &Cache,
preview: PreviewMode,
preview: Preview,
) -> Result<FindPythonResult, Error> {
debug!("Starting Python discovery for {}", request);

View File

@ -7,7 +7,7 @@ use owo_colors::OwoColorize;
use tracing::debug;
use uv_cache::Cache;
use uv_configuration::PreviewMode;
use uv_configuration::Preview;
use uv_fs::{LockedFile, Simplified};
use uv_pep440::Version;
@ -153,7 +153,7 @@ impl PythonEnvironment {
request: &PythonRequest,
preference: EnvironmentPreference,
cache: &Cache,
preview: PreviewMode,
preview: Preview,
) -> Result<Self, Error> {
let installation = match find_python_installation(
request,

View File

@ -8,7 +8,7 @@ use tracing::{debug, info};
use uv_cache::Cache;
use uv_client::BaseClientBuilder;
use uv_configuration::PreviewMode;
use uv_configuration::Preview;
use uv_pep440::{Prerelease, Version};
use crate::discovery::{
@ -58,7 +58,7 @@ impl PythonInstallation {
environments: EnvironmentPreference,
preference: PythonPreference,
cache: &Cache,
preview: PreviewMode,
preview: Preview,
) -> Result<Self, Error> {
let installation =
find_python_installation(request, environments, preference, cache, preview)??;
@ -72,7 +72,7 @@ impl PythonInstallation {
environments: EnvironmentPreference,
preference: PythonPreference,
cache: &Cache,
preview: PreviewMode,
preview: Preview,
) -> Result<Self, Error> {
Ok(find_best_python_installation(
request,
@ -97,7 +97,7 @@ impl PythonInstallation {
python_install_mirror: Option<&str>,
pypy_install_mirror: Option<&str>,
python_downloads_json_url: Option<&str>,
preview: PreviewMode,
preview: Preview,
) -> Result<Self, Error> {
let request = request.unwrap_or(&PythonRequest::Default);
@ -220,7 +220,7 @@ impl PythonInstallation {
reporter: Option<&dyn Reporter>,
python_install_mirror: Option<&str>,
pypy_install_mirror: Option<&str>,
preview: PreviewMode,
preview: Preview,
) -> Result<Self, Error> {
let installations = ManagedPythonInstallations::from_settings(None)?.init()?;
let installations_dir = installations.root();

View File

@ -135,7 +135,7 @@ mod tests {
use indoc::{formatdoc, indoc};
use temp_env::with_vars;
use test_log::test;
use uv_configuration::PreviewMode;
use uv_configuration::Preview;
use uv_static::EnvVars;
use uv_cache::Cache;
@ -468,7 +468,7 @@ mod tests {
EnvironmentPreference::OnlySystem,
PythonPreference::default(),
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
});
assert!(
@ -483,7 +483,7 @@ mod tests {
EnvironmentPreference::OnlySystem,
PythonPreference::default(),
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
});
assert!(
@ -508,7 +508,7 @@ mod tests {
EnvironmentPreference::OnlySystem,
PythonPreference::default(),
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
});
assert!(
@ -530,7 +530,7 @@ mod tests {
EnvironmentPreference::OnlySystem,
PythonPreference::default(),
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert!(
@ -592,7 +592,7 @@ mod tests {
EnvironmentPreference::OnlySystem,
PythonPreference::default(),
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert!(
@ -624,7 +624,7 @@ mod tests {
EnvironmentPreference::OnlySystem,
PythonPreference::default(),
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
});
assert!(
@ -661,7 +661,7 @@ mod tests {
EnvironmentPreference::OnlySystem,
PythonPreference::default(),
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert!(
@ -693,7 +693,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -715,7 +715,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -741,7 +741,7 @@ mod tests {
EnvironmentPreference::OnlySystem,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -767,7 +767,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -790,7 +790,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
@ -824,7 +824,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
@ -858,7 +858,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})?;
assert!(
@ -880,7 +880,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})?;
assert!(
@ -902,7 +902,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
@ -936,7 +936,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
@ -973,7 +973,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert!(
@ -1004,7 +1004,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert!(
@ -1039,7 +1039,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -1065,7 +1065,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -1092,7 +1092,7 @@ mod tests {
EnvironmentPreference::OnlyVirtual,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
},
)??;
@ -1117,7 +1117,7 @@ mod tests {
EnvironmentPreference::OnlyVirtual,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
},
)?;
@ -1139,7 +1139,7 @@ mod tests {
EnvironmentPreference::OnlySystem,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
},
)??;
@ -1162,7 +1162,7 @@ mod tests {
EnvironmentPreference::OnlyVirtual,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
},
)??;
@ -1195,7 +1195,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
},
)??;
@ -1216,7 +1216,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
},
)??;
@ -1243,7 +1243,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
@ -1261,7 +1261,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
@ -1290,7 +1290,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -1328,7 +1328,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
},
)??;
@ -1356,7 +1356,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
},
)??;
@ -1381,7 +1381,7 @@ mod tests {
EnvironmentPreference::ExplicitSystem,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
},
)??;
@ -1406,7 +1406,7 @@ mod tests {
EnvironmentPreference::OnlySystem,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
},
)??;
@ -1431,7 +1431,7 @@ mod tests {
EnvironmentPreference::OnlyVirtual,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
},
)??;
@ -1469,7 +1469,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
},
)??;
@ -1497,7 +1497,7 @@ mod tests {
EnvironmentPreference::OnlySystem,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -1514,7 +1514,7 @@ mod tests {
EnvironmentPreference::OnlySystem,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -1531,7 +1531,7 @@ mod tests {
EnvironmentPreference::OnlySystem,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})?;
assert!(
@ -1553,7 +1553,7 @@ mod tests {
EnvironmentPreference::OnlyVirtual,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})?;
assert!(
@ -1570,7 +1570,7 @@ mod tests {
EnvironmentPreference::OnlySystem,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
},
)?;
@ -1592,7 +1592,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -1607,7 +1607,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})?;
assert!(
@ -1621,7 +1621,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})?;
assert!(
@ -1650,7 +1650,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -1666,7 +1666,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -1696,7 +1696,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -1712,7 +1712,7 @@ mod tests {
EnvironmentPreference::ExplicitSystem,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -1728,7 +1728,7 @@ mod tests {
EnvironmentPreference::OnlyVirtual,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -1744,7 +1744,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -1768,7 +1768,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -1783,7 +1783,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -1807,7 +1807,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -1827,7 +1827,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
},
)??;
@ -1856,7 +1856,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -1878,7 +1878,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})?;
assert!(
@ -1908,7 +1908,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -1924,7 +1924,7 @@ mod tests {
EnvironmentPreference::ExplicitSystem,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})?;
assert!(
@ -1951,7 +1951,7 @@ mod tests {
EnvironmentPreference::ExplicitSystem,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})
.unwrap()
@ -1976,7 +1976,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})?;
assert!(
@ -1993,7 +1993,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -2008,7 +2008,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -2034,7 +2034,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -2049,7 +2049,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -2075,7 +2075,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -2102,7 +2102,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -2129,7 +2129,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -2156,7 +2156,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -2183,7 +2183,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -2211,7 +2211,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})?;
assert!(
@ -2233,7 +2233,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -2248,7 +2248,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -2274,7 +2274,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -2289,7 +2289,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
assert_eq!(
@ -2327,7 +2327,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})
.unwrap()
@ -2345,7 +2345,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})
.unwrap()
@ -2387,7 +2387,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})
.unwrap()
@ -2405,7 +2405,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})
.unwrap()
@ -2442,7 +2442,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})
.unwrap()
@ -2465,7 +2465,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})
.unwrap()
@ -2488,7 +2488,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})
.unwrap()
@ -2527,7 +2527,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;
@ -2580,7 +2580,7 @@ mod tests {
EnvironmentPreference::Any,
PythonPreference::OnlySystem,
&context.cache,
PreviewMode::Disabled,
Preview::default(),
)
})??;

View File

@ -12,7 +12,7 @@ use itertools::Itertools;
use same_file::is_same_file;
use thiserror::Error;
use tracing::{debug, warn};
use uv_configuration::PreviewMode;
use uv_configuration::{Preview, PreviewFeatures};
#[cfg(windows)]
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT;
@ -519,7 +519,7 @@ impl ManagedPythonInstallation {
/// Ensure the environment contains the symlink directory (or junction on Windows)
/// pointing to the patch directory for this minor version.
pub fn ensure_minor_version_link(&self, preview: PreviewMode) -> Result<(), Error> {
pub fn ensure_minor_version_link(&self, preview: Preview) -> Result<(), Error> {
if let Some(minor_version_link) = PythonMinorVersionLink::from_installation(self, preview) {
minor_version_link.create_directory()?;
}
@ -531,7 +531,7 @@ impl ManagedPythonInstallation {
///
/// Unlike [`ensure_minor_version_link`], will not create a new symlink directory
/// if one doesn't already exist,
pub fn update_minor_version_link(&self, preview: PreviewMode) -> Result<(), Error> {
pub fn update_minor_version_link(&self, preview: Preview) -> Result<(), Error> {
if let Some(minor_version_link) = PythonMinorVersionLink::from_installation(self, preview) {
if !minor_version_link.exists() {
return Ok(());
@ -702,7 +702,7 @@ impl PythonMinorVersionLink {
pub fn from_executable(
executable: &Path,
key: &PythonInstallationKey,
preview: PreviewMode,
preview: Preview,
) -> Option<Self> {
let implementation = key.implementation();
if !matches!(
@ -755,7 +755,7 @@ impl PythonMinorVersionLink {
// If preview mode is disabled, still return a `MinorVersionSymlink` for
// existing symlinks, allowing continued operations without the `--preview`
// flag after initial symlink directory installation.
if preview.is_disabled() && !minor_version_link.exists() {
if !preview.is_enabled(PreviewFeatures::PYTHON_UPGRADE) && !minor_version_link.exists() {
return None;
}
Some(minor_version_link)
@ -763,7 +763,7 @@ impl PythonMinorVersionLink {
pub fn from_installation(
installation: &ManagedPythonInstallation,
preview: PreviewMode,
preview: Preview,
) -> Option<Self> {
PythonMinorVersionLink::from_executable(
installation.executable(false).as_path(),

View File

@ -1,5 +1,6 @@
use ref_cast::RefCast;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::fmt::{Debug, Display};
use std::ops::{Deref, DerefMut};
use std::str::FromStr;
@ -98,6 +99,24 @@ impl DisplaySafeUrl {
let _ = self.0.set_password(None);
}
/// Returns the URL with any credentials removed.
pub fn without_credentials(&self) -> Cow<'_, Url> {
if self.0.password().is_none() && self.0.username() == "" {
return Cow::Borrowed(&self.0);
}
// For URLs that use the `git` convention (i.e., `ssh://git@github.com/...`), avoid dropping the
// username.
if is_ssh_git_username(&self.0) {
return Cow::Borrowed(&self.0);
}
let mut url = self.0.clone();
let _ = url.set_username("");
let _ = url.set_password(None);
Cow::Owned(url)
}
/// Returns [`Display`] implementation that doesn't mask credentials.
#[inline]
pub fn displayable_with_credentials(&self) -> impl Display {

View File

@ -266,7 +266,6 @@ impl CandidateSelector {
return Some(Candidate {
name: package_name,
version,
prioritized: None,
dist: CandidateDist::Compatible(CompatibleDist::InstalledDist(
dist,
)),
@ -368,7 +367,6 @@ impl CandidateSelector {
return Some(Candidate {
name: package_name,
version,
prioritized: None,
dist: CandidateDist::Compatible(CompatibleDist::InstalledDist(dist)),
choice_kind: VersionChoiceKind::Installed,
});
@ -546,10 +544,14 @@ impl CandidateSelector {
// exclude-newer in our error messages.
if matches!(
candidate.dist(),
CandidateDist::Incompatible(
IncompatibleDist::Source(IncompatibleSource::ExcludeNewer(_))
| IncompatibleDist::Wheel(IncompatibleWheel::ExcludeNewer(_))
)
CandidateDist::Incompatible {
incompatible_dist: IncompatibleDist::Source(IncompatibleSource::ExcludeNewer(
_
)) | IncompatibleDist::Wheel(
IncompatibleWheel::ExcludeNewer(_)
),
..
}
) {
continue;
}
@ -572,7 +574,7 @@ impl CandidateSelector {
// even though there are compatible wheels on PyPI. Thus, we need to ensure that we
// return the first _compatible_ candidate across all indexes, if such a candidate
// exists.
if matches!(candidate.dist(), CandidateDist::Incompatible(_)) {
if matches!(candidate.dist(), CandidateDist::Incompatible { .. }) {
if incompatible.is_none() {
incompatible = Some(candidate);
}
@ -602,7 +604,25 @@ impl CandidateSelector {
#[derive(Debug, Clone)]
pub(crate) enum CandidateDist<'a> {
Compatible(CompatibleDist<'a>),
Incompatible(IncompatibleDist),
Incompatible {
/// The reason the prioritized distribution is incompatible.
incompatible_dist: IncompatibleDist,
/// The prioritized distribution that had no compatible wheelr or sdist.
prioritized_dist: &'a PrioritizedDist,
},
}
impl CandidateDist<'_> {
/// For an installable dist, return the prioritized distribution.
fn prioritized(&self) -> Option<&PrioritizedDist> {
match self {
CandidateDist::Compatible(dist) => dist.prioritized(),
CandidateDist::Incompatible {
incompatible_dist: _,
prioritized_dist: prioritized,
} => Some(prioritized),
}
}
}
impl<'a> From<&'a PrioritizedDist> for CandidateDist<'a> {
@ -621,7 +641,10 @@ impl<'a> From<&'a PrioritizedDist> for CandidateDist<'a> {
} else {
IncompatibleDist::Unavailable
};
CandidateDist::Incompatible(dist)
CandidateDist::Incompatible {
incompatible_dist: dist,
prioritized_dist: value,
}
}
}
}
@ -654,8 +677,6 @@ pub(crate) struct Candidate<'a> {
name: &'a PackageName,
/// The version of the package.
version: &'a Version,
/// The prioritized distribution for the package.
prioritized: Option<&'a PrioritizedDist>,
/// The distributions to use for resolving and installing the package.
dist: CandidateDist<'a>,
/// Whether this candidate was selected from a preference.
@ -672,7 +693,6 @@ impl<'a> Candidate<'a> {
Self {
name,
version,
prioritized: Some(dist),
dist: CandidateDist::from(dist),
choice_kind,
}
@ -709,7 +729,7 @@ impl<'a> Candidate<'a> {
/// Return the prioritized distribution for the candidate.
pub(crate) fn prioritized(&self) -> Option<&PrioritizedDist> {
self.prioritized
self.dist.prioritized()
}
}

View File

@ -1431,7 +1431,7 @@ impl Lock {
}
// Collect the set of available indexes (both `--index-url` and `--find-links` entries).
let remotes = indexes.map(|locations| {
let mut remotes = indexes.map(|locations| {
locations
.allowed_indexes()
.into_iter()
@ -1444,7 +1444,7 @@ impl Lock {
.collect::<BTreeSet<_>>()
});
let locals = indexes.map(|locations| {
let mut locals = indexes.map(|locations| {
locations
.allowed_indexes()
.into_iter()
@ -1717,6 +1717,38 @@ impl Lock {
return Ok(SatisfiesResult::MissingVersion(&package.id.name));
}
// Add any explicit indexes to the list of known locals or remotes. These indexes may
// not be available as top-level configuration (i.e., if they're defined within a
// workspace member), but we already validated that the dependencies are up-to-date, so
// we can consider them "available".
for requirement in &package.metadata.requires_dist {
if let RequirementSource::Registry {
index: Some(index), ..
} = &requirement.source
{
match &index.url {
IndexUrl::Pypi(_) | IndexUrl::Url(_) => {
if let Some(remotes) = remotes.as_mut() {
remotes.insert(UrlString::from(
index.url().without_credentials().as_ref(),
));
}
}
IndexUrl::Path(url) => {
if let Some(locals) = locals.as_mut() {
if let Some(path) = url.to_file_path().ok().and_then(|path| {
relative_to(&path, root)
.or_else(|_| std::path::absolute(path))
.ok()
}) {
locals.insert(path.into_boxed_path());
}
}
}
}
}
}
// Recurse.
for dep in &package.dependencies {
if seen.insert(&dep.package_id) {

View File

@ -1271,7 +1271,10 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
let dist = match candidate.dist() {
CandidateDist::Compatible(dist) => dist,
CandidateDist::Incompatible(incompatibility) => {
CandidateDist::Incompatible {
incompatible_dist: incompatibility,
prioritized_dist: _,
} => {
// If the version is incompatible because no distributions are compatible, exit early.
return Ok(Some(ResolverVersion::Unavailable(
candidate.version().clone(),
@ -3779,9 +3782,7 @@ impl Fork {
if self.env.included_by_group(conflicting_item) {
return true;
}
if let Some(conflicting_item) = dep.package.conflicting_item() {
self.conflicts.remove(&conflicting_item);
}
self.conflicts.remove(&conflicting_item);
false
});
Some(self)

View File

@ -225,6 +225,9 @@ impl EnvVars {
/// Equivalent to the `--preview` argument. Enables preview mode.
pub const UV_PREVIEW: &'static str = "UV_PREVIEW";
/// Equivalent to the `--preview-features` argument. Enables specific preview features.
pub const UV_PREVIEW_FEATURES: &'static str = "UV_PREVIEW_FEATURES";
/// Equivalent to the `--token` argument for self update. A GitHub token for authentication.
pub const UV_GITHUB_TOKEN: &'static str = "UV_GITHUB_TOKEN";

View File

@ -1,7 +1,7 @@
use core::fmt;
use fs_err as fs;
use uv_configuration::PreviewMode;
use uv_configuration::Preview;
use uv_dirs::user_executable_directory;
use uv_pep440::Version;
use uv_pep508::{InvalidNameError, PackageName};
@ -258,7 +258,7 @@ impl InstalledTools {
&self,
name: &PackageName,
interpreter: Interpreter,
preview: PreviewMode,
preview: Preview,
) -> Result<PythonEnvironment, Error> {
let environment_path = self.tool_dir(name);

View File

@ -1,6 +1,6 @@
[package]
name = "uv-version"
version = "0.8.2"
version = "0.8.3"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }

View File

@ -3,7 +3,7 @@ use std::path::Path;
use thiserror::Error;
use uv_configuration::PreviewMode;
use uv_configuration::Preview;
use uv_python::{Interpreter, PythonEnvironment};
pub use virtualenv::{OnExisting, remove_virtualenv};
@ -56,7 +56,7 @@ pub fn create_venv(
relocatable: bool,
seed: bool,
upgradeable: bool,
preview: PreviewMode,
preview: Preview,
) -> Result<PythonEnvironment, Error> {
// Create the virtualenv at the given location.
let virtualenv = virtualenv::create(

View File

@ -12,7 +12,7 @@ use itertools::Itertools;
use owo_colors::OwoColorize;
use tracing::{debug, trace};
use uv_configuration::PreviewMode;
use uv_configuration::Preview;
use uv_fs::{CWD, Simplified, cachedir};
use uv_pypi_types::Scheme;
use uv_python::managed::{PythonMinorVersionLink, create_link_to_executable};
@ -59,7 +59,7 @@ pub(crate) fn create(
relocatable: bool,
seed: bool,
upgradeable: bool,
preview: PreviewMode,
preview: Preview,
) -> Result<VirtualEnvironment, Error> {
// Determine the base Python executable; that is, the Python executable that should be
// considered the "base" for the virtual environment.

View File

@ -1,6 +1,6 @@
[package]
name = "uv"
version = "0.8.2"
version = "0.8.3"
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }

View File

@ -16,7 +16,7 @@ use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
BuildKind, BuildOptions, BuildOutput, Concurrency, ConfigSettings, Constraints,
DependencyGroupsWithDefaults, HashCheckingMode, IndexStrategy, KeyringProviderType,
PackageConfigSettings, PreviewMode, SourceStrategy,
PackageConfigSettings, Preview, SourceStrategy,
};
use uv_dispatch::{BuildDispatch, SharedState};
use uv_distribution_filename::{
@ -118,7 +118,7 @@ pub(crate) async fn build_frontend(
concurrency: Concurrency,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
let build_result = build_impl(
project_dir,
@ -186,7 +186,7 @@ async fn build_impl(
concurrency: Concurrency,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<BuildResult> {
// Extract the resolver settings.
let ResolverSettings {
@ -441,7 +441,7 @@ async fn build_package(
link_mode: LinkMode,
config_setting: &ConfigSettings,
config_settings_package: &PackageConfigSettings,
preview: PreviewMode,
preview: Preview,
) -> Result<Vec<BuildMessage>, Error> {
let output_dir = if let Some(output_dir) = output_dir {
Cow::Owned(std::path::absolute(output_dir)?)

View File

@ -5,7 +5,7 @@ use anyhow::Result;
use owo_colors::OwoColorize;
use uv_cache::Cache;
use uv_configuration::PreviewMode;
use uv_configuration::Preview;
use uv_distribution_types::{Diagnostic, InstalledDist};
use uv_installer::{SitePackages, SitePackagesDiagnostic};
use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest};
@ -20,7 +20,7 @@ pub(crate) fn pip_check(
system: bool,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
let start = Instant::now();

View File

@ -14,7 +14,7 @@ use uv_cache::Cache;
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, Constraints, ExportFormat, ExtrasSpecification,
IndexStrategy, NoBinary, NoBuild, PackageConfigSettings, PreviewMode, Reinstall,
IndexStrategy, NoBinary, NoBuild, PackageConfigSettings, Preview, PreviewFeatures, Reinstall,
SourceStrategy, Upgrade,
};
use uv_configuration::{KeyringProviderType, TargetTriple};
@ -112,11 +112,14 @@ pub(crate) async fn pip_compile(
quiet: bool,
cache: Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
if preview.is_disabled() && !extra_build_dependencies.is_empty() {
if !preview.is_enabled(PreviewFeatures::EXTRA_BUILD_DEPENDENCIES)
&& !extra_build_dependencies.is_empty()
{
warn_user_once!(
"The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview` to disable this warning."
"The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
PreviewFeatures::EXTRA_BUILD_DEPENDENCIES
);
}

View File

@ -6,7 +6,7 @@ use itertools::Itertools;
use owo_colors::OwoColorize;
use uv_cache::Cache;
use uv_configuration::PreviewMode;
use uv_configuration::Preview;
use uv_distribution_types::{Diagnostic, InstalledDist, Name};
use uv_installer::SitePackages;
use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest};
@ -24,7 +24,7 @@ pub(crate) fn pip_freeze(
paths: Option<Vec<PathBuf>>,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
// Detect the current Python interpreter.
let environment = PythonEnvironment::find(

View File

@ -10,8 +10,8 @@ use uv_cache::Cache;
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, Constraints, DryRun, ExtrasSpecification,
HashCheckingMode, IndexStrategy, PackageConfigSettings, PreviewMode, Reinstall, SourceStrategy,
Upgrade,
HashCheckingMode, IndexStrategy, PackageConfigSettings, Preview, PreviewFeatures, Reinstall,
SourceStrategy, Upgrade,
};
use uv_configuration::{KeyringProviderType, TargetTriple};
use uv_dispatch::{BuildDispatch, SharedState};
@ -97,13 +97,16 @@ pub(crate) async fn pip_install(
cache: Cache,
dry_run: DryRun,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> anyhow::Result<ExitStatus> {
let start = std::time::Instant::now();
if preview.is_disabled() && !extra_build_dependencies.is_empty() {
if !preview.is_enabled(PreviewFeatures::EXTRA_BUILD_DEPENDENCIES)
&& !extra_build_dependencies.is_empty()
{
warn_user_once!(
"The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview` to disable this warning."
"The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
PreviewFeatures::EXTRA_BUILD_DEPENDENCIES
);
}
@ -141,9 +144,10 @@ pub(crate) async fn pip_install(
.await?;
if pylock.is_some() {
if preview.is_disabled() {
if !preview.is_enabled(PreviewFeatures::PYLOCK) {
warn_user!(
"The `--pylock` setting is experimental and may change without warning. Pass `--preview` to disable this warning."
"The `--pylock` setting is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
PreviewFeatures::PYLOCK
);
}
}

View File

@ -15,7 +15,7 @@ use uv_cache::{Cache, Refresh};
use uv_cache_info::Timestamp;
use uv_cli::ListFormat;
use uv_client::{BaseClientBuilder, RegistryClientBuilder};
use uv_configuration::{Concurrency, IndexStrategy, KeyringProviderType, PreviewMode};
use uv_configuration::{Concurrency, IndexStrategy, KeyringProviderType, Preview};
use uv_distribution_filename::DistFilename;
use uv_distribution_types::{
Diagnostic, IndexCapabilities, IndexLocations, InstalledDist, Name, RequiresPython,
@ -54,7 +54,7 @@ pub(crate) async fn pip_list(
system: bool,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
// Disallow `--outdated` with `--format freeze`.
if outdated && matches!(format, ListFormat::Freeze) {

View File

@ -7,7 +7,7 @@ use owo_colors::OwoColorize;
use rustc_hash::FxHashMap;
use uv_cache::Cache;
use uv_configuration::PreviewMode;
use uv_configuration::Preview;
use uv_distribution_types::{Diagnostic, Name};
use uv_fs::Simplified;
use uv_install_wheel::read_record_file;
@ -28,7 +28,7 @@ pub(crate) fn pip_show(
files: bool,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
if packages.is_empty() {
#[allow(clippy::print_stderr)]

View File

@ -9,8 +9,8 @@ use uv_cache::Cache;
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, Constraints, DryRun, ExtrasSpecification,
HashCheckingMode, IndexStrategy, PackageConfigSettings, PreviewMode, Reinstall, SourceStrategy,
Upgrade,
HashCheckingMode, IndexStrategy, PackageConfigSettings, Preview, PreviewFeatures, Reinstall,
SourceStrategy, Upgrade,
};
use uv_configuration::{KeyringProviderType, TargetTriple};
use uv_dispatch::{BuildDispatch, SharedState};
@ -85,11 +85,14 @@ pub(crate) async fn pip_sync(
cache: Cache,
dry_run: DryRun,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
if preview.is_disabled() && !extra_build_dependencies.is_empty() {
if !preview.is_enabled(PreviewFeatures::EXTRA_BUILD_DEPENDENCIES)
&& !extra_build_dependencies.is_empty()
{
warn_user_once!(
"The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview` to disable this warning."
"The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
PreviewFeatures::EXTRA_BUILD_DEPENDENCIES
);
}
@ -134,9 +137,10 @@ pub(crate) async fn pip_sync(
.await?;
if pylock.is_some() {
if preview.is_disabled() {
if !preview.is_enabled(PreviewFeatures::PYLOCK) {
warn_user!(
"The `--pylock` setting is experimental and may change without warning. Pass `--preview` to disable this warning."
"The `--pylock` setting is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
PreviewFeatures::PYLOCK
);
}
}

View File

@ -13,7 +13,7 @@ use tokio::sync::Semaphore;
use uv_cache::{Cache, Refresh};
use uv_cache_info::Timestamp;
use uv_client::{BaseClientBuilder, RegistryClientBuilder};
use uv_configuration::{Concurrency, IndexStrategy, KeyringProviderType, PreviewMode};
use uv_configuration::{Concurrency, IndexStrategy, KeyringProviderType, Preview};
use uv_distribution_types::{Diagnostic, IndexCapabilities, IndexLocations, Name, RequiresPython};
use uv_installer::SitePackages;
use uv_normalize::PackageName;
@ -52,7 +52,7 @@ pub(crate) async fn pip_tree(
system: bool,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
// Detect the current Python interpreter.
let environment = PythonEnvironment::find(

View File

@ -7,7 +7,7 @@ use tracing::{debug, warn};
use uv_cache::Cache;
use uv_client::BaseClientBuilder;
use uv_configuration::{DryRun, KeyringProviderType, PreviewMode};
use uv_configuration::{DryRun, KeyringProviderType, Preview};
use uv_distribution_types::Requirement;
use uv_distribution_types::{InstalledMetadata, Name, UnresolvedRequirement};
use uv_fs::Simplified;
@ -37,7 +37,7 @@ pub(crate) async fn pip_uninstall(
network_settings: &NetworkSettings,
dry_run: DryRun,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
let start = std::time::Instant::now();

View File

@ -18,8 +18,8 @@ use uv_cache_key::RepositoryUrl;
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
Concurrency, Constraints, DependencyGroups, DependencyGroupsWithDefaults, DevMode, DryRun,
EditableMode, ExtrasSpecification, ExtrasSpecificationWithDefaults, InstallOptions,
PreviewMode, SourceStrategy,
EditableMode, ExtrasSpecification, ExtrasSpecificationWithDefaults, InstallOptions, Preview,
PreviewFeatures, SourceStrategy,
};
use uv_dispatch::BuildDispatch;
use uv_distribution::DistributionDatabase;
@ -95,15 +95,21 @@ pub(crate) async fn add(
no_config: bool,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
if bounds.is_some() && preview.is_disabled() {
warn_user_once!("The bounds option is in preview and may change in any future release.");
if bounds.is_some() && !preview.is_enabled(PreviewFeatures::ADD_BOUNDS) {
warn_user_once!(
"The `bounds` option is in preview and may change in any future release. Pass `--preview-features {}` to disable this warning.",
PreviewFeatures::ADD_BOUNDS
);
}
if preview.is_disabled() && !settings.resolver.extra_build_dependencies.is_empty() {
if !preview.is_enabled(PreviewFeatures::EXTRA_BUILD_DEPENDENCIES)
&& !settings.resolver.extra_build_dependencies.is_empty()
{
warn_user_once!(
"The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview` to disable this warning."
"The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
PreviewFeatures::EXTRA_BUILD_DEPENDENCIES
);
}
@ -963,7 +969,7 @@ async fn lock_and_sync(
concurrency: Concurrency,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<(), ProjectError> {
let mut lock = project::lock::LockOperation::new(
if locked {

View File

@ -12,7 +12,7 @@ use crate::settings::{NetworkSettings, ResolverInstallerSettings};
use uv_cache::{Cache, CacheBucket};
use uv_cache_key::{cache_digest, hash_digest};
use uv_configuration::{Concurrency, Constraints, PreviewMode};
use uv_configuration::{Concurrency, Constraints, Preview};
use uv_distribution_types::{Name, Resolution};
use uv_fs::PythonExt;
use uv_python::{Interpreter, PythonEnvironment, canonicalize_executable};
@ -119,7 +119,7 @@ impl CachedEnvironment {
concurrency: Concurrency,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<Self, ProjectError> {
let interpreter = Self::base_interpreter(interpreter, cache)?;

View File

@ -9,7 +9,7 @@ use owo_colors::OwoColorize;
use uv_cache::Cache;
use uv_configuration::{
Concurrency, DependencyGroups, EditableMode, ExportFormat, ExtrasSpecification, InstallOptions,
PreviewMode,
Preview,
};
use uv_normalize::{DefaultExtras, DefaultGroups, PackageName};
use uv_python::{PythonDownloads, PythonPreference, PythonRequest};
@ -80,7 +80,7 @@ pub(crate) async fn export(
quiet: bool,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
// Identify the target.
let workspace_cache = WorkspaceCache::default();

View File

@ -12,7 +12,7 @@ use uv_cache::Cache;
use uv_cli::AuthorFrom;
use uv_client::BaseClientBuilder;
use uv_configuration::{
DependencyGroupsWithDefaults, PreviewMode, ProjectBuildBackend, VersionControlError,
DependencyGroupsWithDefaults, Preview, ProjectBuildBackend, VersionControlError,
VersionControlSystem,
};
use uv_fs::{CWD, Simplified};
@ -62,7 +62,7 @@ pub(crate) async fn init(
no_config: bool,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
match init_kind {
InitKind::Script => {
@ -201,7 +201,7 @@ async fn init_script(
pin_python: bool,
package: bool,
no_config: bool,
preview: PreviewMode,
preview: Preview,
) -> Result<()> {
if no_workspace {
warn_user_once!("`--no-workspace` is a no-op for Python scripts, which are standalone");
@ -296,7 +296,7 @@ async fn init_project(
no_config: bool,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<()> {
// Discover the current workspace, if it exists.
let workspace_cache = WorkspaceCache::default();

View File

@ -12,8 +12,8 @@ use tracing::debug;
use uv_cache::Cache;
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
Concurrency, Constraints, DependencyGroupsWithDefaults, DryRun, ExtrasSpecification,
PreviewMode, Reinstall, Upgrade,
Concurrency, Constraints, DependencyGroupsWithDefaults, DryRun, ExtrasSpecification, Preview,
PreviewFeatures, Reinstall, Upgrade,
};
use uv_dispatch::BuildDispatch;
use uv_distribution::DistributionDatabase;
@ -93,7 +93,7 @@ pub(crate) async fn lock(
no_config: bool,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> anyhow::Result<ExitStatus> {
// If necessary, initialize the PEP 723 script.
let script = match script {
@ -271,7 +271,7 @@ pub(super) struct LockOperation<'env> {
cache: &'env Cache,
workspace_cache: &'env WorkspaceCache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
}
impl<'env> LockOperation<'env> {
@ -286,7 +286,7 @@ impl<'env> LockOperation<'env> {
cache: &'env Cache,
workspace_cache: &'env WorkspaceCache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Self {
Self {
mode,
@ -418,7 +418,7 @@ async fn do_lock(
cache: &Cache,
workspace_cache: &WorkspaceCache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<LockResult, ProjectError> {
let start = std::time::Instant::now();
@ -443,9 +443,12 @@ async fn do_lock(
sources,
} = settings;
if preview.is_disabled() && !extra_build_dependencies.is_empty() {
if !preview.is_enabled(PreviewFeatures::EXTRA_BUILD_DEPENDENCIES)
&& !extra_build_dependencies.is_empty()
{
warn_user_once!(
"The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview` to disable this warning."
"The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
PreviewFeatures::EXTRA_BUILD_DEPENDENCIES
);
}

View File

@ -12,8 +12,8 @@ use uv_cache::{Cache, CacheBucket};
use uv_cache_key::cache_digest;
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
Concurrency, Constraints, DependencyGroupsWithDefaults, DryRun, ExtrasSpecification,
PreviewMode, Reinstall, SourceStrategy, Upgrade,
Concurrency, Constraints, DependencyGroupsWithDefaults, DryRun, ExtrasSpecification, Preview,
PreviewFeatures, Reinstall, SourceStrategy, Upgrade,
};
use uv_dispatch::{BuildDispatch, SharedState};
use uv_distribution::{DistributionDatabase, LoweredRequirement};
@ -648,7 +648,7 @@ impl ScriptInterpreter {
active: Option<bool>,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<Self, ProjectError> {
// For now, we assume that scripts are never evaluated in the context of a workspace.
let workspace = None;
@ -888,7 +888,7 @@ impl ProjectInterpreter {
active: Option<bool>,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<Self, ProjectError> {
// Resolve the Python request and requirement for the workspace.
let WorkspacePython {
@ -1270,7 +1270,7 @@ impl ProjectEnvironment {
cache: &Cache,
dry_run: DryRun,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<Self, ProjectError> {
// Lock the project environment to avoid synchronization issues.
let _lock = ProjectInterpreter::lock(workspace)
@ -1280,7 +1280,7 @@ impl ProjectEnvironment {
})
.ok();
let upgradeable = preview.is_enabled()
let upgradeable = preview.is_enabled(PreviewFeatures::PYTHON_UPGRADE)
&& python
.as_ref()
.is_none_or(|request| !request.includes_patch());
@ -1502,7 +1502,7 @@ impl ScriptEnvironment {
cache: &Cache,
dry_run: DryRun,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<Self, ProjectError> {
// Lock the script environment to avoid synchronization issues.
let _lock = ScriptInterpreter::lock(script)
@ -1659,7 +1659,7 @@ pub(crate) async fn resolve_names(
cache: &Cache,
workspace_cache: &WorkspaceCache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<Vec<Requirement>, uv_requirements::Error> {
// Partition the requirements into named and unnamed requirements.
let (mut requirements, unnamed): (Vec<_>, Vec<_>) =
@ -1834,7 +1834,7 @@ pub(crate) async fn resolve_environment(
concurrency: Concurrency,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ResolverOutput, ProjectError> {
warn_on_requirements_txt_setting(&spec.requirements, settings);
@ -2026,7 +2026,7 @@ pub(crate) async fn sync_environment(
concurrency: Concurrency,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<PythonEnvironment, ProjectError> {
let InstallerSettingsRef {
index_locations,
@ -2198,7 +2198,7 @@ pub(crate) async fn update_environment(
workspace_cache: WorkspaceCache,
dry_run: DryRun,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<EnvironmentUpdate, ProjectError> {
warn_on_requirements_txt_setting(&spec, &settings.resolver);
@ -2441,7 +2441,7 @@ pub(crate) async fn init_script_python_requirement(
client_builder: &BaseClientBuilder<'_>,
cache: &Cache,
reporter: &PythonDownloadReporter,
preview: PreviewMode,
preview: Preview,
) -> anyhow::Result<RequiresPython> {
let python_request = if let Some(request) = python {
// (1) Explicit request from user

View File

@ -10,7 +10,7 @@ use tracing::{debug, warn};
use uv_cache::Cache;
use uv_configuration::{
Concurrency, DependencyGroups, DryRun, EditableMode, ExtrasSpecification, InstallOptions,
PreviewMode,
Preview,
};
use uv_fs::Simplified;
use uv_normalize::{DEV_DEPENDENCIES, DefaultExtras, DefaultGroups};
@ -60,7 +60,7 @@ pub(crate) async fn remove(
no_config: bool,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
let target = if let Some(script) = script {
// If we found a PEP 723 script and the user provided a project-only setting, warn.

View File

@ -19,7 +19,7 @@ use uv_cli::ExternalCommand;
use uv_client::BaseClientBuilder;
use uv_configuration::{
Concurrency, Constraints, DependencyGroups, DryRun, EditableMode, ExtrasSpecification,
InstallOptions, PreviewMode,
InstallOptions, Preview,
};
use uv_distribution_types::Requirement;
use uv_fs::which::is_executable;
@ -94,7 +94,7 @@ pub(crate) async fn run(
printer: Printer,
env_file: Vec<PathBuf>,
no_env_file: bool,
preview: PreviewMode,
preview: Preview,
max_recursion_depth: u32,
) -> anyhow::Result<ExitStatus> {
// Check if max recursion depth was exceeded. This most commonly happens
@ -1071,8 +1071,8 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
ephemeral_env.set_overlay(format!(
"import site; site.addsitedir(\"{}\"); site.addsitedir(\"{}\");",
base_site_packages.escape_for_python(),
requirements_site_packages.escape_for_python(),
base_site_packages.escape_for_python(),
))?;
// N.B. The order here matters — earlier interpreters take precedence over the

View File

@ -14,7 +14,7 @@ use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
Concurrency, Constraints, DependencyGroups, DependencyGroupsWithDefaults, DryRun, EditableMode,
ExtrasSpecification, ExtrasSpecificationWithDefaults, HashCheckingMode, InstallOptions,
PreviewMode, TargetTriple, Upgrade,
Preview, PreviewFeatures, TargetTriple, Upgrade,
};
use uv_dispatch::BuildDispatch;
use uv_distribution_types::{
@ -80,12 +80,14 @@ pub(crate) async fn sync(
no_config: bool,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
output_format: SyncFormat,
) -> Result<ExitStatus> {
if preview.is_enabled() && matches!(output_format, SyncFormat::Json) {
if preview.is_enabled(PreviewFeatures::JSON_OUTPUT) && matches!(output_format, SyncFormat::Json)
{
warn_user!(
"The `--output-format json` option is experimental and the schema may change without warning. Pass `--preview` to disable this warning."
"The `--output-format json` option is experimental and the schema may change without warning. Pass `--preview-features {}` to disable this warning.",
PreviewFeatures::JSON_OUTPUT
);
}
@ -579,7 +581,7 @@ pub(super) async fn do_sync(
workspace_cache: WorkspaceCache,
dry_run: DryRun,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<(), ProjectError> {
// Extract the project settings.
let InstallerSettingsRef {
@ -600,9 +602,12 @@ pub(super) async fn do_sync(
sources,
} = settings;
if preview.is_disabled() && !extra_build_dependencies.is_empty() {
if !preview.is_enabled(PreviewFeatures::EXTRA_BUILD_DEPENDENCIES)
&& !extra_build_dependencies.is_empty()
{
warn_user_once!(
"The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview` to disable this warning."
"The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
PreviewFeatures::EXTRA_BUILD_DEPENDENCIES
);
}

View File

@ -7,7 +7,7 @@ use tokio::sync::Semaphore;
use uv_cache::{Cache, Refresh};
use uv_cache_info::Timestamp;
use uv_client::RegistryClientBuilder;
use uv_configuration::{Concurrency, DependencyGroups, PreviewMode, TargetTriple};
use uv_configuration::{Concurrency, DependencyGroups, Preview, TargetTriple};
use uv_distribution_types::IndexCapabilities;
use uv_normalize::DefaultGroups;
use uv_pep508::PackageName;
@ -57,7 +57,7 @@ pub(crate) async fn tree(
no_config: bool,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
// Find the project requirements.
let workspace_cache = WorkspaceCache::default();

View File

@ -11,7 +11,7 @@ use uv_cli::version::VersionInfo;
use uv_cli::{VersionBump, VersionFormat};
use uv_configuration::{
Concurrency, DependencyGroups, DependencyGroupsWithDefaults, DryRun, EditableMode,
ExtrasSpecification, InstallOptions, PreviewMode,
ExtrasSpecification, InstallOptions, Preview,
};
use uv_fs::Simplified;
use uv_normalize::DefaultExtras;
@ -76,7 +76,7 @@ pub(crate) async fn project_version(
no_config: bool,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
// Read the metadata
let project = find_target(project_dir, package.as_ref(), explicit_project).await?;
@ -414,7 +414,7 @@ async fn print_frozen_version(
short: bool,
output_format: VersionFormat,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
// Discover the interpreter (this is the same interpreter --no-sync uses).
let interpreter = ProjectInterpreter::discover(
@ -509,7 +509,7 @@ async fn lock_and_sync(
no_config: bool,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
// If frozen, don't touch the lock or sync at all
if frozen {

View File

@ -3,7 +3,7 @@ use std::fmt::Write;
use std::path::Path;
use uv_cache::Cache;
use uv_configuration::{DependencyGroupsWithDefaults, PreviewMode};
use uv_configuration::{DependencyGroupsWithDefaults, Preview};
use uv_fs::Simplified;
use uv_python::{
EnvironmentPreference, PythonDownloads, PythonInstallation, PythonPreference, PythonRequest,
@ -32,7 +32,7 @@ pub(crate) async fn find(
python_preference: PythonPreference,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
let environment_preference = if system {
EnvironmentPreference::OnlySystem
@ -123,7 +123,7 @@ pub(crate) async fn find_script(
no_config: bool,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
let interpreter = match ScriptInterpreter::discover(
script,

View File

@ -14,7 +14,7 @@ use owo_colors::OwoColorize;
use rustc_hash::{FxHashMap, FxHashSet};
use tracing::{debug, trace};
use uv_configuration::PreviewMode;
use uv_configuration::{Preview, PreviewFeatures};
use uv_fs::Simplified;
use uv_python::downloads::{
self, ArchRequest, DownloadResult, ManagedPythonDownload, PythonDownloadRequest,
@ -161,7 +161,7 @@ pub(crate) async fn install(
default: bool,
python_downloads: PythonDownloads,
no_config: bool,
preview: PreviewMode,
preview: Preview,
printer: Printer,
) -> Result<ExitStatus> {
let start = std::time::Instant::now();
@ -170,15 +170,17 @@ pub(crate) async fn install(
// `--default` is used. It's not clear how this overlaps with a global Python pin, but I'd be
// surprised if `uv python find` returned the "newest" Python version rather than the one I just
// installed with the `--default` flag.
if default && !preview.is_enabled() {
if default && !preview.is_enabled(PreviewFeatures::PYTHON_INSTALL_DEFAULT) {
warn_user!(
"The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning"
"The `--default` option is experimental and may change without warning. Pass `--preview-features {}` to disable this warning",
PreviewFeatures::PYTHON_INSTALL_DEFAULT
);
}
if upgrade && preview.is_disabled() {
if upgrade && !preview.is_enabled(PreviewFeatures::PYTHON_UPGRADE) {
warn_user!(
"`uv python upgrade` is experimental and may change without warning. Pass `--preview` to disable this warning"
"`uv python upgrade` is experimental and may change without warning. Pass `--preview-features {}` to disable this warning",
PreviewFeatures::PYTHON_UPGRADE
);
}
@ -737,12 +739,13 @@ fn create_bin_links(
installations: &[&ManagedPythonInstallation],
changelog: &mut Changelog,
errors: &mut Vec<(InstallErrorKind, PythonInstallationKey, Error)>,
preview: PreviewMode,
preview: Preview,
) {
// TODO(zanieb): We want more feedback on the `is_default_install` behavior before stabilizing
// it. In particular, it may be confusing because it does not apply when versions are loaded
// from a `.python-version` file.
let targets = if (default || (is_default_install && preview.is_enabled()))
let targets = if (default
|| (is_default_install && preview.is_enabled(PreviewFeatures::PYTHON_INSTALL_DEFAULT)))
&& first_request.matches_installation(installation)
{
vec![

View File

@ -2,7 +2,7 @@ use serde::Serialize;
use std::collections::BTreeSet;
use std::fmt::Write;
use uv_cli::PythonListFormat;
use uv_configuration::PreviewMode;
use uv_configuration::Preview;
use uv_pep440::Version;
use anyhow::Result;
@ -65,7 +65,7 @@ pub(crate) async fn list(
python_downloads: PythonDownloads,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
let request = request.as_deref().map(PythonRequest::parse);
let base_download_request = if python_preference == PythonPreference::OnlySystem {

View File

@ -8,7 +8,7 @@ use tracing::debug;
use uv_cache::Cache;
use uv_client::BaseClientBuilder;
use uv_configuration::{DependencyGroupsWithDefaults, PreviewMode};
use uv_configuration::{DependencyGroupsWithDefaults, Preview};
use uv_fs::Simplified;
use uv_python::{
EnvironmentPreference, PYTHON_VERSION_FILENAME, PythonDownloads, PythonInstallation,
@ -39,7 +39,7 @@ pub(crate) async fn pin(
network_settings: NetworkSettings,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
let workspace_cache = WorkspaceCache::default();
let virtual_project = if no_project {
@ -270,7 +270,7 @@ fn warn_if_existing_pin_incompatible_with_project(
virtual_project: &VirtualProject,
python_preference: PythonPreference,
cache: &Cache,
preview: PreviewMode,
preview: Preview,
) {
// Check if the pinned version is compatible with the project.
if let Some(pin_version) = pep440_version_from_request(pin) {

View File

@ -11,7 +11,7 @@ use owo_colors::OwoColorize;
use rustc_hash::{FxHashMap, FxHashSet};
use tracing::{debug, warn};
use uv_configuration::PreviewMode;
use uv_configuration::Preview;
use uv_fs::Simplified;
use uv_python::downloads::PythonDownloadRequest;
use uv_python::managed::{
@ -30,7 +30,7 @@ pub(crate) async fn uninstall(
targets: Vec<String>,
all: bool,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
let installations = ManagedPythonInstallations::from_settings(install_dir)?.init()?;
@ -66,7 +66,7 @@ async fn do_uninstall(
targets: Vec<String>,
all: bool,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
let start = std::time::Instant::now();
@ -112,13 +112,11 @@ async fn do_uninstall(
}
if !found {
// Clear any remnants in the registry
if preview.is_enabled() {
#[cfg(windows)]
{
uv_python::windows_registry::remove_orphan_registry_entries(
&installed_installations,
);
}
#[cfg(windows)]
{
uv_python::windows_registry::remove_orphan_registry_entries(
&installed_installations,
);
}
if matches!(requests.as_slice(), [PythonRequest::Default]) {

View File

@ -7,7 +7,7 @@ use std::{collections::BTreeSet, ffi::OsString};
use tracing::{debug, warn};
use uv_cache::Cache;
use uv_client::BaseClientBuilder;
use uv_configuration::PreviewMode;
use uv_configuration::Preview;
use uv_distribution_types::Requirement;
use uv_distribution_types::{InstalledDist, Name};
use uv_fs::Simplified;
@ -81,7 +81,7 @@ pub(crate) async fn refine_interpreter(
python_preference: PythonPreference,
python_downloads: PythonDownloads,
cache: &Cache,
preview: PreviewMode,
preview: Preview,
) -> anyhow::Result<Option<Interpreter>, ProjectError> {
let pip::operations::Error::Resolve(uv_resolver::ResolveError::NoSolution(no_solution_err)) =
err

View File

@ -2,12 +2,12 @@ use anstream::println;
use anyhow::Context;
use owo_colors::OwoColorize;
use uv_configuration::PreviewMode;
use uv_configuration::Preview;
use uv_fs::Simplified;
use uv_tool::{InstalledTools, tool_executable_dir};
/// Show the tool directory.
pub(crate) fn dir(bin: bool, _preview: PreviewMode) -> anyhow::Result<()> {
pub(crate) fn dir(bin: bool, _preview: Preview) -> anyhow::Result<()> {
if bin {
let executable_directory = tool_executable_dir()?;
println!("{}", executable_directory.simplified_display().cyan());

View File

@ -8,7 +8,7 @@ use tracing::{debug, trace};
use uv_cache::{Cache, Refresh};
use uv_cache_info::Timestamp;
use uv_client::BaseClientBuilder;
use uv_configuration::{Concurrency, Constraints, DryRun, PreviewMode, Reinstall, Upgrade};
use uv_configuration::{Concurrency, Constraints, DryRun, Preview, Reinstall, Upgrade};
use uv_distribution_types::{
NameRequirementSpecification, Requirement, RequirementSource,
UnresolvedRequirementSpecification,
@ -62,7 +62,7 @@ pub(crate) async fn install(
concurrency: Concurrency,
cache: Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
let client_builder = BaseClientBuilder::new()
.retries_from_env()?

View File

@ -17,7 +17,7 @@ use uv_cache_info::Timestamp;
use uv_cli::ExternalCommand;
use uv_client::BaseClientBuilder;
use uv_configuration::Constraints;
use uv_configuration::{Concurrency, PreviewMode};
use uv_configuration::{Concurrency, Preview};
use uv_distribution_types::InstalledDist;
use uv_distribution_types::{
IndexUrl, Name, NameRequirementSpecification, Requirement, RequirementSource,
@ -101,7 +101,7 @@ pub(crate) async fn run(
printer: Printer,
env_file: Vec<PathBuf>,
no_env_file: bool,
preview: PreviewMode,
preview: Preview,
) -> anyhow::Result<ExitStatus> {
/// Whether or not a path looks like a Python script based on the file extension.
fn has_python_script_ext(path: &Path) -> bool {
@ -686,7 +686,7 @@ async fn get_or_create_environment(
concurrency: Concurrency,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<(ToolRequirement, PythonEnvironment), ProjectError> {
let client_builder = BaseClientBuilder::new()
.retries_from_env()?

View File

@ -7,7 +7,7 @@ use tracing::debug;
use uv_cache::Cache;
use uv_client::BaseClientBuilder;
use uv_configuration::{Concurrency, Constraints, DryRun, PreviewMode};
use uv_configuration::{Concurrency, Constraints, DryRun, Preview};
use uv_distribution_types::Requirement;
use uv_fs::CWD;
use uv_normalize::PackageName;
@ -47,7 +47,7 @@ pub(crate) async fn upgrade(
concurrency: Concurrency,
cache: &Cache,
printer: Printer,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
let installed_tools = InstalledTools::from_settings()?.init()?;
let _lock = installed_tools.lock().await?;
@ -221,7 +221,7 @@ async fn upgrade_tool(
filesystem: &ResolverInstallerOptions,
installer_metadata: bool,
concurrency: Concurrency,
preview: PreviewMode,
preview: Preview,
) -> Result<UpgradeOutcome> {
// Ensure the tool is installed.
let existing_tool_receipt = match installed_tools.get_tool_receipt(name) {

View File

@ -1,7 +1,6 @@
use std::fmt::Write;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::Arc;
use std::vec;
use anyhow::Result;
@ -12,7 +11,8 @@ use uv_cache::Cache;
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, Constraints, DependencyGroups, IndexStrategy,
KeyringProviderType, NoBinary, NoBuild, PackageConfigSettings, PreviewMode, SourceStrategy,
KeyringProviderType, NoBinary, NoBuild, PackageConfigSettings, Preview, PreviewFeatures,
SourceStrategy,
};
use uv_dispatch::{BuildDispatch, SharedState};
use uv_distribution_types::Requirement;
@ -83,7 +83,7 @@ pub(crate) async fn venv(
cache: &Cache,
printer: Printer,
relocatable: bool,
preview: PreviewMode,
preview: Preview,
) -> Result<ExitStatus> {
let workspace_cache = WorkspaceCache::default();
let project = if no_project {
@ -200,7 +200,7 @@ pub(crate) async fn venv(
path.user_display().cyan()
)?;
let upgradeable = preview.is_enabled()
let upgradeable = preview.is_enabled(PreviewFeatures::PYTHON_UPGRADE)
&& python_request
.as_ref()
.is_none_or(|request| !request.includes_patch());
@ -225,15 +225,7 @@ pub(crate) async fn venv(
let interpreter = venv.interpreter();
// Add all authenticated sources to the cache.
for index in index_locations.allowed_indexes() {
if let Some(credentials) = index.credentials() {
let credentials = Arc::new(credentials);
uv_auth::store_credentials(index.raw_url(), credentials.clone());
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
}
}
}
index_locations.cache_index_credentials();
// Instantiate a client.
let client = RegistryClientBuilder::try_from(client_builder)?

View File

@ -28,7 +28,7 @@ use uv_cli::{
ProjectCommand, PythonCommand, PythonNamespace, SelfCommand, SelfNamespace, ToolCommand,
ToolNamespace, TopLevelArgs, compat::CompatArgs,
};
use uv_configuration::min_stack_size;
use uv_configuration::{PreviewFeatures, min_stack_size};
use uv_fs::{CWD, Simplified};
#[cfg(feature = "self-update")]
use uv_pep440::release_specifiers_to_ranges;
@ -443,9 +443,14 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipCompileSettings::resolve(args, filesystem);
show_settings!(args);
if !args.settings.extra_build_dependencies.is_empty() && globals.preview.is_disabled() {
if !args.settings.extra_build_dependencies.is_empty()
&& !globals
.preview
.is_enabled(PreviewFeatures::EXTRA_BUILD_DEPENDENCIES)
{
warn_user_once!(
"The `extra-build-dependencies` setting is experimental and may change without warning. Pass `--preview` to disable this warning."
"The `extra-build-dependencies` setting is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
PreviewFeatures::EXTRA_BUILD_DEPENDENCIES
);
}
@ -549,9 +554,14 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipSyncSettings::resolve(args, filesystem);
show_settings!(args);
if !args.settings.extra_build_dependencies.is_empty() && globals.preview.is_disabled() {
if !args.settings.extra_build_dependencies.is_empty()
&& !globals
.preview
.is_enabled(PreviewFeatures::EXTRA_BUILD_DEPENDENCIES)
{
warn_user_once!(
"The `extra-build-dependencies` setting is experimental and may change without warning. Pass `--preview` to disable this warning."
"The `extra-build-dependencies` setting is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
PreviewFeatures::EXTRA_BUILD_DEPENDENCIES
);
}
@ -633,9 +643,14 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
// Resolve the settings from the command-line arguments and workspace configuration.
let mut args = PipInstallSettings::resolve(args, filesystem);
show_settings!(args);
if !args.settings.extra_build_dependencies.is_empty() && globals.preview.is_disabled() {
if !args.settings.extra_build_dependencies.is_empty()
&& !globals
.preview
.is_enabled(PreviewFeatures::EXTRA_BUILD_DEPENDENCIES)
{
warn_user_once!(
"The `extra-build-dependencies` setting is experimental and may change without warning. Pass `--preview` to disable this warning."
"The `extra-build-dependencies` setting is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
PreviewFeatures::EXTRA_BUILD_DEPENDENCIES
);
}

View File

@ -23,9 +23,9 @@ use uv_client::Connectivity;
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, DependencyGroups, DryRun, EditableMode,
ExportFormat, ExtrasSpecification, HashCheckingMode, IndexStrategy, InstallOptions,
KeyringProviderType, NoBinary, NoBuild, PackageConfigSettings, PreviewMode,
ProjectBuildBackend, Reinstall, RequiredVersion, SourceStrategy, TargetTriple, TrustedHost,
TrustedPublishing, Upgrade, VersionControlSystem,
KeyringProviderType, NoBinary, NoBuild, PackageConfigSettings, Preview, ProjectBuildBackend,
Reinstall, RequiredVersion, SourceStrategy, TargetTriple, TrustedHost, TrustedPublishing,
Upgrade, VersionControlSystem,
};
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, IndexUrl, Requirement};
use uv_install_wheel::LinkMode;
@ -63,7 +63,7 @@ pub(crate) struct GlobalSettings {
pub(crate) network_settings: NetworkSettings,
pub(crate) concurrency: Concurrency,
pub(crate) show_settings: bool,
pub(crate) preview: PreviewMode,
pub(crate) preview: Preview,
pub(crate) python_preference: PythonPreference,
pub(crate) python_downloads: PythonDownloads,
pub(crate) no_progress: bool,
@ -117,10 +117,12 @@ impl GlobalSettings {
.unwrap_or_else(Concurrency::threads),
},
show_settings: args.show_settings,
preview: PreviewMode::from(
preview: Preview::from_args(
flag(args.preview, args.no_preview, "preview")
.combine(workspace.and_then(|workspace| workspace.globals.preview))
.unwrap_or(false),
args.no_preview,
&args.preview_features,
),
python_preference,
python_downloads: flag(

View File

@ -21,7 +21,7 @@ use regex::Regex;
use tokio::io::AsyncWriteExt;
use uv_cache::Cache;
use uv_configuration::PreviewMode;
use uv_configuration::Preview;
use uv_fs::Simplified;
use uv_python::managed::ManagedPythonInstallations;
use uv_python::{
@ -706,6 +706,11 @@ impl TestContext {
),
r#"requires = ["uv_build>=[CURRENT_VERSION],<[NEXT_BREAKING]"]"#.to_string(),
));
// Filter script environment hashes
filters.push((
r"environments-v(\d+)[\\/](\w+)-[a-z0-9]+".to_string(),
"environments-v$1/$2-[HASH]".to_string(),
));
Self {
root: ChildPath::new(root.path()),
@ -1500,7 +1505,7 @@ pub fn python_installations_for_versions(
EnvironmentPreference::OnlySystem,
PythonPreference::Managed,
&cache,
PreviewMode::Disabled,
Preview::default(),
) {
python.into_interpreter().sys_executable().to_owned()
} else {

View File

@ -13021,7 +13021,7 @@ fn add_bounds() -> Result<()> {
----- stdout -----
----- stderr -----
warning: The bounds option is in preview and may change in any future release.
warning: The `bounds` option is in preview and may change in any future release. Pass `--preview-features add-bounds` to disable this warning.
Resolved 2 packages in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
@ -13061,7 +13061,7 @@ fn add_bounds() -> Result<()> {
----- stdout -----
----- stderr -----
warning: The bounds option is in preview and may change in any future release.
warning: The `bounds` option is in preview and may change in any future release. Pass `--preview-features add-bounds` to disable this warning.
Resolved 4 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]

View File

@ -29411,3 +29411,490 @@ fn test_tilde_equals_python_version() -> Result<()> {
Ok(())
}
/// Test that lockfile validation includes explicit indexes from path dependencies.
/// <https://github.com/astral-sh/uv/issues/11419>
#[test]
fn lock_path_dependency_explicit_index() -> Result<()> {
let context = TestContext::new("3.12");
// Create the path dependency with explicit index
let pkg_a = context.temp_dir.child("pkg_a");
fs_err::create_dir_all(&pkg_a)?;
let pyproject_toml = pkg_a.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "pkg-a"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig"]
[tool.uv.sources]
iniconfig = { index = "inner-index" }
[[tool.uv.index]]
name = "inner-index"
url = "https://pypi-proxy.fly.dev/simple"
explicit = true
"#,
)?;
// Create a project that depends on pkg_a
let pkg_b = context.temp_dir.child("pkg_b");
fs_err::create_dir_all(&pkg_b)?;
let pyproject_toml = pkg_b.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "pkg-b"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["pkg-a"]
[tool.uv.sources]
pkg-a = { path = "../pkg_a/", editable = true }
black = { index = "outer-index" }
[[tool.uv.index]]
name = "outer-index"
url = "https://outer-index.com/simple"
explicit = true
"#,
)?;
uv_snapshot!(context.filters(), context.lock().current_dir(&pkg_b), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Resolved 3 packages in [TIME]
");
uv_snapshot!(context.filters(), context.lock().arg("--check").current_dir(&pkg_b), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Resolved 3 packages in [TIME]
");
Ok(())
}
/// Test that lockfile validation includes explicit indexes from path dependencies
/// defined in a non-root workspace member.
#[test]
fn lock_path_dependency_explicit_index_workspace_member() -> Result<()> {
let context = TestContext::new("3.12");
// Create the path dependency with explicit index
let pkg_a = context.temp_dir.child("pkg_a");
fs_err::create_dir_all(&pkg_a)?;
let pyproject_toml = pkg_a.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "pkg-a"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig"]
[tool.uv.sources]
iniconfig = { index = "inner-index" }
[[tool.uv.index]]
name = "inner-index"
url = "https://pypi-proxy.fly.dev/simple"
explicit = true
"#,
)?;
// Create a project that depends on pkg_a
let member = context.temp_dir.child("member");
fs_err::create_dir_all(&member)?;
let pyproject_toml = member.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "member"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["pkg-a"]
[tool.uv.sources]
pkg-a = { path = "../pkg_a/", editable = true }
black = { index = "middle-index" }
[[tool.uv.index]]
name = "middle-index"
url = "https://middle-index.com/simple"
explicit = true
"#,
)?;
// Create a root with workspace member
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "root-project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["member"]
[tool.uv.workspace]
members = ["member"]
[tool.uv.sources]
member = { workspace = true }
anyio = { index = "outer-index" }
[[tool.uv.index]]
name = "outer-index"
url = "https://outer-index.com/simple"
explicit = true
"#,
)?;
uv_snapshot!(context.filters(), context.lock(), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 4 packages in [TIME]
");
uv_snapshot!(context.filters(), context.lock().arg("--check"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 4 packages in [TIME]
");
Ok(())
}
/// Test that lockfile validation works correctly when path dependency has
/// both explicit and non-explicit indexes.
#[test]
fn lock_path_dependency_mixed_indexes() -> Result<()> {
let context = TestContext::new("3.12");
// Create the path dependency with both explicit and non-explicit indexes.
let pkg_a = context.temp_dir.child("pkg_a");
fs_err::create_dir_all(&pkg_a)?;
let pyproject_toml = pkg_a.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "pkg-a"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig", "anyio"]
[tool.uv.sources]
iniconfig = { index = "explicit-index" }
anyio = { index = "non-explicit-index" }
[[tool.uv.index]]
name = "non-explicit-index"
url = "https://pypi-proxy.fly.dev/simple"
[[tool.uv.index]]
name = "explicit-index"
url = "https://pypi.org/simple"
explicit = true
"#,
)?;
// Create a project that depends on pkg_a.
let pkg_b = context.temp_dir.child("pkg_b");
fs_err::create_dir_all(&pkg_b)?;
let pyproject_toml = pkg_b.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "pkg-b"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["pkg-a"]
[tool.uv.sources]
pkg-a = { path = "../pkg_a/", editable = true }
black = { index = "outer-index" }
[[tool.uv.index]]
name = "outer-index"
url = "https://outer-index.com/simple"
explicit = true
"#,
)?;
uv_snapshot!(context.filters(), context.lock().current_dir(&pkg_b), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Resolved 6 packages in [TIME]
");
uv_snapshot!(context.filters(), context.lock().arg("--check").current_dir(&pkg_b), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Resolved 6 packages in [TIME]
");
Ok(())
}
/// Test that path dependencies without an index don't affect validation.
#[test]
fn lock_path_dependency_no_index() -> Result<()> {
let context = TestContext::new("3.12");
// Create the path dependency without explicit indexes.
let pkg_a = context.temp_dir.child("pkg_a");
fs_err::create_dir_all(&pkg_a)?;
let pyproject_toml = pkg_a.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "pkg-a"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["requests"]
"#,
)?;
// Create a project that depends on pkg_a.
let pkg_b = context.temp_dir.child("pkg_b");
fs_err::create_dir_all(&pkg_b)?;
let pyproject_toml = pkg_b.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "pkg-b"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["pkg-a"]
[tool.uv.sources]
pkg-a = { path = "../pkg_a/", editable = true }
"#,
)?;
uv_snapshot!(context.filters(), context.lock().current_dir(&pkg_b), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Resolved 7 packages in [TIME]
");
uv_snapshot!(context.filters(), context.lock().arg("--check").current_dir(&pkg_b), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Resolved 7 packages in [TIME]
");
Ok(())
}
/// Test that a nested path dependency with an explicit index validates correctly.
#[test]
fn lock_nested_path_dependency_explicit_index() -> Result<()> {
let context = TestContext::new("3.12");
// Create the inner dependency with explicit index.
let pkg_a = context.temp_dir.child("pkg_a");
fs_err::create_dir_all(&pkg_a)?;
let pyproject_toml = pkg_a.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "pkg-a"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig"]
[tool.uv.sources]
iniconfig = { index = "inner-index" }
[[tool.uv.index]]
name = "inner-index"
url = "https://pypi-proxy.fly.dev/simple"
explicit = true
"#,
)?;
// Create intermediate dependency that depends on pkg_a.
let pkg_b = context.temp_dir.child("pkg_b");
fs_err::create_dir_all(&pkg_b)?;
let pyproject_toml = pkg_b.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "pkg-b"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["pkg-a"]
[tool.uv.sources]
pkg-a = { path = "../pkg_a/", editable = true }
"#,
)?;
// Create a project that depends on intermediate dependency.
let pkg_c = context.temp_dir.child("pkg_c");
fs_err::create_dir_all(&pkg_c)?;
let pyproject_toml = pkg_c.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "pkg-c"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["pkg-b"]
[tool.uv.sources]
pkg-b = { path = "../pkg_b/", editable = true }
black = { index = "outer-index" }
[[tool.uv.index]]
name = "outer-index"
url = "https://outer-index.com/simple"
explicit = true
"#,
)?;
uv_snapshot!(context.filters(), context.lock().current_dir(&pkg_c), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Resolved 4 packages in [TIME]
");
uv_snapshot!(context.filters(), context.lock().arg("--check").current_dir(&pkg_c), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Resolved 4 packages in [TIME]
");
Ok(())
}
/// Test that validating circular path dependency indexes doesn't cause an infinite loop.
#[test]
fn lock_circular_path_dependency_explicit_index() -> Result<()> {
let context = TestContext::new("3.12");
// Create pkg_a (with explicit index) that depends on pkg_b.
let pkg_a = context.temp_dir.child("pkg_a");
fs_err::create_dir_all(&pkg_a)?;
let pyproject_toml = pkg_a.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "pkg-a"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = ["pkg-b", "iniconfig"]
[tool.uv.sources]
pkg-b = { path = "../pkg_b/" }
iniconfig = { index = "index-a" }
[[tool.uv.index]]
name = "index-a"
url = "https://pypi-proxy.fly.dev/simple"
explicit = true
"#,
)?;
// Create pkg_b that depends on pkg_a. This is a circular dependency.
let pkg_b = context.temp_dir.child("pkg_b");
fs_err::create_dir_all(&pkg_b)?;
let pyproject_toml = pkg_b.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "pkg-b"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = ["pkg-a", "anyio"]
[tool.uv.sources]
pkg-a = { path = "../pkg_a/" }
anyio = { index = "index-b" }
[[tool.uv.index]]
name = "index-b"
url = "https://pypi.org/simple"
explicit = true
default = true
"#,
)?;
// This should not hang or crash due to the circular dependency.
uv_snapshot!(context.filters(), context.lock().current_dir(&pkg_a), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Resolved 8 packages in [TIME]
");
uv_snapshot!(context.filters(), context.lock().arg("--check").current_dir(&pkg_a), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Using CPython 3.12.[X] interpreter at: [PYTHON-3.12]
Resolved 8 packages in [TIME]
");
Ok(())
}

View File

@ -1057,6 +1057,7 @@ fn extra_unconditional() -> Result<()> {
----- stderr -----
Resolved 6 packages in [TIME]
"###);
// This should error since we're enabling two conflicting extras.
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
success: false
@ -1652,6 +1653,249 @@ fn extra_nested_across_workspace() -> Result<()> {
Ok(())
}
/// The project declares conflicting extras, but one of the extras directly depends on the other.
#[test]
fn extra_depends_on_conflicting_extra() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "example"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[project.optional-dependencies]
foo = ["sortedcontainers==2.3.0", "example[bar]"]
bar = ["sortedcontainers==2.4.0"]
[tool.uv]
conflicts = [
[
{ extra = "foo" },
{ extra = "bar" },
],
]
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
[tool.setuptools.packages.find]
include = ["example"]
"#,
)?;
// This should fail to resolve, because the extras are always required together and
// `example[foo]` is unusable.
uv_snapshot!(context.filters(), context.lock(), @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
Because example[foo] depends on sortedcontainers==2.3.0 and sortedcontainers==2.4.0, we can conclude that example[foo]'s requirements are unsatisfiable.
And because your project requires example[foo], we can conclude that your project's requirements are unsatisfiable.
");
Ok(())
}
/// Like [`extra_depends_on_conflicting_extra`], but the conflict between the extras is mediated by
/// another package.
#[test]
fn extra_depends_on_conflicting_extra_transitive() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "example"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[project.optional-dependencies]
foo = ["sortedcontainers==2.3.0", "indirection"]
bar = ["sortedcontainers==2.4.0"]
[tool.uv]
conflicts = [
[
{ extra = "foo" },
{ extra = "bar" },
],
]
[tool.uv.sources]
indirection = { workspace = true }
[tool.uv.workspace]
members = ["indirection"]
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
[tool.setuptools.packages.find]
include = ["example"]
"#,
)?;
// Create the indirection subproject
let subproject_dir = context.temp_dir.child("indirection");
subproject_dir.create_dir_all()?;
let sub_pyproject_toml = subproject_dir.child("pyproject.toml");
sub_pyproject_toml.write_str(
r#"
[project]
name = "indirection"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["example[bar]"]
[tool.uv.sources]
example = { workspace = true }
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
"#,
)?;
// This succeeds, but probably shouldn't. There's an unconditional conflict in `example[foo]
// -> indirection[bar] -> example[bar]`, which means `example[foo]` can never be used.
uv_snapshot!(context.filters(), context.lock(), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 4 packages in [TIME]
");
let lock = context.read("uv.lock");
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r#"
version = 1
revision = 2
requires-python = ">=3.12"
conflicts = [[
{ package = "example", extra = "bar" },
{ package = "example", extra = "foo" },
]]
[options]
exclude-newer = "2024-03-25T00:00:00Z"
[manifest]
members = [
"example",
"indirection",
]
[[package]]
name = "example"
version = "0.1.0"
source = { editable = "." }
[package.optional-dependencies]
bar = [
{ name = "sortedcontainers", version = "2.4.0", source = { registry = "https://pypi.org/simple" } },
]
foo = [
{ name = "indirection" },
{ name = "sortedcontainers", version = "2.3.0", source = { registry = "https://pypi.org/simple" } },
]
[package.metadata]
requires-dist = [
{ name = "indirection", marker = "extra == 'foo'", editable = "indirection" },
{ name = "sortedcontainers", marker = "extra == 'bar'", specifier = "==2.4.0" },
{ name = "sortedcontainers", marker = "extra == 'foo'", specifier = "==2.3.0" },
]
provides-extras = ["foo", "bar"]
[[package]]
name = "indirection"
version = "0.1.0"
source = { editable = "indirection" }
dependencies = [
{ name = "example" },
{ name = "example", extra = ["bar"], marker = "extra == 'extra-7-example-bar'" },
]
[package.metadata]
requires-dist = [{ name = "example", extras = ["bar"], editable = "." }]
[[package]]
name = "sortedcontainers"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/14/10/6a9481890bae97da9edd6e737c9c3dec6aea3fc2fa53b0934037b35c89ea/sortedcontainers-2.3.0.tar.gz", hash = "sha256:59cc937650cf60d677c16775597c89a960658a09cf7c1a668f86e1e4464b10a1", size = 30509, upload-time = "2020-11-09T00:03:52.258Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/4d/a7046ae1a1a4cc4e9bbed194c387086f06b25038be596543d026946330c9/sortedcontainers-2.3.0-py2.py3-none-any.whl", hash = "sha256:37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f", size = 29479, upload-time = "2020-11-09T00:03:50.723Z" },
]
[[package]]
name = "sortedcontainers"
version = "2.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" },
]
"#
);
});
// Install from the lockfile
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ example==0.1.0 (from file://[TEMP_DIR]/)
");
// Install with `foo`
uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--extra").arg("foo"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Found conflicting extras `example[bar]` and `example[foo]` enabled simultaneously
");
// Install the child package
uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--package").arg("indirection"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ indirection==0.1.0 (from file://[TEMP_DIR]/indirection)
+ sortedcontainers==2.4.0
");
Ok(())
}
/// This tests a "basic" case for specifying conflicting groups.
#[test]
fn group_basic() -> Result<()> {

View File

@ -2479,8 +2479,7 @@ fn install_git_private_https_pat_not_authorized() {
failed to clone into: [CACHE_DIR]/git-v0/db/8401f5508e3e612d
process didn't exit successfully: `git fetch --force --update-head-ok 'https://git:***@github.com/astral-test/uv-private-pypackage' '+HEAD:refs/remotes/origin/HEAD'` (exit status: 128)
--- stderr
remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
remote: Invalid username or token. Password authentication is not supported for Git operations.
fatal: Authentication failed for 'https://github.com/astral-test/uv-private-pypackage/'
");
}

View File

@ -837,16 +837,7 @@ fn python_find_script() {
.with_filtered_python_names()
.with_filtered_exe_suffix();
let filters = context
.filters()
.into_iter()
.chain(vec![(
r"environments-v2/[\w-]+",
"environments-v2/[HASHEDNAME]",
)])
.collect::<Vec<_>>();
uv_snapshot!(filters, context.init().arg("--script").arg("foo.py"), @r###"
uv_snapshot!(context.filters(), context.init().arg("--script").arg("foo.py"), @r###"
success: true
exit_code: 0
----- stdout -----
@ -855,22 +846,22 @@ fn python_find_script() {
Initialized script at `foo.py`
"###);
uv_snapshot!(filters, context.sync().arg("--script").arg("foo.py"), @r"
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("foo.py"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Creating script environment at: [CACHE_DIR]/environments-v2/[HASHEDNAME]
Creating script environment at: [CACHE_DIR]/environments-v2/foo-[HASH]
Resolved in [TIME]
Audited in [TIME]
");
uv_snapshot!(filters, context.python_find().arg("--script").arg("foo.py"), @r"
uv_snapshot!(context.filters(), context.python_find().arg("--script").arg("foo.py"), @r"
success: true
exit_code: 0
----- stdout -----
[CACHE_DIR]/environments-v2/[HASHEDNAME]/[BIN]/[PYTHON]
[CACHE_DIR]/environments-v2/foo-[HASH]/[BIN]/[PYTHON]
----- stderr -----
");
@ -936,15 +927,6 @@ fn python_find_script_no_such_version() {
.with_filtered_python_names()
.with_filtered_exe_suffix()
.with_filtered_python_sources();
let filters = context
.filters()
.into_iter()
.chain(vec![(
r"environments-v2/[\w-]+",
"environments-v2/[HASHEDNAME]",
)])
.collect::<Vec<_>>();
let script = context.temp_dir.child("foo.py");
script
.write_str(indoc! {r#"
@ -955,13 +937,13 @@ fn python_find_script_no_such_version() {
"#})
.unwrap();
uv_snapshot!(filters, context.sync().arg("--script").arg("foo.py"), @r"
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("foo.py"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Creating script environment at: [CACHE_DIR]/environments-v2/[HASHEDNAME]
Creating script environment at: [CACHE_DIR]/environments-v2/foo-[HASH]
Resolved in [TIME]
Audited in [TIME]
");
@ -975,7 +957,7 @@ fn python_find_script_no_such_version() {
"#})
.unwrap();
uv_snapshot!(filters, context.python_find().arg("--script").arg("foo.py"), @r"
uv_snapshot!(context.filters(), context.python_find().arg("--script").arg("foo.py"), @r"
success: false
exit_code: 1
----- stdout -----

View File

@ -630,14 +630,14 @@ fn python_install_preview() {
"###);
// Should be a no-op when already installed
uv_snapshot!(context.filters(), context.python_install().arg("--preview"), @r###"
uv_snapshot!(context.filters(), context.python_install().arg("--preview"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Python is already installed. Use `uv python install <request>` to install another version.
"###);
");
// You can opt-in to a reinstall
uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("--reinstall"), @r"
@ -1260,7 +1260,7 @@ fn python_install_default() {
----- stdout -----
----- stderr -----
warning: The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning
warning: The `--default` option is experimental and may change without warning. Pass `--preview-features python-install-default` to disable this warning
Installed Python 3.13.5 in [TIME]
+ cpython-3.13.5-[PLATFORM] (python, python3)
");
@ -1294,7 +1294,7 @@ fn python_install_default() {
----- stdout -----
----- stderr -----
warning: The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning
warning: The `--default` option is experimental and may change without warning. Pass `--preview-features python-install-default` to disable this warning
Installed Python 3.13.5 in [TIME]
+ cpython-3.13.5-[PLATFORM] (python, python3, python3.13)
");
@ -1379,7 +1379,7 @@ fn python_install_default() {
----- stdout -----
----- stderr -----
warning: The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning
warning: The `--default` option is experimental and may change without warning. Pass `--preview-features python-install-default` to disable this warning
error: The `--default` flag cannot be used with multiple targets
");
@ -1390,7 +1390,7 @@ fn python_install_default() {
----- stdout -----
----- stderr -----
warning: The `--default` option is experimental and may change without warning. Pass `--preview` to disable this warning
warning: The `--default` option is experimental and may change without warning. Pass `--preview-features python-install-default` to disable this warning
Installed Python 3.12.11 in [TIME]
+ cpython-3.12.11-[PLATFORM] (python, python3, python3.12)
");

View File

@ -1167,14 +1167,17 @@ fn run_with() -> Result<()> {
let test_script = context.temp_dir.child("main.py");
test_script.write_str(indoc! { r"
import sniffio
print(sniffio.__version__)
"
})?;
// Requesting an unsatisfied requirement should install it.
uv_snapshot!(context.filters(), context.run().arg("--with").arg("iniconfig").arg("main.py"), @r###"
uv_snapshot!(context.filters(), context.run().arg("--with").arg("iniconfig").arg("main.py"), @r"
success: true
exit_code: 0
----- stdout -----
1.3.0
----- stderr -----
Resolved 2 packages in [TIME]
@ -1186,24 +1189,26 @@ fn run_with() -> Result<()> {
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0
"###);
");
// Requesting a satisfied requirement should use the base environment.
uv_snapshot!(context.filters(), context.run().arg("--with").arg("sniffio").arg("main.py"), @r###"
uv_snapshot!(context.filters(), context.run().arg("--with").arg("sniffio").arg("main.py"), @r"
success: true
exit_code: 0
----- stdout -----
1.3.0
----- stderr -----
Resolved 2 packages in [TIME]
Audited 2 packages in [TIME]
"###);
");
// Unless the user requests a different version.
uv_snapshot!(context.filters(), context.run().arg("--with").arg("sniffio<1.3.0").arg("main.py"), @r###"
uv_snapshot!(context.filters(), context.run().arg("--with").arg("sniffio<1.3.0").arg("main.py"), @r"
success: true
exit_code: 0
----- stdout -----
1.2.0
----- stderr -----
Resolved 2 packages in [TIME]
@ -1212,15 +1217,16 @@ fn run_with() -> Result<()> {
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ sniffio==1.2.0
"###);
");
// If we request a dependency that isn't in the base environment, we should still respect any
// other dependencies. In this case, `sniffio==1.3.0` is not the latest-compatible version, but
// we should use it anyway.
uv_snapshot!(context.filters(), context.run().arg("--with").arg("anyio").arg("main.py"), @r###"
uv_snapshot!(context.filters(), context.run().arg("--with").arg("anyio").arg("main.py"), @r"
success: true
exit_code: 0
----- stdout -----
1.3.0
----- stderr -----
Resolved 2 packages in [TIME]
@ -1231,13 +1237,14 @@ fn run_with() -> Result<()> {
+ anyio==4.3.0
+ idna==3.6
+ sniffio==1.3.0
"###);
");
// Even if we run with` --no-sync`.
uv_snapshot!(context.filters(), context.run().arg("--with").arg("anyio==4.2.0").arg("--no-sync").arg("main.py"), @r###"
uv_snapshot!(context.filters(), context.run().arg("--with").arg("anyio==4.2.0").arg("--no-sync").arg("main.py"), @r"
success: true
exit_code: 0
----- stdout -----
1.3.0
----- stderr -----
Resolved 3 packages in [TIME]
@ -1246,7 +1253,7 @@ fn run_with() -> Result<()> {
+ anyio==4.2.0
+ idna==3.6
+ sniffio==1.3.0
"###);
");
// If the dependencies can't be resolved, we should reference `--with`.
uv_snapshot!(context.filters(), context.run().arg("--with").arg("add").arg("main.py"), @r###"
@ -4024,17 +4031,8 @@ fn run_active_script_environment() -> Result<()> {
"#
})?;
let filters = context
.filters()
.into_iter()
.chain(vec![(
r"environments-v1/main-\w+",
"environments-v1/main-[HASH]",
)])
.collect::<Vec<_>>();
// Running `uv run --script` with `VIRTUAL_ENV` should _not_ warn.
uv_snapshot!(&filters, context.run()
uv_snapshot!(context.filters(), context.run()
.arg("--script")
.arg("main.py")
.env(EnvVars::VIRTUAL_ENV, "foo"), @r###"
@ -4051,7 +4049,7 @@ fn run_active_script_environment() -> Result<()> {
"###);
// Using `--no-active` should also _not_ warn.
uv_snapshot!(&filters, context.run()
uv_snapshot!(context.filters(), context.run()
.arg("--no-active")
.arg("--script")
.arg("main.py")
@ -4070,7 +4068,7 @@ fn run_active_script_environment() -> Result<()> {
.assert(predicate::path::missing());
// Using `--active` should create the environment
uv_snapshot!(&filters, context.run()
uv_snapshot!(context.filters(), context.run()
.arg("--active")
.arg("--script")
.arg("main.py")
@ -4092,7 +4090,7 @@ fn run_active_script_environment() -> Result<()> {
.assert(predicate::path::is_dir());
// Requesting a different Python version should invalidate the environment
uv_snapshot!(&filters, context.run()
uv_snapshot!(context.filters(), context.run()
.arg("--active")
.arg("-p").arg("3.12")
.arg("--script")

File diff suppressed because it is too large Load Diff

View File

@ -1655,7 +1655,7 @@ fn sync_extra_build_dependencies() -> Result<()> {
----- stdout -----
----- stderr -----
warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview` to disable this warning.
warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning.
Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME]
Installed [N] packages in [TIME]
@ -1685,7 +1685,7 @@ fn sync_extra_build_dependencies() -> Result<()> {
----- stdout -----
----- stderr -----
warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview` to disable this warning.
warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning.
Resolved [N] packages in [TIME]
× Failed to build `child @ file://[TEMP_DIR]/child`
The build backend returned an error
@ -1754,7 +1754,7 @@ fn sync_extra_build_dependencies() -> Result<()> {
----- stdout -----
----- stderr -----
warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview` to disable this warning.
warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning.
Resolved [N] packages in [TIME]
× Failed to build `bad-child @ file://[TEMP_DIR]/bad_child`
The build backend returned an error
@ -1790,7 +1790,7 @@ fn sync_extra_build_dependencies() -> Result<()> {
----- stdout -----
----- stderr -----
warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview` to disable this warning.
warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning.
Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME]
Installed [N] packages in [TIME]
@ -1866,7 +1866,7 @@ fn sync_extra_build_dependencies_sources() -> Result<()> {
----- stdout -----
----- stderr -----
warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview` to disable this warning.
warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning.
Resolved [N] packages in [TIME]
Prepared [N] packages in [TIME]
Installed [N] packages in [TIME]
@ -1946,7 +1946,7 @@ fn sync_extra_build_dependencies_sources_from_child() -> Result<()> {
----- stdout -----
----- stderr -----
warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview` to disable this warning.
warning: The `extra-build-dependencies` option is experimental and may change without warning. Pass `--preview-features extra-build-dependencies` to disable this warning.
Resolved [N] packages in [TIME]
× Failed to build `child @ file://[TEMP_DIR]/child`
The build backend returned an error
@ -5487,17 +5487,8 @@ fn sync_active_script_environment() -> Result<()> {
"#
})?;
let filters = context
.filters()
.into_iter()
.chain(vec![(
r"environments-v2/script-[a-z0-9]+",
"environments-v2/script-[HASH]",
)])
.collect::<Vec<_>>();
// Running `uv sync --script` with `VIRTUAL_ENV` should warn
uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo"), @r"
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo"), @r"
success: true
exit_code: 0
----- stdout -----
@ -5519,7 +5510,7 @@ fn sync_active_script_environment() -> Result<()> {
.assert(predicate::path::missing());
// Using `--active` should create the environment
uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r"
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r"
success: true
exit_code: 0
----- stdout -----
@ -5539,7 +5530,7 @@ fn sync_active_script_environment() -> Result<()> {
.assert(predicate::path::is_dir());
// A subsequent sync will re-use the environment
uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r"
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r"
success: true
exit_code: 0
----- stdout -----
@ -5551,7 +5542,7 @@ fn sync_active_script_environment() -> Result<()> {
");
// Requesting another Python version will invalidate the environment
uv_snapshot!(&filters, context.sync()
uv_snapshot!(context.filters(), context.sync()
.arg("--script")
.arg("script.py")
.env(EnvVars::VIRTUAL_ENV, "foo")
@ -5593,17 +5584,8 @@ fn sync_active_script_environment_json() -> Result<()> {
"#
})?;
let filters = context
.filters()
.into_iter()
.chain(vec![(
r"environments-v2/script-[a-z0-9]+",
"environments-v2/script-[HASH]",
)])
.collect::<Vec<_>>();
// Running `uv sync --script` with `VIRTUAL_ENV` should warn
uv_snapshot!(&filters, context.sync()
uv_snapshot!(context.filters(), context.sync()
.arg("--script").arg("script.py")
.arg("--output-format").arg("json")
.env(EnvVars::VIRTUAL_ENV, "foo"), @r#"
@ -5649,7 +5631,7 @@ fn sync_active_script_environment_json() -> Result<()> {
.assert(predicate::path::missing());
// Using `--active` should create the environment
uv_snapshot!(&filters, context.sync()
uv_snapshot!(context.filters(), context.sync()
.arg("--script").arg("script.py")
.arg("--output-format").arg("json")
.env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r#"
@ -5693,7 +5675,7 @@ fn sync_active_script_environment_json() -> Result<()> {
.assert(predicate::path::is_dir());
// A subsequent sync will re-use the environment
uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r"
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r"
success: true
exit_code: 0
----- stdout -----
@ -5705,7 +5687,7 @@ fn sync_active_script_environment_json() -> Result<()> {
");
// Requesting another Python version will invalidate the environment
uv_snapshot!(&filters, context.sync()
uv_snapshot!(context.filters(), context.sync()
.arg("--script").arg("script.py")
.arg("--output-format").arg("json")
.env(EnvVars::VIRTUAL_ENV, "foo")
@ -9591,16 +9573,7 @@ fn sync_script() -> Result<()> {
"#
})?;
let filters = context
.filters()
.into_iter()
.chain(vec![(
r"environments-v2/script-\w+",
"environments-v2/script-[HASH]",
)])
.collect::<Vec<_>>();
uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r"
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py"), @r"
success: true
exit_code: 0
----- stdout -----
@ -9632,7 +9605,7 @@ fn sync_script() -> Result<()> {
"#
})?;
uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r"
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py"), @r"
success: true
exit_code: 0
----- stdout -----
@ -9658,7 +9631,7 @@ fn sync_script() -> Result<()> {
"#
})?;
uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r"
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py"), @r"
success: true
exit_code: 0
----- stdout -----
@ -9683,7 +9656,7 @@ fn sync_script() -> Result<()> {
"#
})?;
uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r"
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py"), @r"
success: true
exit_code: 0
----- stdout -----
@ -9701,7 +9674,7 @@ fn sync_script() -> Result<()> {
");
// `--locked` and `--frozen` should fail with helpful error messages.
uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").arg("--locked"), @r"
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py").arg("--locked"), @r"
success: false
exit_code: 2
----- stdout -----
@ -9711,7 +9684,7 @@ fn sync_script() -> Result<()> {
error: `uv sync --locked` requires a script lockfile; run `uv lock --script script.py` to lock the script
");
uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").arg("--frozen"), @r"
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py").arg("--frozen"), @r"
success: false
exit_code: 2
----- stdout -----
@ -9741,17 +9714,8 @@ fn sync_locked_script() -> Result<()> {
"#
})?;
let filters = context
.filters()
.into_iter()
.chain(vec![(
r"environments-v2/script-\w+",
"environments-v2/script-[HASH]",
)])
.collect::<Vec<_>>();
// Lock the script.
uv_snapshot!(&filters, context.lock().arg("--script").arg("script.py"), @r###"
uv_snapshot!(context.filters(), context.lock().arg("--script").arg("script.py"), @r###"
success: true
exit_code: 0
----- stdout -----
@ -9811,7 +9775,7 @@ fn sync_locked_script() -> Result<()> {
);
});
uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r"
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py"), @r"
success: true
exit_code: 0
----- stdout -----
@ -9841,7 +9805,7 @@ fn sync_locked_script() -> Result<()> {
})?;
// Re-run with `--locked`.
uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").arg("--locked"), @r"
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py").arg("--locked"), @r"
success: false
exit_code: 1
----- stdout -----
@ -9852,7 +9816,7 @@ fn sync_locked_script() -> Result<()> {
The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`.
");
uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r"
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py"), @r"
success: true
exit_code: 0
----- stdout -----
@ -9943,7 +9907,7 @@ fn sync_locked_script() -> Result<()> {
})?;
// Re-run with `--locked`.
uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").arg("--locked"), @r"
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py").arg("--locked"), @r"
success: false
exit_code: 1
----- stdout -----
@ -9955,7 +9919,7 @@ fn sync_locked_script() -> Result<()> {
The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`.
");
uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r"
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py"), @r"
success: true
exit_code: 0
----- stdout -----
@ -10000,16 +9964,7 @@ fn sync_script_with_compatible_build_constraints() -> Result<()> {
"#
})?;
let filters = context
.filters()
.into_iter()
.chain(vec![(
r"environments-v2/script-\w+",
"environments-v2/script-[HASH]",
)])
.collect::<Vec<_>>();
uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r"
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py"), @r"
success: true
exit_code: 0
----- stdout -----
@ -10035,14 +9990,6 @@ fn sync_script_with_incompatible_build_constraints() -> Result<()> {
let context = TestContext::new("3.9");
let test_script = context.temp_dir.child("script.py");
let filters = context
.filters()
.into_iter()
.chain(vec![(
r"environments-v2/script-\w+",
"environments-v2/script-[HASH]",
)])
.collect::<Vec<_>>();
// Incompatible build constraints.
test_script.write_str(indoc! { r#"
@ -10061,7 +10008,7 @@ fn sync_script_with_incompatible_build_constraints() -> Result<()> {
"#
})?;
uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r"
uv_snapshot!(context.filters(), context.sync().arg("--script").arg("script.py"), @r"
success: false
exit_code: 1
----- stdout -----

View File

@ -3629,3 +3629,206 @@ fn tool_install_mismatched_name() {
error: Package name (`black`) provided with `--from` does not match install request (`flask`)
"###);
}
/// When installing from an authenticated index, the credentials should be omitted from the receipt.
#[test]
fn tool_install_credentials() {
let context = TestContext::new("3.12")
.with_exclude_newer("2025-01-18T00:00:00Z")
.with_filtered_counts()
.with_filtered_exe_suffix();
let tool_dir = context.temp_dir.child("tools");
let bin_dir = context.temp_dir.child("bin");
// Install `executable-application`
uv_snapshot!(context.filters(), context.tool_install()
.arg("executable-application")
.arg("--index")
.arg("https://public:heron@pypi-proxy.fly.dev/basic-auth/simple")
.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]
+ executable-application==0.3.0
Installed 1 executable: app
"###);
tool_dir
.child("executable-application")
.assert(predicate::path::is_dir());
tool_dir
.child("executable-application")
.child("uv-receipt.toml")
.assert(predicate::path::exists());
let executable = bin_dir.child(format!("app{}", std::env::consts::EXE_SUFFIX));
assert!(executable.exists());
// On Windows, we can't snapshot an executable file.
#[cfg(not(windows))]
insta::with_settings!({
filters => context.filters(),
}, {
// Should run black in the virtual environment
assert_snapshot!(fs_err::read_to_string(executable).unwrap(), @r###"
#![TEMP_DIR]/tools/executable-application/bin/python
# -*- coding: utf-8 -*-
import sys
from executable_application import main
if __name__ == "__main__":
if sys.argv[0].endswith("-script.pyw"):
sys.argv[0] = sys.argv[0][:-11]
elif sys.argv[0].endswith(".exe"):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())
"###);
});
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "executable-application" }]
entrypoints = [
{ name = "app", install-path = "[TEMP_DIR]/bin/app" },
]
[tool.options]
index = [{ url = "https://pypi-proxy.fly.dev/basic-auth/simple", explicit = false, default = false, format = "simple", authenticate = "auto" }]
exclude-newer = "2025-01-18T00:00:00Z"
"#);
});
}
/// When installing from an authenticated index, the credentials should be omitted from the receipt.
#[test]
fn tool_install_default_credentials() -> Result<()> {
let context = TestContext::new("3.12")
.with_exclude_newer("2025-01-18T00:00:00Z")
.with_filtered_counts()
.with_filtered_exe_suffix();
let tool_dir = context.temp_dir.child("tools");
let bin_dir = context.temp_dir.child("bin");
// Write a `uv.toml` with a default index that has credentials.
let uv_toml = context.temp_dir.child("uv.toml");
uv_toml.write_str(indoc::indoc! {r#"
[[index]]
url = "https://public:heron@pypi-proxy.fly.dev/basic-auth/simple"
default = true
authenticate = "always"
"#})?;
// Install `executable-application`
uv_snapshot!(context.filters(), context.tool_install()
.arg("executable-application")
.arg("--config-file")
.arg(uv_toml.as_os_str())
.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]
+ executable-application==0.3.0
Installed 1 executable: app
"###);
tool_dir
.child("executable-application")
.assert(predicate::path::is_dir());
tool_dir
.child("executable-application")
.child("uv-receipt.toml")
.assert(predicate::path::exists());
let executable = bin_dir.child(format!("app{}", std::env::consts::EXE_SUFFIX));
assert!(executable.exists());
// On Windows, we can't snapshot an executable file.
#[cfg(not(windows))]
insta::with_settings!({
filters => context.filters(),
}, {
// Should run black in the virtual environment
assert_snapshot!(fs_err::read_to_string(executable).unwrap(), @r###"
#![TEMP_DIR]/tools/executable-application/bin/python
# -*- coding: utf-8 -*-
import sys
from executable_application import main
if __name__ == "__main__":
if sys.argv[0].endswith("-script.pyw"):
sys.argv[0] = sys.argv[0][:-11]
elif sys.argv[0].endswith(".exe"):
sys.argv[0] = sys.argv[0][:-4]
sys.exit(main())
"###);
});
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "executable-application" }]
entrypoints = [
{ name = "app", install-path = "[TEMP_DIR]/bin/app" },
]
[tool.options]
index = [{ url = "https://pypi-proxy.fly.dev/basic-auth/simple", explicit = false, default = true, format = "simple", authenticate = "always" }]
exclude-newer = "2025-01-18T00:00:00Z"
"#);
});
// Attempt to upgrade without providing the credentials (from the config file).
uv_snapshot!(context.filters(), context.tool_upgrade()
.arg("executable-application")
.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: false
exit_code: 1
----- stdout -----
----- stderr -----
error: Failed to upgrade executable-application
Caused by: Failed to fetch: `https://pypi-proxy.fly.dev/basic-auth/simple/executable-application/`
Caused by: Missing credentials for https://pypi-proxy.fly.dev/basic-auth/simple/executable-application/
");
// Attempt to upgrade.
uv_snapshot!(context.filters(), context.tool_upgrade()
.arg("executable-application")
.arg("--config-file")
.arg(uv_toml.as_os_str())
.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 -----
Nothing to upgrade
");
Ok(())
}

View File

@ -31,7 +31,7 @@ To use uv as a build backend in an existing project, add `uv_build` to the
```toml title="pyproject.toml"
[build-system]
requires = ["uv_build>=0.8.2,<0.9.0"]
requires = ["uv_build>=0.8.3,<0.9.0"]
build-backend = "uv_build"
```

View File

@ -25,7 +25,7 @@ uv provides a standalone installer to download and install uv:
Request a specific version by including it in the URL:
```console
$ curl -LsSf https://astral.sh/uv/0.8.2/install.sh | sh
$ curl -LsSf https://astral.sh/uv/0.8.3/install.sh | sh
```
=== "Windows"
@ -41,7 +41,7 @@ uv provides a standalone installer to download and install uv:
Request a specific version by including it in the URL:
```pwsh-session
PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.8.2/install.ps1 | iex"
PS> powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/0.8.3/install.ps1 | iex"
```
!!! tip

View File

@ -92,7 +92,7 @@ the second stage, we'll copy this directory over to the final image, omitting th
other unnecessary files.
```dockerfile title="Dockerfile"
FROM ghcr.io/astral-sh/uv:0.8.2 AS uv
FROM ghcr.io/astral-sh/uv:0.8.3 AS uv
# First, bundle the dependencies into the task root.
FROM public.ecr.aws/lambda/python:3.13 AS builder
@ -334,7 +334,7 @@ And confirm that opening http://127.0.0.1:8000/ in a web browser displays, "Hell
Finally, we'll update the Dockerfile to include the local library in the deployment package:
```dockerfile title="Dockerfile"
FROM ghcr.io/astral-sh/uv:0.8.2 AS uv
FROM ghcr.io/astral-sh/uv:0.8.3 AS uv
# First, bundle the dependencies into the task root.
FROM public.ecr.aws/lambda/python:3.13 AS builder

View File

@ -31,7 +31,7 @@ $ docker run --rm -it ghcr.io/astral-sh/uv:debian uv --help
The following distroless images are available:
- `ghcr.io/astral-sh/uv:latest`
- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.8.2`
- `ghcr.io/astral-sh/uv:{major}.{minor}.{patch}`, e.g., `ghcr.io/astral-sh/uv:0.8.3`
- `ghcr.io/astral-sh/uv:{major}.{minor}`, e.g., `ghcr.io/astral-sh/uv:0.8` (the latest patch
version)
@ -75,7 +75,7 @@ And the following derived images are available:
As with the distroless image, each derived image is published with uv version tags as
`ghcr.io/astral-sh/uv:{major}.{minor}.{patch}-{base}` and
`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.8.2-alpine`.
`ghcr.io/astral-sh/uv:{major}.{minor}-{base}`, e.g., `ghcr.io/astral-sh/uv:0.8.3-alpine`.
In addition, starting with `0.8` each derived image also sets `UV_TOOL_BIN_DIR` to `/usr/local/bin`
to allow `uv tool install` to work as expected with the default user.
@ -116,7 +116,7 @@ Note this requires `curl` to be available.
In either case, it is best practice to pin to a specific uv version, e.g., with:
```dockerfile
COPY --from=ghcr.io/astral-sh/uv:0.8.2 /uv /uvx /bin/
COPY --from=ghcr.io/astral-sh/uv:0.8.3 /uv /uvx /bin/
```
!!! tip
@ -134,7 +134,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.8.2 /uv /uvx /bin/
Or, with the installer:
```dockerfile
ADD https://astral.sh/uv/0.8.2/install.sh /uv-installer.sh
ADD https://astral.sh/uv/0.8.3/install.sh /uv-installer.sh
```
### Installing a project
@ -560,5 +560,5 @@ Verified OK
!!! tip
These examples use `latest`, but best practice is to verify the attestation for a specific
version tag, e.g., `ghcr.io/astral-sh/uv:0.8.2`, or (even better) the specific image digest,
version tag, e.g., `ghcr.io/astral-sh/uv:0.8.3`, or (even better) the specific image digest,
such as `ghcr.io/astral-sh/uv:0.5.27@sha256:5adf09a5a526f380237408032a9308000d14d5947eafa687ad6c6a2476787b4f`.

View File

@ -47,7 +47,7 @@ jobs:
uses: astral-sh/setup-uv@v6
with:
# Install a specific version of uv.
version: "0.8.2"
version: "0.8.3"
```
## Setting up Python

View File

@ -19,7 +19,7 @@ To make sure your `uv.lock` file is up to date even if your `pyproject.toml` fil
repos:
- repo: https://github.com/astral-sh/uv-pre-commit
# uv version.
rev: 0.8.2
rev: 0.8.3
hooks:
- id: uv-lock
```
@ -30,7 +30,7 @@ To keep a `requirements.txt` file in sync with your `uv.lock` file:
repos:
- repo: https://github.com/astral-sh/uv-pre-commit
# uv version.
rev: 0.8.2
rev: 0.8.3
hooks:
- id: uv-export
```
@ -41,7 +41,7 @@ To compile requirements files:
repos:
- repo: https://github.com/astral-sh/uv-pre-commit
# uv version.
rev: 0.8.2
rev: 0.8.3
hooks:
# Compile requirements
- id: pip-compile
@ -54,7 +54,7 @@ To compile alternative requirements files, modify `args` and `files`:
repos:
- repo: https://github.com/astral-sh/uv-pre-commit
# uv version.
rev: 0.8.2
rev: 0.8.3
hooks:
# Compile requirements
- id: pip-compile
@ -68,7 +68,7 @@ To run the hook over multiple files at the same time, add additional entries:
repos:
- repo: https://github.com/astral-sh/uv-pre-commit
# uv version.
rev: 0.8.2
rev: 0.8.3
hooks:
# Compile requirements
- id: pip-compile

View File

@ -306,6 +306,10 @@ Equivalent to the `--prerelease` command-line argument. For example, if set to
Equivalent to the `--preview` argument. Enables preview mode.
### `UV_PREVIEW_FEATURES`
Equivalent to the `--preview-features` argument. Enables specific preview features.
### `UV_PROJECT`
Equivalent to the `--project` command-line argument.

View File

@ -447,7 +447,7 @@ data files are included by placing them in the Python module instead of using da
with this package as build requirement use the include directory to find additional header
files.
- `purelib` and `platlib`: Installed to the `site-packages` directory. It is not recommended
to uses these two options.
to use these two options.
**Default value**: `{}`
@ -457,7 +457,7 @@ data files are included by placing them in the Python module instead of using da
```toml title="pyproject.toml"
[tool.uv.build-backend]
data = { "headers": "include/headers", "scripts": "bin" }
data = { headers = "include/headers", scripts = "bin" }
```
---

View File

@ -4,7 +4,7 @@ build-backend = "maturin"
[project]
name = "uv"
version = "0.8.2"
version = "0.8.3"
description = "An extremely fast Python package and project manager, written in Rust."
authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }]
requires-python = ">=3.8"

2
uv.schema.json generated
View File

@ -670,7 +670,7 @@
"type": "object",
"properties": {
"data": {
"description": "Data includes for wheels.\n\nEach entry is a directory, whose contents are copied to the matching directory in the wheel\nin `<name>-<version>.data/(purelib|platlib|headers|scripts|data)`. Upon installation, this\ndata is moved to its target location, as defined by\n<https://docs.python.org/3.12/library/sysconfig.html#installation-paths>. Usually, small\ndata files are included by placing them in the Python module instead of using data includes.\n\n- `scripts`: Installed to the directory for executables, `<venv>/bin` on Unix or\n `<venv>\\Scripts` on Windows. This directory is added to `PATH` when the virtual\n environment is activated or when using `uv run`, so this data type can be used to install\n additional binaries. Consider using `project.scripts` instead for Python entrypoints.\n- `data`: Installed over the virtualenv environment root.\n\n Warning: This may override existing files!\n\n- `headers`: Installed to the include directory. Compilers building Python packages\n with this package as build requirement use the include directory to find additional header\n files.\n- `purelib` and `platlib`: Installed to the `site-packages` directory. It is not recommended\n to uses these two options.",
"description": "Data includes for wheels.\n\nEach entry is a directory, whose contents are copied to the matching directory in the wheel\nin `<name>-<version>.data/(purelib|platlib|headers|scripts|data)`. Upon installation, this\ndata is moved to its target location, as defined by\n<https://docs.python.org/3.12/library/sysconfig.html#installation-paths>. Usually, small\ndata files are included by placing them in the Python module instead of using data includes.\n\n- `scripts`: Installed to the directory for executables, `<venv>/bin` on Unix or\n `<venv>\\Scripts` on Windows. This directory is added to `PATH` when the virtual\n environment is activated or when using `uv run`, so this data type can be used to install\n additional binaries. Consider using `project.scripts` instead for Python entrypoints.\n- `data`: Installed over the virtualenv environment root.\n\n Warning: This may override existing files!\n\n- `headers`: Installed to the include directory. Compilers building Python packages\n with this package as build requirement use the include directory to find additional header\n files.\n- `purelib` and `platlib`: Installed to the `site-packages` directory. It is not recommended\n to use these two options.",
"allOf": [
{
"$ref": "#/definitions/WheelDataIncludes"