Add a diagnostic trait (#3777)

This commit is contained in:
Charlie Marsh 2024-05-22 19:44:37 -04:00 committed by GitHub
parent 9db6852f64
commit 79fecdf251
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 60 additions and 44 deletions

View 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;
}

View file

@ -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;

View 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(),

View file

@ -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;

View file

@ -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,

View file

@ -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
}

View file

@ -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!(

View file

@ -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;

View file

@ -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;

View file

@ -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 {

View file

@ -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;