mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 10:58:28 +00:00
Invalidate lockfile when static versions change (#10858)
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 | 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 (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 (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (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 | macos (push) Blocked by required conditions
CI / smoke test | windows (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 linux (push) Blocked by required conditions
CI / integration test | free-threaded on windows (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 | github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | uv publish (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 opensuse (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (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 | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / check system | python 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 (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 (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on linux (push) Blocked by required conditions
CI / check system | conda3.8 on linux (push) Blocked by required conditions
CI / check system | conda3.11 on macos (push) Blocked by required conditions
CI / check system | conda3.8 on macos (push) Blocked by required conditions
CI / check system | conda3.11 on windows (push) Blocked by required conditions
CI / check system | conda3.8 on windows (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
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 | 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 (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 (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | freebsd (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 | macos (push) Blocked by required conditions
CI / smoke test | windows (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 linux (push) Blocked by required conditions
CI / integration test | free-threaded on windows (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 | github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | uv publish (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 opensuse (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (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 | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / check system | python 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 (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 (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on linux (push) Blocked by required conditions
CI / check system | conda3.8 on linux (push) Blocked by required conditions
CI / check system | conda3.11 on macos (push) Blocked by required conditions
CI / check system | conda3.8 on macos (push) Blocked by required conditions
CI / check system | conda3.11 on windows (push) Blocked by required conditions
CI / check system | conda3.8 on windows (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
## Summary We should only be ignoring changes in `version` for dynamic projects; for static projects, it should still be enforced. We should also be invalidating the lockfile if a project goes from static to dynamic or vice versa. Closes #10852.
This commit is contained in:
parent
434706389b
commit
f5447ce965
6 changed files with 541 additions and 99 deletions
|
@ -1067,32 +1067,17 @@ impl Lock {
|
|||
}
|
||||
}
|
||||
|
||||
// Validate that the member sources have not changed.
|
||||
{
|
||||
// E.g., that they've switched from virtual to non-virtual or vice versa.
|
||||
for (name, member) in packages {
|
||||
let expected = !member.pyproject_toml().is_package();
|
||||
let actual = self
|
||||
.find_by_name(name)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|package| matches!(package.id.source, Source::Virtual(_)));
|
||||
if actual != Some(expected) {
|
||||
return Ok(SatisfiesResult::MismatchedVirtual(name.clone(), expected));
|
||||
}
|
||||
}
|
||||
|
||||
// E.g., that they've switched from dynamic to non-dynamic or vice versa.
|
||||
for (name, member) in packages {
|
||||
let expected = member.pyproject_toml().is_dynamic();
|
||||
let actual = self
|
||||
.find_by_name(name)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(Package::is_dynamic);
|
||||
if actual != Some(expected) {
|
||||
return Ok(SatisfiesResult::MismatchedDynamic(name.clone(), expected));
|
||||
}
|
||||
// Validate that the member sources have not changed (e.g., that they've switched from
|
||||
// virtual to non-virtual or vice versa).
|
||||
for (name, member) in packages {
|
||||
let expected = !member.pyproject_toml().is_package();
|
||||
let actual = self
|
||||
.find_by_name(name)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|package| matches!(package.id.source, Source::Virtual(_)));
|
||||
if actual != Some(expected) {
|
||||
return Ok(SatisfiesResult::MismatchedVirtual(name.clone(), expected));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1287,60 +1272,10 @@ impl Lock {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Fetch the metadata for the distribution.
|
||||
//
|
||||
// If the distribution is a source tree, attempt to extract the requirements from the
|
||||
// `pyproject.toml` directly. The distribution database will do this too, but we can be
|
||||
// even more aggressive here since we _only_ need the requirements. So, for example,
|
||||
// even if the version is dynamic, we can still extract the requirements without
|
||||
// performing a build, unlike in the database where we typically construct a "complete"
|
||||
// metadata object.
|
||||
let metadata = if let Some(source_tree) = package.id.source.as_source_tree() {
|
||||
database
|
||||
.requires_dist(root.join(source_tree))
|
||||
.await
|
||||
.map_err(|err| LockErrorKind::Resolution {
|
||||
id: package.id.clone(),
|
||||
err,
|
||||
})?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let satisfied = metadata.is_some_and(|metadata| {
|
||||
match satisfies_requires_dist(metadata, package, root) {
|
||||
Ok(SatisfiesResult::Satisfied) => {
|
||||
debug!("Static `requires-dist` for `{}` is up-to-date", package.id);
|
||||
true
|
||||
},
|
||||
Ok(..) => {
|
||||
debug!("Static `requires-dist` for `{}` is out-of-date; falling back to distribution database", package.id);
|
||||
false
|
||||
},
|
||||
Err(..) => {
|
||||
debug!("Static `requires-dist` for `{}` is invalid; falling back to distribution database", package.id);
|
||||
false
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
// If the `requires-dist` metadata matches the requirements, we're done; otherwise,
|
||||
// fetch the "full" metadata, which may involve invoking the build system. In some
|
||||
// cases, build backends return metadata that does _not_ match the `pyproject.toml`
|
||||
// exactly. For example, `hatchling` will flatten any recursive (or self-referential)
|
||||
// extras, while `setuptools` will not.
|
||||
if !satisfied {
|
||||
// Get the metadata for the distribution.
|
||||
let dist = package.to_dist(
|
||||
root,
|
||||
// When validating, it's okay to use wheels that don't match the current platform.
|
||||
TagPolicy::Preferred(tags),
|
||||
// When validating, it's okay to use (e.g.) a source distribution with `--no-build`.
|
||||
// We're just trying to determine whether the lockfile is up-to-date. If we end
|
||||
// up needing to build a source distribution in order to do so, below, we'll error
|
||||
// there.
|
||||
&BuildOptions::default(),
|
||||
)?;
|
||||
if let Some(version) = package.id.version.as_ref() {
|
||||
// For a non-dynamic package, fetch the metadata from the distribution database.
|
||||
let dist =
|
||||
package.to_dist(root, TagPolicy::Preferred(tags), &BuildOptions::default())?;
|
||||
|
||||
let metadata = {
|
||||
let id = dist.version_id();
|
||||
|
@ -1380,10 +1315,139 @@ impl Lock {
|
|||
}
|
||||
};
|
||||
|
||||
// If this is a local package, validate that it hasn't become dynamic (in which
|
||||
// case, we'd expect the version to be omitted).
|
||||
if package.id.source.is_source_tree() {
|
||||
if metadata.dynamic {
|
||||
return Ok(SatisfiesResult::MismatchedDynamic(
|
||||
package.id.name.clone(),
|
||||
false,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the `version` metadata.
|
||||
if metadata.version != *version {
|
||||
return Ok(SatisfiesResult::MismatchedVersion(
|
||||
package.id.name.clone(),
|
||||
version.clone(),
|
||||
Some(metadata.version.clone()),
|
||||
));
|
||||
}
|
||||
|
||||
// Validate that the requirements are unchanged.
|
||||
match satisfies_requires_dist(RequiresDist::from(metadata), package, root)? {
|
||||
SatisfiesResult::Satisfied => {}
|
||||
result => return Ok(result),
|
||||
}
|
||||
} else if let Some(source_tree) = package.id.source.as_source_tree() {
|
||||
// For dynamic packages, we don't need the version. We only need to know that the
|
||||
// package is still dynamic, and that the requirements are unchanged.
|
||||
//
|
||||
// If the distribution is a source tree, attempt to extract the requirements from the
|
||||
// `pyproject.toml` directly. The distribution database will do this too, but we can be
|
||||
// even more aggressive here since we _only_ need the requirements. So, for example,
|
||||
// even if the version is dynamic, we can still extract the requirements without
|
||||
// performing a build, unlike in the database where we typically construct a "complete"
|
||||
// metadata object.
|
||||
let metadata = database
|
||||
.requires_dist(root.join(source_tree))
|
||||
.await
|
||||
.map_err(|err| LockErrorKind::Resolution {
|
||||
id: package.id.clone(),
|
||||
err,
|
||||
})?;
|
||||
|
||||
let satisfied = metadata.is_some_and(|metadata| {
|
||||
// Validate that the package is still dynamic.
|
||||
if !metadata.dynamic {
|
||||
debug!("Static `requires-dist` for `{}` is out-of-date; falling back to distribution database", package.id);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate that the requirements are unchanged.
|
||||
match satisfies_requires_dist(metadata, package, root) {
|
||||
Ok(SatisfiesResult::Satisfied) => {
|
||||
debug!("Static `requires-dist` for `{}` is up-to-date", package.id);
|
||||
true
|
||||
},
|
||||
Ok(..) => {
|
||||
debug!("Static `requires-dist` for `{}` is out-of-date; falling back to distribution database", package.id);
|
||||
false
|
||||
},
|
||||
Err(..) => {
|
||||
debug!("Static `requires-dist` for `{}` is invalid; falling back to distribution database", package.id);
|
||||
false
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
// If the `requires-dist` metadata matches the requirements, we're done; otherwise,
|
||||
// fetch the "full" metadata, which may involve invoking the build system. In some
|
||||
// cases, build backends return metadata that does _not_ match the `pyproject.toml`
|
||||
// exactly. For example, `hatchling` will flatten any recursive (or self-referential)
|
||||
// extras, while `setuptools` will not.
|
||||
if !satisfied {
|
||||
let dist = package.to_dist(
|
||||
root,
|
||||
TagPolicy::Preferred(tags),
|
||||
&BuildOptions::default(),
|
||||
)?;
|
||||
|
||||
let metadata = {
|
||||
let id = dist.version_id();
|
||||
if let Some(archive) =
|
||||
index
|
||||
.distributions()
|
||||
.get(&id)
|
||||
.as_deref()
|
||||
.and_then(|response| {
|
||||
if let MetadataResponse::Found(archive, ..) = response {
|
||||
Some(archive)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
{
|
||||
// If the metadata is already in the index, return it.
|
||||
archive.metadata.clone()
|
||||
} else {
|
||||
// Run the PEP 517 build process to extract metadata from the source distribution.
|
||||
let archive = database
|
||||
.get_or_build_wheel_metadata(&dist, hasher.get(&dist))
|
||||
.await
|
||||
.map_err(|err| LockErrorKind::Resolution {
|
||||
id: package.id.clone(),
|
||||
err,
|
||||
})?;
|
||||
|
||||
let metadata = archive.metadata.clone();
|
||||
|
||||
// Insert the metadata into the index.
|
||||
index
|
||||
.distributions()
|
||||
.done(id, Arc::new(MetadataResponse::Found(archive)));
|
||||
|
||||
metadata
|
||||
}
|
||||
};
|
||||
|
||||
// Validate that the package is still dynamic.
|
||||
if !metadata.dynamic {
|
||||
return Ok(SatisfiesResult::MismatchedDynamic(
|
||||
package.id.name.clone(),
|
||||
true,
|
||||
));
|
||||
}
|
||||
|
||||
// Validate that the requirements are unchanged.
|
||||
match satisfies_requires_dist(RequiresDist::from(metadata), package, root)? {
|
||||
SatisfiesResult::Satisfied => {}
|
||||
result => return Ok(result),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Ok(SatisfiesResult::MissingVersion(package.id.name.clone()));
|
||||
}
|
||||
|
||||
// Recurse.
|
||||
|
@ -1446,7 +1510,7 @@ pub enum SatisfiesResult<'lock> {
|
|||
MismatchedMembers(BTreeSet<PackageName>, &'lock BTreeSet<PackageName>),
|
||||
/// A workspace member switched from virtual to non-virtual or vice versa.
|
||||
MismatchedVirtual(PackageName, bool),
|
||||
/// A workspace member switched from dynamic to non-dynamic or vice versa.
|
||||
/// A source tree switched from dynamic to non-dynamic or vice versa.
|
||||
MismatchedDynamic(PackageName, bool),
|
||||
/// The lockfile uses a different set of version for its workspace members.
|
||||
MismatchedVersion(PackageName, Version, Option<Version>),
|
||||
|
@ -1483,6 +1547,8 @@ pub enum SatisfiesResult<'lock> {
|
|||
BTreeMap<GroupName, BTreeSet<Requirement>>,
|
||||
BTreeMap<GroupName, BTreeSet<Requirement>>,
|
||||
),
|
||||
/// The lockfile is missing a version.
|
||||
MissingVersion(PackageName),
|
||||
}
|
||||
|
||||
/// We discard the lockfile if these options match.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue