mirror of https://github.com/astral-sh/uv
Unwire PackageMetadata fields (#13635)
PackageMetadata, for whatever reason, does not have a mirrored Wire type so it was easy to not realize that it contains markers that need to be complexified. Fixes #13614 --------- Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
This commit is contained in:
parent
6ab1d12480
commit
cd8171d2a1
|
|
@ -1144,7 +1144,9 @@ impl Lock {
|
||||||
Some(
|
Some(
|
||||||
FlatRequiresDist::from_requirements(requires_dist.clone(), &package.id.name)
|
FlatRequiresDist::from_requirements(requires_dist.clone(), &package.id.name)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|requirement| normalize_requirement(requirement, root))
|
.map(|requirement| {
|
||||||
|
normalize_requirement(requirement, root, &self.requires_python)
|
||||||
|
})
|
||||||
.collect::<Result<BTreeSet<_>, _>>()?,
|
.collect::<Result<BTreeSet<_>, _>>()?,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1153,14 +1155,14 @@ impl Lock {
|
||||||
|
|
||||||
// Validate the `requires-dist` metadata.
|
// Validate the `requires-dist` metadata.
|
||||||
let expected: BTreeSet<_> = Box::into_iter(requires_dist)
|
let expected: BTreeSet<_> = Box::into_iter(requires_dist)
|
||||||
.map(|requirement| normalize_requirement(requirement, root))
|
.map(|requirement| normalize_requirement(requirement, root, &self.requires_python))
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
let actual: BTreeSet<_> = package
|
let actual: BTreeSet<_> = package
|
||||||
.metadata
|
.metadata
|
||||||
.requires_dist
|
.requires_dist
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|requirement| normalize_requirement(requirement, root))
|
.map(|requirement| normalize_requirement(requirement, root, &self.requires_python))
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
if expected != actual && flattened.is_none_or(|expected| expected != actual) {
|
if expected != actual && flattened.is_none_or(|expected| expected != actual) {
|
||||||
|
|
@ -1180,7 +1182,9 @@ impl Lock {
|
||||||
Ok::<_, LockError>((
|
Ok::<_, LockError>((
|
||||||
group,
|
group,
|
||||||
Box::into_iter(requirements)
|
Box::into_iter(requirements)
|
||||||
.map(|requirement| normalize_requirement(requirement, root))
|
.map(|requirement| {
|
||||||
|
normalize_requirement(requirement, root, &self.requires_python)
|
||||||
|
})
|
||||||
.collect::<Result<_, _>>()?,
|
.collect::<Result<_, _>>()?,
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
|
|
@ -1196,7 +1200,9 @@ impl Lock {
|
||||||
requirements
|
requirements
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|requirement| normalize_requirement(requirement, root))
|
.map(|requirement| {
|
||||||
|
normalize_requirement(requirement, root, &self.requires_python)
|
||||||
|
})
|
||||||
.collect::<Result<_, _>>()?,
|
.collect::<Result<_, _>>()?,
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
|
|
@ -1263,14 +1269,14 @@ impl Lock {
|
||||||
let expected: BTreeSet<_> = requirements
|
let expected: BTreeSet<_> = requirements
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|requirement| normalize_requirement(requirement, root))
|
.map(|requirement| normalize_requirement(requirement, root, &self.requires_python))
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
let actual: BTreeSet<_> = self
|
let actual: BTreeSet<_> = self
|
||||||
.manifest
|
.manifest
|
||||||
.requirements
|
.requirements
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|requirement| normalize_requirement(requirement, root))
|
.map(|requirement| normalize_requirement(requirement, root, &self.requires_python))
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
if expected != actual {
|
if expected != actual {
|
||||||
return Ok(SatisfiesResult::MismatchedRequirements(expected, actual));
|
return Ok(SatisfiesResult::MismatchedRequirements(expected, actual));
|
||||||
|
|
@ -1282,14 +1288,14 @@ impl Lock {
|
||||||
let expected: BTreeSet<_> = constraints
|
let expected: BTreeSet<_> = constraints
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|requirement| normalize_requirement(requirement, root))
|
.map(|requirement| normalize_requirement(requirement, root, &self.requires_python))
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
let actual: BTreeSet<_> = self
|
let actual: BTreeSet<_> = self
|
||||||
.manifest
|
.manifest
|
||||||
.constraints
|
.constraints
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|requirement| normalize_requirement(requirement, root))
|
.map(|requirement| normalize_requirement(requirement, root, &self.requires_python))
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
if expected != actual {
|
if expected != actual {
|
||||||
return Ok(SatisfiesResult::MismatchedConstraints(expected, actual));
|
return Ok(SatisfiesResult::MismatchedConstraints(expected, actual));
|
||||||
|
|
@ -1301,14 +1307,14 @@ impl Lock {
|
||||||
let expected: BTreeSet<_> = overrides
|
let expected: BTreeSet<_> = overrides
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|requirement| normalize_requirement(requirement, root))
|
.map(|requirement| normalize_requirement(requirement, root, &self.requires_python))
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
let actual: BTreeSet<_> = self
|
let actual: BTreeSet<_> = self
|
||||||
.manifest
|
.manifest
|
||||||
.overrides
|
.overrides
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|requirement| normalize_requirement(requirement, root))
|
.map(|requirement| normalize_requirement(requirement, root, &self.requires_python))
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
if expected != actual {
|
if expected != actual {
|
||||||
return Ok(SatisfiesResult::MismatchedOverrides(expected, actual));
|
return Ok(SatisfiesResult::MismatchedOverrides(expected, actual));
|
||||||
|
|
@ -1320,14 +1326,14 @@ impl Lock {
|
||||||
let expected: BTreeSet<_> = build_constraints
|
let expected: BTreeSet<_> = build_constraints
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|requirement| normalize_requirement(requirement, root))
|
.map(|requirement| normalize_requirement(requirement, root, &self.requires_python))
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
let actual: BTreeSet<_> = self
|
let actual: BTreeSet<_> = self
|
||||||
.manifest
|
.manifest
|
||||||
.build_constraints
|
.build_constraints
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|requirement| normalize_requirement(requirement, root))
|
.map(|requirement| normalize_requirement(requirement, root, &self.requires_python))
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
if expected != actual {
|
if expected != actual {
|
||||||
return Ok(SatisfiesResult::MismatchedBuildConstraints(
|
return Ok(SatisfiesResult::MismatchedBuildConstraints(
|
||||||
|
|
@ -1347,7 +1353,9 @@ impl Lock {
|
||||||
requirements
|
requirements
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|requirement| normalize_requirement(requirement, root))
|
.map(|requirement| {
|
||||||
|
normalize_requirement(requirement, root, &self.requires_python)
|
||||||
|
})
|
||||||
.collect::<Result<_, _>>()?,
|
.collect::<Result<_, _>>()?,
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
|
|
@ -1363,7 +1371,9 @@ impl Lock {
|
||||||
requirements
|
requirements
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|requirement| normalize_requirement(requirement, root))
|
.map(|requirement| {
|
||||||
|
normalize_requirement(requirement, root, &self.requires_python)
|
||||||
|
})
|
||||||
.collect::<Result<_, _>>()?,
|
.collect::<Result<_, _>>()?,
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
|
|
@ -2837,6 +2847,34 @@ struct PackageMetadata {
|
||||||
dependency_groups: BTreeMap<GroupName, BTreeSet<Requirement>>,
|
dependency_groups: BTreeMap<GroupName, BTreeSet<Requirement>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PackageMetadata {
|
||||||
|
fn unwire(self, requires_python: &RequiresPython) -> PackageMetadata {
|
||||||
|
// We need to complexify these markers so things like
|
||||||
|
// `requires_python < '0'` get normalized to False
|
||||||
|
let unwire_requirements = |requirements: BTreeSet<Requirement>| -> BTreeSet<Requirement> {
|
||||||
|
requirements
|
||||||
|
.into_iter()
|
||||||
|
.map(|mut requirement| {
|
||||||
|
let complexified_marker =
|
||||||
|
requires_python.complexify_markers(requirement.marker);
|
||||||
|
requirement.marker = complexified_marker;
|
||||||
|
requirement
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
PackageMetadata {
|
||||||
|
requires_dist: unwire_requirements(self.requires_dist),
|
||||||
|
provides_extras: self.provides_extras,
|
||||||
|
dependency_groups: self
|
||||||
|
.dependency_groups
|
||||||
|
.into_iter()
|
||||||
|
.map(|(group, requirements)| (group, unwire_requirements(requirements)))
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PackageWire {
|
impl PackageWire {
|
||||||
fn unwire(
|
fn unwire(
|
||||||
self,
|
self,
|
||||||
|
|
@ -2865,9 +2903,10 @@ impl PackageWire {
|
||||||
.map(|dep| dep.unwire(requires_python, unambiguous_package_ids))
|
.map(|dep| dep.unwire(requires_python, unambiguous_package_ids))
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Package {
|
Ok(Package {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
metadata: self.metadata,
|
metadata: self.metadata.unwire(requires_python),
|
||||||
sdist: self.sdist,
|
sdist: self.sdist,
|
||||||
wheels: self.wheels,
|
wheels: self.wheels,
|
||||||
fork_markers: self
|
fork_markers: self
|
||||||
|
|
@ -4546,9 +4585,11 @@ fn normalize_url(mut url: Url) -> UrlString {
|
||||||
/// 2. Ensures that the lock and install paths are appropriately framed with respect to the
|
/// 2. Ensures that the lock and install paths are appropriately framed with respect to the
|
||||||
/// current [`Workspace`].
|
/// current [`Workspace`].
|
||||||
/// 3. Removes the `origin` field, which is only used in `requirements.txt`.
|
/// 3. Removes the `origin` field, which is only used in `requirements.txt`.
|
||||||
|
/// 4. Simplifies the markers using the provided [`RequiresPython`] instance.
|
||||||
fn normalize_requirement(
|
fn normalize_requirement(
|
||||||
mut requirement: Requirement,
|
mut requirement: Requirement,
|
||||||
root: &Path,
|
root: &Path,
|
||||||
|
requires_python: &RequiresPython,
|
||||||
) -> Result<Requirement, LockError> {
|
) -> Result<Requirement, LockError> {
|
||||||
// Sort the extras and groups for consistency.
|
// Sort the extras and groups for consistency.
|
||||||
requirement.extras.sort();
|
requirement.extras.sort();
|
||||||
|
|
@ -4585,7 +4626,7 @@ fn normalize_requirement(
|
||||||
name: requirement.name,
|
name: requirement.name,
|
||||||
extras: requirement.extras,
|
extras: requirement.extras,
|
||||||
groups: requirement.groups,
|
groups: requirement.groups,
|
||||||
marker: requirement.marker,
|
marker: requires_python.simplify_markers(requirement.marker),
|
||||||
source: RequirementSource::Git {
|
source: RequirementSource::Git {
|
||||||
git,
|
git,
|
||||||
subdirectory,
|
subdirectory,
|
||||||
|
|
@ -4608,7 +4649,7 @@ fn normalize_requirement(
|
||||||
name: requirement.name,
|
name: requirement.name,
|
||||||
extras: requirement.extras,
|
extras: requirement.extras,
|
||||||
groups: requirement.groups,
|
groups: requirement.groups,
|
||||||
marker: requirement.marker,
|
marker: requires_python.simplify_markers(requirement.marker),
|
||||||
source: RequirementSource::Path {
|
source: RequirementSource::Path {
|
||||||
install_path,
|
install_path,
|
||||||
ext,
|
ext,
|
||||||
|
|
@ -4632,7 +4673,7 @@ fn normalize_requirement(
|
||||||
name: requirement.name,
|
name: requirement.name,
|
||||||
extras: requirement.extras,
|
extras: requirement.extras,
|
||||||
groups: requirement.groups,
|
groups: requirement.groups,
|
||||||
marker: requirement.marker,
|
marker: requires_python.simplify_markers(requirement.marker),
|
||||||
source: RequirementSource::Directory {
|
source: RequirementSource::Directory {
|
||||||
install_path,
|
install_path,
|
||||||
editable,
|
editable,
|
||||||
|
|
@ -4659,7 +4700,7 @@ fn normalize_requirement(
|
||||||
name: requirement.name,
|
name: requirement.name,
|
||||||
extras: requirement.extras,
|
extras: requirement.extras,
|
||||||
groups: requirement.groups,
|
groups: requirement.groups,
|
||||||
marker: requirement.marker,
|
marker: requires_python.simplify_markers(requirement.marker),
|
||||||
source: RequirementSource::Registry {
|
source: RequirementSource::Registry {
|
||||||
specifier,
|
specifier,
|
||||||
index,
|
index,
|
||||||
|
|
@ -4691,7 +4732,7 @@ fn normalize_requirement(
|
||||||
name: requirement.name,
|
name: requirement.name,
|
||||||
extras: requirement.extras,
|
extras: requirement.extras,
|
||||||
groups: requirement.groups,
|
groups: requirement.groups,
|
||||||
marker: requirement.marker,
|
marker: requires_python.simplify_markers(requirement.marker),
|
||||||
source: RequirementSource::Url {
|
source: RequirementSource::Url {
|
||||||
location,
|
location,
|
||||||
subdirectory,
|
subdirectory,
|
||||||
|
|
|
||||||
|
|
@ -11767,6 +11767,89 @@ fn unconditional_overlapping_marker_disjoint_version_constraints() -> Result<()>
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This checks that markers that normalize to 'false', which are serialized
|
||||||
|
/// to the lockfile as `python_full_version < '0'`, get read back as false.
|
||||||
|
/// Otherwise `uv lock --check` will always fail.
|
||||||
|
#[test]
|
||||||
|
fn normalize_false_marker_dependency_groups() -> 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.11"
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"pytest;python_full_version>'3.8' and python_full_version<'3.6'"
|
||||||
|
]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 1 package in [TIME]
|
||||||
|
");
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.lock().arg("--check"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 1 package in [TIME]
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This checks that markers that normalize to 'false', which are serialized
|
||||||
|
/// to the lockfile as `python_full_version < '0'`, get read back as false.
|
||||||
|
/// Otherwise `uv lock --check` will always fail.
|
||||||
|
#[test]
|
||||||
|
fn normalize_false_marker_requires_dist() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(
|
||||||
|
r#"
|
||||||
|
[project]
|
||||||
|
name = "debug"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = [
|
||||||
|
"pytest; python_full_version>'3.8' and python_full_version<'3.6'"
|
||||||
|
]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 1 package in [TIME]
|
||||||
|
");
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.lock().arg("--check"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 1 package in [TIME]
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Change indexes between locking operations.
|
/// Change indexes between locking operations.
|
||||||
#[test]
|
#[test]
|
||||||
fn lock_change_index() -> Result<()> {
|
fn lock_change_index() -> Result<()> {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue