mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-02 12:59:45 +00:00
Avoid persisting uv add calls that result in resolver errors (#5664)
## Summary Closes https://github.com/astral-sh/uv/issues/5622.
This commit is contained in:
parent
54398fa7bc
commit
4b8a127c54
4 changed files with 92 additions and 5 deletions
|
|
@ -1,5 +1,5 @@
|
|||
pub use dependency_mode::DependencyMode;
|
||||
pub use error::ResolveError;
|
||||
pub use error::{NoSolutionError, ResolveError};
|
||||
pub use exclude_newer::ExcludeNewer;
|
||||
pub use exclusions::Exclusions;
|
||||
pub use flat_index::FlatIndex;
|
||||
|
|
|
|||
|
|
@ -50,6 +50,12 @@ impl PartialEq for PyProjectToml {
|
|||
|
||||
impl Eq for PyProjectToml {}
|
||||
|
||||
impl AsRef<[u8]> for PyProjectToml {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.raw.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
/// PEP 621 project metadata (`project`).
|
||||
///
|
||||
/// See <https://packaging.python.org/en/latest/specifications/pyproject-toml>.
|
||||
|
|
|
|||
|
|
@ -20,8 +20,9 @@ use uv_workspace::{DiscoveryOptions, ProjectWorkspace, VirtualProject, Workspace
|
|||
|
||||
use crate::commands::pip::operations::Modifications;
|
||||
use crate::commands::pip::resolution_environment;
|
||||
use crate::commands::project::ProjectError;
|
||||
use crate::commands::reporters::ResolverReporter;
|
||||
use crate::commands::{project, ExitStatus, SharedState};
|
||||
use crate::commands::{pip, project, ExitStatus, SharedState};
|
||||
use crate::printer::Printer;
|
||||
use crate::settings::ResolverInstallerSettings;
|
||||
|
||||
|
|
@ -154,7 +155,8 @@ pub(crate) async fn add(
|
|||
.await?;
|
||||
|
||||
// Add the requirements to the `pyproject.toml`.
|
||||
let mut pyproject = PyProjectTomlMut::from_toml(project.current_project().pyproject_toml())?;
|
||||
let existing = project.current_project().pyproject_toml();
|
||||
let mut pyproject = PyProjectTomlMut::from_toml(existing)?;
|
||||
for mut req in requirements {
|
||||
// Add the specified extras.
|
||||
req.extras.extend(extras.iter().cloned());
|
||||
|
|
@ -221,7 +223,7 @@ pub(crate) async fn add(
|
|||
let state = SharedState::default();
|
||||
|
||||
// Lock and sync the environment, if necessary.
|
||||
let lock = project::lock::do_safe_lock(
|
||||
let lock = match project::lock::do_safe_lock(
|
||||
locked,
|
||||
frozen,
|
||||
project.workspace(),
|
||||
|
|
@ -235,7 +237,26 @@ pub(crate) async fn add(
|
|||
cache,
|
||||
printer,
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
{
|
||||
Ok(lock) => lock,
|
||||
Err(ProjectError::Operation(pip::operations::Error::Resolve(
|
||||
uv_resolver::ResolveError::NoSolution(err),
|
||||
))) => {
|
||||
let header = err.header();
|
||||
let report = miette::Report::new(WithHelp { header, cause: err, help: Some("If this is intentional, run `uv add --frozen` to skip the lock and sync steps.") });
|
||||
anstream::eprint!("{report:?}");
|
||||
|
||||
// Revert the changes to the `pyproject.toml`.
|
||||
fs_err::write(
|
||||
project.current_project().root().join("pyproject.toml"),
|
||||
existing,
|
||||
)?;
|
||||
|
||||
return Ok(ExitStatus::Failure);
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
// Perform a full sync, because we don't know what exactly is affected by the removal.
|
||||
// TODO(ibraheem): Should we accept CLI overrides for this? Should we even sync here?
|
||||
|
|
@ -262,3 +283,20 @@ pub(crate) async fn add(
|
|||
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
||||
/// Render a [`uv_resolver::NoSolutionError`] with a help message.
|
||||
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||
#[error("{header}")]
|
||||
#[diagnostic()]
|
||||
struct WithHelp {
|
||||
/// The header to render in the error message.
|
||||
header: String,
|
||||
|
||||
/// The underlying error.
|
||||
#[source]
|
||||
cause: uv_resolver::NoSolutionError,
|
||||
|
||||
/// The help message to display.
|
||||
#[help]
|
||||
help: Option<&'static str>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2101,3 +2101,46 @@ fn add_reject_multiple_git_ref_flags() {
|
|||
"###
|
||||
);
|
||||
}
|
||||
|
||||
/// Avoiding persisting `add` calls when resolution fails.
|
||||
#[test]
|
||||
fn add_error() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
# ...
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.add(&["xyz"]), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv add` is experimental and may change without warning
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because there are no versions of xyz and project==0.1.0 depends on xyz, we can conclude that project==0.1.0 cannot be used.
|
||||
And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable.
|
||||
help: If this is intentional, run `uv add --frozen` to skip the lock and sync steps.
|
||||
"###);
|
||||
|
||||
uv_snapshot!(context.filters(), context.add(&["xyz"]).arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv add` is experimental and may change without warning
|
||||
"###);
|
||||
|
||||
let lock = context.temp_dir.join("uv.lock");
|
||||
assert!(!lock.exists());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue