mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-03 10:33:49 +00:00
Error when user-provided environments are disjoint with Python (#6841)
This commit is contained in:
parent
97e6861b35
commit
9f8ebca941
4 changed files with 140 additions and 0 deletions
|
@ -7,6 +7,7 @@ use pubgrub::Range;
|
|||
|
||||
use distribution_filename::WheelFilename;
|
||||
use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers};
|
||||
use pep508_rs::{MarkerExpression, MarkerTree, MarkerValueVersion};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum RequiresPythonError {
|
||||
|
@ -124,6 +125,85 @@ impl RequiresPython {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the [`RequiresPython`] as a [`MarkerTree`].
|
||||
pub fn markers(&self) -> MarkerTree {
|
||||
match (self.range.0.as_ref(), self.range.0.as_ref()) {
|
||||
(Bound::Included(lower), Bound::Included(upper)) => {
|
||||
let mut lower = MarkerTree::expression(MarkerExpression::Version {
|
||||
key: MarkerValueVersion::PythonFullVersion,
|
||||
specifier: VersionSpecifier::greater_than_equal_version(lower.clone()),
|
||||
});
|
||||
let upper = MarkerTree::expression(MarkerExpression::Version {
|
||||
key: MarkerValueVersion::PythonFullVersion,
|
||||
specifier: VersionSpecifier::less_than_equal_version(upper.clone()),
|
||||
});
|
||||
lower.and(upper);
|
||||
lower
|
||||
}
|
||||
(Bound::Included(lower), Bound::Excluded(upper)) => {
|
||||
let mut lower = MarkerTree::expression(MarkerExpression::Version {
|
||||
key: MarkerValueVersion::PythonFullVersion,
|
||||
specifier: VersionSpecifier::greater_than_equal_version(lower.clone()),
|
||||
});
|
||||
let upper = MarkerTree::expression(MarkerExpression::Version {
|
||||
key: MarkerValueVersion::PythonFullVersion,
|
||||
specifier: VersionSpecifier::less_than_version(upper.clone()),
|
||||
});
|
||||
lower.and(upper);
|
||||
lower
|
||||
}
|
||||
(Bound::Excluded(lower), Bound::Included(upper)) => {
|
||||
let mut lower = MarkerTree::expression(MarkerExpression::Version {
|
||||
key: MarkerValueVersion::PythonFullVersion,
|
||||
specifier: VersionSpecifier::greater_than_version(lower.clone()),
|
||||
});
|
||||
let upper = MarkerTree::expression(MarkerExpression::Version {
|
||||
key: MarkerValueVersion::PythonFullVersion,
|
||||
specifier: VersionSpecifier::less_than_equal_version(upper.clone()),
|
||||
});
|
||||
lower.and(upper);
|
||||
lower
|
||||
}
|
||||
(Bound::Excluded(lower), Bound::Excluded(upper)) => {
|
||||
let mut lower = MarkerTree::expression(MarkerExpression::Version {
|
||||
key: MarkerValueVersion::PythonFullVersion,
|
||||
specifier: VersionSpecifier::greater_than_version(lower.clone()),
|
||||
});
|
||||
let upper = MarkerTree::expression(MarkerExpression::Version {
|
||||
key: MarkerValueVersion::PythonFullVersion,
|
||||
specifier: VersionSpecifier::less_than_version(upper.clone()),
|
||||
});
|
||||
lower.and(upper);
|
||||
lower
|
||||
}
|
||||
(Bound::Unbounded, Bound::Unbounded) => MarkerTree::TRUE,
|
||||
(Bound::Unbounded, Bound::Included(upper)) => {
|
||||
MarkerTree::expression(MarkerExpression::Version {
|
||||
key: MarkerValueVersion::PythonFullVersion,
|
||||
specifier: VersionSpecifier::less_than_equal_version(upper.clone()),
|
||||
})
|
||||
}
|
||||
(Bound::Unbounded, Bound::Excluded(upper)) => {
|
||||
MarkerTree::expression(MarkerExpression::Version {
|
||||
key: MarkerValueVersion::PythonFullVersion,
|
||||
specifier: VersionSpecifier::less_than_version(upper.clone()),
|
||||
})
|
||||
}
|
||||
(Bound::Included(lower), Bound::Unbounded) => {
|
||||
MarkerTree::expression(MarkerExpression::Version {
|
||||
key: MarkerValueVersion::PythonFullVersion,
|
||||
specifier: VersionSpecifier::greater_than_equal_version(lower.clone()),
|
||||
})
|
||||
}
|
||||
(Bound::Excluded(lower), Bound::Unbounded) => {
|
||||
MarkerTree::expression(MarkerExpression::Version {
|
||||
key: MarkerValueVersion::PythonFullVersion,
|
||||
specifier: VersionSpecifier::greater_than_version(lower.clone()),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the `Requires-Python` is compatible with the given version.
|
||||
pub fn contains(&self, version: &Version) -> bool {
|
||||
let version = version.only_release();
|
||||
|
|
|
@ -325,6 +325,25 @@ async fn do_lock(
|
|||
default
|
||||
};
|
||||
|
||||
// If any of the forks are incompatible with the Python requirement, error.
|
||||
for environment in environments
|
||||
.map(SupportedEnvironments::as_markers)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
if requires_python.markers().is_disjoint(environment) {
|
||||
return if let Some(contents) = environment.contents() {
|
||||
Err(ProjectError::DisjointEnvironment(
|
||||
contents,
|
||||
requires_python.specifiers().clone(),
|
||||
))
|
||||
} else {
|
||||
Err(ProjectError::EmptyEnvironment)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the Python requirement.
|
||||
let python_requirement = PythonRequirement::from_requires_python(interpreter, &requires_python);
|
||||
|
||||
// Add all authenticated sources to the cache.
|
||||
|
|
|
@ -7,6 +7,7 @@ use tracing::debug;
|
|||
|
||||
use distribution_types::{Resolution, UnresolvedRequirementSpecification};
|
||||
use pep440_rs::{Version, VersionSpecifiers};
|
||||
use pep508_rs::MarkerTreeContents;
|
||||
use pypi_types::Requirement;
|
||||
use uv_auth::store_credentials_from_url;
|
||||
use uv_cache::Cache;
|
||||
|
@ -77,6 +78,12 @@ pub(crate) enum ProjectError {
|
|||
#[error("Supported environments must be disjoint, but the following markers overlap: `{0}` and `{1}`.\n\n{hint}{colon} replace `{1}` with `{2}`.", hint = "hint".bold().cyan(), colon = ":".bold())]
|
||||
OverlappingMarkers(String, String, String),
|
||||
|
||||
#[error("Environment markers `{0}` don't overlap with Python requirement `{1}`")]
|
||||
DisjointEnvironment(MarkerTreeContents, VersionSpecifiers),
|
||||
|
||||
#[error("Environment marker is empty")]
|
||||
EmptyEnvironment,
|
||||
|
||||
#[error(transparent)]
|
||||
Python(#[from] uv_python::Error),
|
||||
|
||||
|
|
|
@ -11962,3 +11962,37 @@ fn lock_implicit_virtual_path() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_conflicting_environment() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["anyio"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.uv]
|
||||
environments = ["python_version < '3.11'"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Environment markers `python_full_version < '3.11'` don't overlap with Python requirement `>=3.12`
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue