mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-15 10:12:16 +00:00
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | aarch64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | linux libc (push) Blocked by required conditions
CI / build binary | linux aarch64 (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / build binary | msrv (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / smoke test | linux aarch64 (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | aarch64 windows implicit (push) Blocked by required conditions
CI / integration test | aarch64 windows explicit (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | pyodide on ubuntu (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | registries (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | graalpy on ubuntu (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | aarch64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks | walltime aarch64 linux (push) Blocked by required conditions
CI / benchmarks | instrumented (push) Blocked by required conditions
zizmor / Run zizmor (push) Waiting to run
Revives https://github.com/astral-sh/uv/pull/9130 Previously, we allowed scoping conflicting extras or groups to specific packages, e.g. ,`{ package = "foo", extra = "bar" }` for a conflict in `foo[bar]`. Now, we allow dropping the `extra` or `group` bit and using `{ package = "foo" }` directly which declares a conflict with `foo`'s production dependencies. This means you can declare conflicts between workspace members, e.g.: ``` [tool.uv] conflicts = [[{ package = "foo" }, { package = "bar" }]] ``` would not allow `foo` and `bar` to be installed at the same time. Similarly, a conflict can be declared between a package and a group: ``` [tool.uv] conflicts = [[{ package = "foo" }, { group = "lint" }]] ``` which would mean, e.g., that `--only-group lint` would be required for the invocation. As with our existing support for conflicting extras, there are edge-cases here where the resolver will _not_ fail even if there are conflicts that render a particular install target unusable. There's test coverage for some of these. We'll still error at install-time when the conflicting groups are selected. Due to the likelihood of bugs in this feature, I've marked it as a preview feature. I would not recommend reading the commits as there's some slop from not wanting to rebase Andrew's branch. --------- Co-authored-by: Andrew Gallant <andrew@astral.sh>
257 lines
8 KiB
Rust
257 lines
8 KiB
Rust
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 PACKAGE_CONFLICTS = 1 << 5;
|
|
const EXTRA_BUILD_DEPENDENCIES = 1 << 6;
|
|
}
|
|
}
|
|
|
|
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::PACKAGE_CONFLICTS => "package-conflicts",
|
|
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(Self::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 = Self::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,
|
|
"package-conflicts" => Self::PACKAGE_CONFLICTS,
|
|
"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 struct Preview {
|
|
flags: PreviewFeatures,
|
|
}
|
|
|
|
impl Preview {
|
|
pub fn new(flags: PreviewFeatures) -> Self {
|
|
Self { flags }
|
|
}
|
|
|
|
pub fn all() -> Self {
|
|
Self::new(PreviewFeatures::all())
|
|
}
|
|
|
|
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 Preview {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
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::PACKAGE_CONFLICTS.flag_as_str(),
|
|
"package-conflicts"
|
|
);
|
|
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();
|
|
}
|
|
}
|