mirror of https://github.com/astral-sh/uv
Error when user-provided environments are disjoint with Python (#6841)
This commit is contained in:
parent
97e6861b35
commit
9f8ebca941
|
|
@ -7,6 +7,7 @@ use pubgrub::Range;
|
||||||
|
|
||||||
use distribution_filename::WheelFilename;
|
use distribution_filename::WheelFilename;
|
||||||
use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers};
|
use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers};
|
||||||
|
use pep508_rs::{MarkerExpression, MarkerTree, MarkerValueVersion};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum RequiresPythonError {
|
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.
|
/// Returns `true` if the `Requires-Python` is compatible with the given version.
|
||||||
pub fn contains(&self, version: &Version) -> bool {
|
pub fn contains(&self, version: &Version) -> bool {
|
||||||
let version = version.only_release();
|
let version = version.only_release();
|
||||||
|
|
|
||||||
|
|
@ -325,6 +325,25 @@ async fn do_lock(
|
||||||
default
|
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);
|
let python_requirement = PythonRequirement::from_requires_python(interpreter, &requires_python);
|
||||||
|
|
||||||
// Add all authenticated sources to the cache.
|
// Add all authenticated sources to the cache.
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use tracing::debug;
|
||||||
|
|
||||||
use distribution_types::{Resolution, UnresolvedRequirementSpecification};
|
use distribution_types::{Resolution, UnresolvedRequirementSpecification};
|
||||||
use pep440_rs::{Version, VersionSpecifiers};
|
use pep440_rs::{Version, VersionSpecifiers};
|
||||||
|
use pep508_rs::MarkerTreeContents;
|
||||||
use pypi_types::Requirement;
|
use pypi_types::Requirement;
|
||||||
use uv_auth::store_credentials_from_url;
|
use uv_auth::store_credentials_from_url;
|
||||||
use uv_cache::Cache;
|
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())]
|
#[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),
|
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)]
|
#[error(transparent)]
|
||||||
Python(#[from] uv_python::Error),
|
Python(#[from] uv_python::Error),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11962,3 +11962,37 @@ fn lock_implicit_virtual_path() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
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…
Reference in New Issue