mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Add a diagnostic trait (#3777)
This commit is contained in:
parent
9db6852f64
commit
79fecdf251
11 changed files with 60 additions and 44 deletions
9
crates/distribution-types/src/diagnostic.rs
Normal file
9
crates/distribution-types/src/diagnostic.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
use uv_normalize::PackageName;
|
||||
|
||||
pub trait Diagnostic {
|
||||
/// Convert the diagnostic into a user-facing message.
|
||||
fn message(&self) -> String;
|
||||
|
||||
/// Returns `true` if the [`PackageName`] is involved in this diagnostic.
|
||||
fn includes(&self, name: &PackageName) -> bool;
|
||||
}
|
|
@ -49,6 +49,7 @@ pub use crate::annotation::*;
|
|||
pub use crate::any::*;
|
||||
pub use crate::buildable::*;
|
||||
pub use crate::cached::*;
|
||||
pub use crate::diagnostic::*;
|
||||
pub use crate::editable::*;
|
||||
pub use crate::error::*;
|
||||
pub use crate::file::*;
|
||||
|
@ -68,6 +69,7 @@ mod annotation;
|
|||
mod any;
|
||||
mod buildable;
|
||||
mod cached;
|
||||
mod diagnostic;
|
||||
mod editable;
|
||||
mod error;
|
||||
mod file;
|
||||
|
|
|
@ -4,22 +4,22 @@ use pep508_rs::VerbatimUrl;
|
|||
use uv_normalize::{ExtraName, PackageName};
|
||||
|
||||
use crate::{
|
||||
BuiltDist, DirectorySourceDist, Dist, InstalledDirectUrlDist, InstalledDist, LocalEditable,
|
||||
Name, Requirement, RequirementSource, ResolvedDist, SourceDist,
|
||||
BuiltDist, Diagnostic, DirectorySourceDist, Dist, InstalledDirectUrlDist, InstalledDist,
|
||||
LocalEditable, Name, Requirement, RequirementSource, ResolvedDist, SourceDist,
|
||||
};
|
||||
|
||||
/// A set of packages pinned at specific versions.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Resolution {
|
||||
packages: BTreeMap<PackageName, ResolvedDist>,
|
||||
diagnostics: Vec<Diagnostic>,
|
||||
diagnostics: Vec<ResolutionDiagnostic>,
|
||||
}
|
||||
|
||||
impl Resolution {
|
||||
/// Create a new resolution from the given pinned packages.
|
||||
pub fn new(
|
||||
packages: BTreeMap<PackageName, ResolvedDist>,
|
||||
diagnostics: Vec<Diagnostic>,
|
||||
diagnostics: Vec<ResolutionDiagnostic>,
|
||||
) -> Self {
|
||||
Self {
|
||||
packages,
|
||||
|
@ -63,8 +63,8 @@ impl Resolution {
|
|||
self.packages.values().map(Requirement::from)
|
||||
}
|
||||
|
||||
/// Return the [`Diagnostic`]s that were produced during resolution.
|
||||
pub fn diagnostics(&self) -> &[Diagnostic] {
|
||||
/// Return the [`ResolutionDiagnostic`]s that were produced during resolution.
|
||||
pub fn diagnostics(&self) -> &[ResolutionDiagnostic] {
|
||||
&self.diagnostics
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ impl Resolution {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Diagnostic {
|
||||
pub enum ResolutionDiagnostic {
|
||||
MissingExtra {
|
||||
/// The distribution that was requested with a non-existent extra. For example,
|
||||
/// `black==23.10.0`.
|
||||
|
@ -115,9 +115,9 @@ pub enum Diagnostic {
|
|||
},
|
||||
}
|
||||
|
||||
impl Diagnostic {
|
||||
impl Diagnostic for ResolutionDiagnostic {
|
||||
/// Convert the diagnostic into a user-facing message.
|
||||
pub fn message(&self) -> String {
|
||||
fn message(&self) -> String {
|
||||
match self {
|
||||
Self::MissingExtra { dist, extra } => {
|
||||
format!("The package `{dist}` does not have an extra named `{extra}`.")
|
||||
|
@ -133,7 +133,7 @@ impl Diagnostic {
|
|||
}
|
||||
|
||||
/// Returns `true` if the [`PackageName`] is involved in this diagnostic.
|
||||
pub fn includes(&self, name: &PackageName) -> bool {
|
||||
fn includes(&self, name: &PackageName) -> bool {
|
||||
match self {
|
||||
Self::MissingExtra { dist, .. } => name == dist.name(),
|
||||
Self::YankedVersion { dist, .. } => name == dist.name(),
|
||||
|
|
|
@ -3,7 +3,7 @@ pub use downloader::{Downloader, Reporter as DownloadReporter};
|
|||
pub use editable::{is_dynamic, BuiltEditable, InstalledEditable, ResolvedEditable};
|
||||
pub use installer::{Installer, Reporter as InstallReporter};
|
||||
pub use plan::{Plan, Planner};
|
||||
pub use site_packages::{Diagnostic, SatisfiesResult, SitePackages};
|
||||
pub use site_packages::{SatisfiesResult, SitePackages, SitePackagesDiagnostic};
|
||||
pub use uninstall::{uninstall, UninstallError};
|
||||
|
||||
mod compile;
|
||||
|
|
|
@ -8,7 +8,8 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
|||
use url::Url;
|
||||
|
||||
use distribution_types::{
|
||||
InstalledDist, Name, Requirement, UnresolvedRequirement, UnresolvedRequirementSpecification,
|
||||
Diagnostic, InstalledDist, Name, Requirement, UnresolvedRequirement,
|
||||
UnresolvedRequirementSpecification,
|
||||
};
|
||||
use pep440_rs::{Version, VersionSpecifiers};
|
||||
use requirements_txt::EditableRequirement;
|
||||
|
@ -188,7 +189,7 @@ impl SitePackages {
|
|||
}
|
||||
|
||||
/// Validate the installed packages in the virtual environment.
|
||||
pub fn diagnostics(&self) -> Result<Vec<Diagnostic>> {
|
||||
pub fn diagnostics(&self) -> Result<Vec<SitePackagesDiagnostic>> {
|
||||
let mut diagnostics = Vec::new();
|
||||
|
||||
for (package, indexes) in &self.by_name {
|
||||
|
@ -201,7 +202,7 @@ impl SitePackages {
|
|||
|
||||
if let Some(conflict) = distributions.next() {
|
||||
// There are multiple installed distributions for the same package.
|
||||
diagnostics.push(Diagnostic::DuplicatePackage {
|
||||
diagnostics.push(SitePackagesDiagnostic::DuplicatePackage {
|
||||
package: package.clone(),
|
||||
paths: std::iter::once(distribution.path().to_owned())
|
||||
.chain(std::iter::once(conflict.path().to_owned()))
|
||||
|
@ -218,7 +219,7 @@ impl SitePackages {
|
|||
|
||||
// Determine the dependencies for the given package.
|
||||
let Ok(metadata) = distribution.metadata() else {
|
||||
diagnostics.push(Diagnostic::IncompletePackage {
|
||||
diagnostics.push(SitePackagesDiagnostic::IncompletePackage {
|
||||
package: package.clone(),
|
||||
path: distribution.path().to_owned(),
|
||||
});
|
||||
|
@ -228,7 +229,7 @@ impl SitePackages {
|
|||
// Verify that the package is compatible with the current Python version.
|
||||
if let Some(requires_python) = metadata.requires_python.as_ref() {
|
||||
if !requires_python.contains(self.venv.interpreter().python_version()) {
|
||||
diagnostics.push(Diagnostic::IncompatiblePythonVersion {
|
||||
diagnostics.push(SitePackagesDiagnostic::IncompatiblePythonVersion {
|
||||
package: package.clone(),
|
||||
version: self.venv.interpreter().python_version().clone(),
|
||||
requires_python: requires_python.clone(),
|
||||
|
@ -246,7 +247,7 @@ impl SitePackages {
|
|||
match installed.as_slice() {
|
||||
[] => {
|
||||
// No version installed.
|
||||
diagnostics.push(Diagnostic::MissingDependency {
|
||||
diagnostics.push(SitePackagesDiagnostic::MissingDependency {
|
||||
package: package.clone(),
|
||||
requirement: dependency.clone(),
|
||||
});
|
||||
|
@ -261,11 +262,13 @@ impl SitePackages {
|
|||
)) => {
|
||||
// The installed version doesn't satisfy the requirement.
|
||||
if !version_specifier.contains(installed.version()) {
|
||||
diagnostics.push(Diagnostic::IncompatibleDependency {
|
||||
package: package.clone(),
|
||||
version: installed.version().clone(),
|
||||
requirement: dependency.clone(),
|
||||
});
|
||||
diagnostics.push(
|
||||
SitePackagesDiagnostic::IncompatibleDependency {
|
||||
package: package.clone(),
|
||||
version: installed.version().clone(),
|
||||
requirement: dependency.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -449,7 +452,7 @@ impl IntoIterator for SitePackages {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Diagnostic {
|
||||
pub enum SitePackagesDiagnostic {
|
||||
IncompletePackage {
|
||||
/// The package that is missing metadata.
|
||||
package: PackageName,
|
||||
|
@ -486,9 +489,9 @@ pub enum Diagnostic {
|
|||
},
|
||||
}
|
||||
|
||||
impl Diagnostic {
|
||||
impl Diagnostic for SitePackagesDiagnostic {
|
||||
/// Convert the diagnostic into a user-facing message.
|
||||
pub fn message(&self) -> String {
|
||||
fn message(&self) -> String {
|
||||
match self {
|
||||
Self::IncompletePackage { package, path } => format!(
|
||||
"The package `{package}` is broken or incomplete (unable to read `METADATA`). Consider recreating the virtualenv, or removing the package directory at: {}.", path.display(),
|
||||
|
@ -525,7 +528,7 @@ impl Diagnostic {
|
|||
}
|
||||
|
||||
/// Returns `true` if the [`PackageName`] is involved in this diagnostic.
|
||||
pub fn includes(&self, name: &PackageName) -> bool {
|
||||
fn includes(&self, name: &PackageName) -> bool {
|
||||
match self {
|
||||
Self::IncompletePackage { package, .. } => name == package,
|
||||
Self::IncompatiblePythonVersion { package, .. } => name == package,
|
||||
|
|
|
@ -7,8 +7,8 @@ use pubgrub::type_aliases::SelectedDependencies;
|
|||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use distribution_types::{
|
||||
Diagnostic, Dist, DistributionMetadata, Name, ParsedUrlError, Requirement, ResolvedDist,
|
||||
VersionId, VersionOrUrlRef,
|
||||
Dist, DistributionMetadata, Name, ParsedUrlError, Requirement, ResolutionDiagnostic,
|
||||
ResolvedDist, VersionId, VersionOrUrlRef,
|
||||
};
|
||||
use pep440_rs::{Version, VersionSpecifier};
|
||||
use pep508_rs::MarkerEnvironment;
|
||||
|
@ -37,7 +37,7 @@ pub struct ResolutionGraph {
|
|||
/// The set of editable requirements in this resolution.
|
||||
pub(crate) editables: Editables,
|
||||
/// Any diagnostics that were encountered while building the graph.
|
||||
pub(crate) diagnostics: Vec<Diagnostic>,
|
||||
pub(crate) diagnostics: Vec<ResolutionDiagnostic>,
|
||||
}
|
||||
|
||||
impl ResolutionGraph {
|
||||
|
@ -90,7 +90,7 @@ impl ResolutionGraph {
|
|||
.unwrap_or_else(|| panic!("Every package should be pinned: {name:?}"))
|
||||
.clone();
|
||||
|
||||
diagnostics.push(Diagnostic::MissingExtra {
|
||||
diagnostics.push(ResolutionDiagnostic::MissingExtra {
|
||||
dist,
|
||||
extra: extra.clone(),
|
||||
});
|
||||
|
@ -111,7 +111,7 @@ impl ResolutionGraph {
|
|||
} else {
|
||||
let dist = Dist::from_editable(name.clone(), editable.built.clone())?;
|
||||
|
||||
diagnostics.push(Diagnostic::MissingExtra {
|
||||
diagnostics.push(ResolutionDiagnostic::MissingExtra {
|
||||
dist: dist.into(),
|
||||
extra: extra.clone(),
|
||||
});
|
||||
|
@ -141,7 +141,7 @@ impl ResolutionGraph {
|
|||
} else {
|
||||
let dist = Dist::from_url(name.clone(), url_to_precise(url.clone()))?;
|
||||
|
||||
diagnostics.push(Diagnostic::MissingExtra {
|
||||
diagnostics.push(ResolutionDiagnostic::MissingExtra {
|
||||
dist: dist.into(),
|
||||
extra: extra.clone(),
|
||||
});
|
||||
|
@ -177,13 +177,13 @@ impl ResolutionGraph {
|
|||
match dist.yanked() {
|
||||
None | Some(Yanked::Bool(false)) => {}
|
||||
Some(Yanked::Bool(true)) => {
|
||||
diagnostics.push(Diagnostic::YankedVersion {
|
||||
diagnostics.push(ResolutionDiagnostic::YankedVersion {
|
||||
dist: dist.clone(),
|
||||
reason: None,
|
||||
});
|
||||
}
|
||||
Some(Yanked::Reason(reason)) => {
|
||||
diagnostics.push(Diagnostic::YankedVersion {
|
||||
diagnostics.push(ResolutionDiagnostic::YankedVersion {
|
||||
dist: dist.clone(),
|
||||
reason: Some(reason.clone()),
|
||||
});
|
||||
|
@ -411,8 +411,8 @@ impl ResolutionGraph {
|
|||
.map(|node| node.weight.dist)
|
||||
}
|
||||
|
||||
/// Return the [`Diagnostic`]s that were encountered while building the graph.
|
||||
pub fn diagnostics(&self) -> &[Diagnostic] {
|
||||
/// Return the [`ResolutionDiagnostic`]s that were encountered while building the graph.
|
||||
pub fn diagnostics(&self) -> &[ResolutionDiagnostic] {
|
||||
&self.diagnostics
|
||||
}
|
||||
|
||||
|
|
|
@ -5,10 +5,10 @@ use anyhow::Result;
|
|||
use owo_colors::OwoColorize;
|
||||
use tracing::debug;
|
||||
|
||||
use distribution_types::InstalledDist;
|
||||
use distribution_types::{Diagnostic, InstalledDist};
|
||||
use uv_cache::Cache;
|
||||
use uv_fs::Simplified;
|
||||
use uv_installer::{Diagnostic, SitePackages};
|
||||
use uv_installer::{SitePackages, SitePackagesDiagnostic};
|
||||
use uv_interpreter::{PythonEnvironment, SystemPython};
|
||||
|
||||
use crate::commands::{elapsed, ExitStatus};
|
||||
|
@ -53,7 +53,8 @@ pub(crate) fn pip_check(
|
|||
.dimmed()
|
||||
)?;
|
||||
|
||||
let diagnostics: Vec<Diagnostic> = site_packages.diagnostics()?.into_iter().collect();
|
||||
let diagnostics: Vec<SitePackagesDiagnostic> =
|
||||
site_packages.diagnostics()?.into_iter().collect();
|
||||
|
||||
if diagnostics.is_empty() {
|
||||
writeln!(
|
||||
|
|
|
@ -5,7 +5,7 @@ use itertools::Itertools;
|
|||
use owo_colors::OwoColorize;
|
||||
use tracing::debug;
|
||||
|
||||
use distribution_types::{InstalledDist, Name};
|
||||
use distribution_types::{Diagnostic, InstalledDist, Name};
|
||||
use uv_cache::Cache;
|
||||
use uv_fs::Simplified;
|
||||
use uv_installer::SitePackages;
|
||||
|
|
|
@ -8,7 +8,7 @@ use serde::Serialize;
|
|||
use tracing::debug;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use distribution_types::{InstalledDist, Name};
|
||||
use distribution_types::{Diagnostic, InstalledDist, Name};
|
||||
use uv_cache::Cache;
|
||||
use uv_fs::Simplified;
|
||||
use uv_installer::SitePackages;
|
||||
|
|
|
@ -9,7 +9,8 @@ use owo_colors::OwoColorize;
|
|||
use tracing::debug;
|
||||
|
||||
use distribution_types::{
|
||||
CachedDist, Diagnostic, InstalledDist, Requirement, UnresolvedRequirementSpecification,
|
||||
CachedDist, Diagnostic, InstalledDist, Requirement, ResolutionDiagnostic,
|
||||
UnresolvedRequirementSpecification,
|
||||
};
|
||||
use distribution_types::{
|
||||
DistributionMetadata, IndexLocations, InstalledMetadata, InstalledVersion, LocalDist, Name,
|
||||
|
@ -708,7 +709,7 @@ pub(crate) fn report_modifications(
|
|||
|
||||
/// Report any diagnostics on resolved distributions.
|
||||
pub(crate) fn diagnose_resolution(
|
||||
diagnostics: &[Diagnostic],
|
||||
diagnostics: &[ResolutionDiagnostic],
|
||||
printer: Printer,
|
||||
) -> Result<(), Error> {
|
||||
for diagnostic in diagnostics {
|
||||
|
|
|
@ -6,7 +6,7 @@ use owo_colors::OwoColorize;
|
|||
use rustc_hash::FxHashMap;
|
||||
use tracing::debug;
|
||||
|
||||
use distribution_types::Name;
|
||||
use distribution_types::{Diagnostic, Name};
|
||||
use uv_cache::Cache;
|
||||
use uv_fs::Simplified;
|
||||
use uv_installer::SitePackages;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue