mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-20 03:49:54 +00:00
Don't panic in uv export --frozen when the lockfile is outdated (#16407)
Provide a good error message when the discovered workspace members mismatch with the locked workspace members in `uv export --frozen`, instead of panicking. Fixes #16406
This commit is contained in:
parent
940a3f63ce
commit
491293362f
7 changed files with 71 additions and 8 deletions
|
|
@ -16,11 +16,12 @@ use uv_pep508::MarkerTree;
|
||||||
use uv_pypi_types::ConflictItem;
|
use uv_pypi_types::ConflictItem;
|
||||||
|
|
||||||
use crate::graph_ops::{Reachable, marker_reachability};
|
use crate::graph_ops::{Reachable, marker_reachability};
|
||||||
|
use crate::lock::LockErrorKind;
|
||||||
pub(crate) use crate::lock::export::pylock_toml::PylockTomlPackage;
|
pub(crate) use crate::lock::export::pylock_toml::PylockTomlPackage;
|
||||||
pub use crate::lock::export::pylock_toml::{PylockToml, PylockTomlErrorKind};
|
pub use crate::lock::export::pylock_toml::{PylockToml, PylockTomlErrorKind};
|
||||||
pub use crate::lock::export::requirements_txt::RequirementsTxtExport;
|
pub use crate::lock::export::requirements_txt::RequirementsTxtExport;
|
||||||
use crate::universal_marker::resolve_conflicts;
|
use crate::universal_marker::resolve_conflicts;
|
||||||
use crate::{Installable, Package};
|
use crate::{Installable, LockError, Package};
|
||||||
|
|
||||||
mod pylock_toml;
|
mod pylock_toml;
|
||||||
mod requirements_txt;
|
mod requirements_txt;
|
||||||
|
|
@ -49,7 +50,7 @@ impl<'lock> ExportableRequirements<'lock> {
|
||||||
groups: &DependencyGroupsWithDefaults,
|
groups: &DependencyGroupsWithDefaults,
|
||||||
annotate: bool,
|
annotate: bool,
|
||||||
install_options: &'lock InstallOptions,
|
install_options: &'lock InstallOptions,
|
||||||
) -> Self {
|
) -> Result<Self, LockError> {
|
||||||
let size_guess = target.lock().packages.len();
|
let size_guess = target.lock().packages.len();
|
||||||
let mut graph = Graph::<Node<'lock>, Edge<'lock>>::with_capacity(size_guess, size_guess);
|
let mut graph = Graph::<Node<'lock>, Edge<'lock>>::with_capacity(size_guess, size_guess);
|
||||||
let mut inverse = FxHashMap::with_capacity_and_hasher(size_guess, FxBuildHasher);
|
let mut inverse = FxHashMap::with_capacity_and_hasher(size_guess, FxBuildHasher);
|
||||||
|
|
@ -73,8 +74,12 @@ impl<'lock> ExportableRequirements<'lock> {
|
||||||
let dist = target
|
let dist = target
|
||||||
.lock()
|
.lock()
|
||||||
.find_by_name(root_name)
|
.find_by_name(root_name)
|
||||||
.expect("found too many packages matching root")
|
.map_err(|_| LockErrorKind::MultipleRootPackages {
|
||||||
.expect("could not find root");
|
name: root_name.clone(),
|
||||||
|
})?
|
||||||
|
.ok_or_else(|| LockErrorKind::MissingRootPackage {
|
||||||
|
name: root_name.clone(),
|
||||||
|
})?;
|
||||||
|
|
||||||
if groups.prod() {
|
if groups.prod() {
|
||||||
// Add the workspace package to the graph.
|
// Add the workspace package to the graph.
|
||||||
|
|
@ -330,7 +335,7 @@ impl<'lock> ExportableRequirements<'lock> {
|
||||||
.filter(|requirement| !requirement.marker.is_false())
|
.filter(|requirement| !requirement.marker.is_false())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
Self(nodes)
|
Ok(Self(nodes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -631,7 +631,7 @@ impl<'lock> PylockToml {
|
||||||
dev,
|
dev,
|
||||||
annotate,
|
annotate,
|
||||||
install_options,
|
install_options,
|
||||||
);
|
)?;
|
||||||
|
|
||||||
// Sort the nodes.
|
// Sort the nodes.
|
||||||
nodes.sort_unstable_by_key(|node| &node.package.id);
|
nodes.sort_unstable_by_key(|node| &node.package.id);
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ impl<'lock> RequirementsTxtExport<'lock> {
|
||||||
dev,
|
dev,
|
||||||
annotate,
|
annotate,
|
||||||
install_options,
|
install_options,
|
||||||
);
|
)?;
|
||||||
|
|
||||||
// Sort the nodes, such that unnamed URLs (editables) appear at the top.
|
// Sort the nodes, such that unnamed URLs (editables) appear at the top.
|
||||||
nodes.sort_unstable_by(|a, b| {
|
nodes.sort_unstable_by(|a, b| {
|
||||||
|
|
|
||||||
|
|
@ -1269,7 +1269,7 @@ impl Lock {
|
||||||
/// Returns the package with the given name. If there are multiple
|
/// Returns the package with the given name. If there are multiple
|
||||||
/// matching packages, then an error is returned. If there are no
|
/// matching packages, then an error is returned. If there are no
|
||||||
/// matching packages, then `Ok(None)` is returned.
|
/// matching packages, then `Ok(None)` is returned.
|
||||||
fn find_by_name(&self, name: &PackageName) -> Result<Option<&Package>, String> {
|
pub fn find_by_name(&self, name: &PackageName) -> Result<Option<&Package>, String> {
|
||||||
let mut found_dist = None;
|
let mut found_dist = None;
|
||||||
for dist in &self.packages {
|
for dist in &self.packages {
|
||||||
if &dist.id.name == name {
|
if &dist.id.name == name {
|
||||||
|
|
|
||||||
|
|
@ -334,6 +334,17 @@ impl<'env> LockOperation<'env> {
|
||||||
.read()
|
.read()
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| ProjectError::MissingLockfile)?;
|
.ok_or_else(|| ProjectError::MissingLockfile)?;
|
||||||
|
// Check if the discovered workspace members match the locked workspace members.
|
||||||
|
if let LockTarget::Workspace(workspace) = target {
|
||||||
|
for package_name in workspace.packages().keys() {
|
||||||
|
existing
|
||||||
|
.find_by_name(package_name)
|
||||||
|
.map_err(|_| ProjectError::LockWorkspaceMismatch(package_name.clone()))?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
ProjectError::LockWorkspaceMismatch(package_name.clone())
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(LockResult::Unchanged(existing))
|
Ok(LockResult::Unchanged(existing))
|
||||||
}
|
}
|
||||||
LockMode::Locked(interpreter, lock_source) => {
|
LockMode::Locked(interpreter, lock_source) => {
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,11 @@ pub(crate) enum ProjectError {
|
||||||
)]
|
)]
|
||||||
MissingLockfile,
|
MissingLockfile,
|
||||||
|
|
||||||
|
#[error(
|
||||||
|
"The lockfile at `uv.lock` needs to be updated, but `--frozen` was provided: Missing workspace member `{0}`. To update the lockfile, run `uv lock`."
|
||||||
|
)]
|
||||||
|
LockWorkspaceMismatch(PackageName),
|
||||||
|
|
||||||
#[error(
|
#[error(
|
||||||
"The lockfile at `uv.lock` uses an unsupported schema version (v{1}, but only v{0} is supported). Downgrade to a compatible uv version, or remove the `uv.lock` prior to running `uv lock` or `uv sync`."
|
"The lockfile at `uv.lock` uses an unsupported schema version (v{1}, but only v{0} is supported). Downgrade to a compatible uv version, or remove the `uv.lock` prior to running `uv lock` or `uv sync`."
|
||||||
)]
|
)]
|
||||||
|
|
|
||||||
|
|
@ -4618,3 +4618,45 @@ fn export_only_group_and_extra_conflict() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The members in the workspace (`foo`) and in the lockfile (`bar`) mismatch.
|
||||||
|
#[test]
|
||||||
|
fn export_lock_workspace_mismatch_with_frozen() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(
|
||||||
|
r#"
|
||||||
|
[project]
|
||||||
|
name = "foo"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let pyproject_toml = context.temp_dir.child("uv.lock");
|
||||||
|
pyproject_toml.write_str(
|
||||||
|
r#"
|
||||||
|
version = 1
|
||||||
|
revision = 3
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bar"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
dependencies = []
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.export().arg("--frozen"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: The lockfile at `uv.lock` needs to be updated, but `--frozen` was provided: Missing workspace member `foo`. To update the lockfile, run `uv lock`.
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue