Skip to main content

uv_installer/
site_packages.rs

1use std::borrow::Cow;
2use std::collections::BTreeSet;
3use std::iter::Flatten;
4use std::path::PathBuf;
5
6use anyhow::{Context, Result};
7use fs_err as fs;
8use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
9
10use uv_distribution_types::{
11    ConfigSettings, DependencyMetadata, Diagnostic, ExtraBuildRequires, ExtraBuildVariables,
12    InstalledDist, InstalledDistKind, Name, NameRequirementSpecification, PackageConfigSettings,
13    Requirement, UnresolvedRequirement, UnresolvedRequirementSpecification,
14};
15use uv_fs::Simplified;
16use uv_normalize::PackageName;
17use uv_pep440::{Version, VersionSpecifiers};
18use uv_pep508::VersionOrUrl;
19use uv_platform_tags::Tags;
20use uv_pypi_types::{ResolverMarkerEnvironment, VerbatimParsedUrl};
21use uv_python::{Interpreter, PythonEnvironment};
22use uv_redacted::DisplaySafeUrl;
23use uv_types::InstalledPackagesProvider;
24use uv_warnings::warn_user;
25
26use crate::satisfies::RequirementSatisfaction;
27
28/// An index over the packages installed in an environment.
29///
30/// Packages are indexed by both name and (for editable installs) URL.
31#[derive(Debug, Clone)]
32pub struct SitePackages {
33    interpreter: Interpreter,
34    /// The vector of all installed distributions. The `by_name` and `by_url` indices index into
35    /// this vector. The vector may contain `None` values, which represent distributions that were
36    /// removed from the virtual environment.
37    distributions: Vec<Option<InstalledDist>>,
38    /// The installed distributions, keyed by name. Although the Python runtime does not support it,
39    /// it is possible to have multiple distributions with the same name to be present in the
40    /// virtual environment, which we handle gracefully.
41    by_name: FxHashMap<PackageName, Vec<usize>>,
42    /// The installed editable distributions, keyed by URL.
43    by_url: FxHashMap<DisplaySafeUrl, Vec<usize>>,
44}
45
46impl SitePackages {
47    /// Build an index of installed packages from the given Python environment.
48    pub fn from_environment(environment: &PythonEnvironment) -> Result<Self> {
49        Self::from_interpreter(environment.interpreter())
50    }
51
52    /// Build an index of installed packages from the given Python executable.
53    pub fn from_interpreter(interpreter: &Interpreter) -> Result<Self> {
54        let mut distributions: Vec<Option<InstalledDist>> = Vec::new();
55        let mut by_name = FxHashMap::default();
56        let mut by_url = FxHashMap::default();
57
58        for site_packages in interpreter.site_packages() {
59            // Read the site-packages directory.
60            let site_packages = match fs::read_dir(site_packages.as_ref()) {
61                Ok(read_dir) => {
62                    // Collect sorted directory paths; `read_dir` is not stable across platforms
63                    let dist_likes: BTreeSet<_> = read_dir
64                        .filter_map(|read_dir| match read_dir {
65                            Ok(entry) => match entry.file_type() {
66                                Ok(file_type) => (file_type.is_dir()
67                                    || entry
68                                        .path()
69                                        .extension()
70                                        .is_some_and(|ext| ext == "egg-link" || ext == "egg-info"))
71                                .then_some(Ok(entry.path())),
72                                Err(err) => Some(Err(err)),
73                            },
74                            Err(err) => Some(Err(err)),
75                        })
76                        .collect::<Result<_, std::io::Error>>()
77                        .with_context(|| {
78                            format!(
79                                "Failed to read site-packages directory contents: {}",
80                                site_packages.user_display()
81                            )
82                        })?;
83                    dist_likes
84                }
85                Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
86                    return Ok(Self {
87                        interpreter: interpreter.clone(),
88                        distributions,
89                        by_name,
90                        by_url,
91                    });
92                }
93                Err(err) => return Err(err).context("Failed to read site-packages directory"),
94            };
95
96            // Index all installed packages by name.
97            for path in site_packages {
98                let dist_info = match InstalledDist::try_from_path(&path) {
99                    Ok(Some(dist_info)) => dist_info,
100                    Ok(None) => continue,
101                    Err(_)
102                        if path.file_name().is_some_and(|name| {
103                            name.to_str().is_some_and(|name| name.starts_with('~'))
104                        }) =>
105                    {
106                        warn_user!(
107                            "Ignoring dangling temporary directory: `{}`",
108                            path.simplified_display().cyan()
109                        );
110                        continue;
111                    }
112                    Err(err) => {
113                        return Err(err).context(format!(
114                            "Failed to read metadata from: `{}`",
115                            path.simplified_display()
116                        ));
117                    }
118                };
119
120                let idx = distributions.len();
121
122                // Index the distribution by name.
123                by_name
124                    .entry(dist_info.name().clone())
125                    .or_default()
126                    .push(idx);
127
128                // Index the distribution by URL.
129                if let InstalledDistKind::Url(dist) = &dist_info.kind {
130                    by_url.entry(dist.url.clone()).or_default().push(idx);
131                }
132
133                // Add the distribution to the database.
134                distributions.push(Some(dist_info));
135            }
136        }
137
138        Ok(Self {
139            interpreter: interpreter.clone(),
140            distributions,
141            by_name,
142            by_url,
143        })
144    }
145
146    /// Returns the [`Interpreter`] used to install the packages.
147    pub fn interpreter(&self) -> &Interpreter {
148        &self.interpreter
149    }
150
151    /// Returns an iterator over the installed distributions.
152    pub fn iter(&self) -> impl Iterator<Item = &InstalledDist> {
153        self.distributions.iter().flatten()
154    }
155
156    /// Returns the installed distributions for a given package.
157    pub fn get_packages(&self, name: &PackageName) -> Vec<&InstalledDist> {
158        let Some(indexes) = self.by_name.get(name) else {
159            return Vec::new();
160        };
161        indexes
162            .iter()
163            .flat_map(|&index| &self.distributions[index])
164            .collect()
165    }
166
167    /// Remove the given packages from the index, returning all installed versions, if any.
168    pub fn remove_packages(&mut self, name: &PackageName) -> Vec<InstalledDist> {
169        let Some(indexes) = self.by_name.get(name) else {
170            return Vec::new();
171        };
172        indexes
173            .iter()
174            .filter_map(|index| std::mem::take(&mut self.distributions[*index]))
175            .collect()
176    }
177
178    /// Returns the distributions installed from the given URL, if any.
179    pub fn get_urls(&self, url: &DisplaySafeUrl) -> Vec<&InstalledDist> {
180        let Some(indexes) = self.by_url.get(url) else {
181            return Vec::new();
182        };
183        indexes
184            .iter()
185            .flat_map(|&index| &self.distributions[index])
186            .collect()
187    }
188
189    /// Returns `true` if there are any installed packages.
190    pub fn any(&self) -> bool {
191        self.distributions.iter().any(Option::is_some)
192    }
193
194    /// Validate the installed packages in the virtual environment.
195    pub fn diagnostics(
196        &self,
197        markers: &ResolverMarkerEnvironment,
198        tags: &Tags,
199        dependency_metadata: &DependencyMetadata,
200    ) -> Result<Vec<SitePackagesDiagnostic>> {
201        let mut diagnostics = Vec::new();
202
203        for (package, indexes) in &self.by_name {
204            let mut distributions = indexes.iter().flat_map(|index| &self.distributions[*index]);
205
206            // Find the installed distribution for the given package.
207            let Some(distribution) = distributions.next() else {
208                continue;
209            };
210
211            if let Some(conflict) = distributions.next() {
212                // There are multiple installed distributions for the same package.
213                diagnostics.push(SitePackagesDiagnostic::DuplicatePackage {
214                    package: package.clone(),
215                    paths: std::iter::once(distribution.install_path().to_owned())
216                        .chain(std::iter::once(conflict.install_path().to_owned()))
217                        .chain(distributions.map(|dist| dist.install_path().to_owned()))
218                        .collect(),
219                });
220                continue;
221            }
222
223            for index in indexes {
224                let Some(distribution) = &self.distributions[*index] else {
225                    continue;
226                };
227
228                // Determine the dependencies for the given package.
229                let metadata = if let Some(metadata) =
230                    dependency_metadata.get(package, Some(distribution.version()))
231                {
232                    Cow::Owned(metadata)
233                } else {
234                    let Ok(metadata) = distribution.read_metadata() else {
235                        diagnostics.push(SitePackagesDiagnostic::MetadataUnavailable {
236                            package: package.clone(),
237                            path: distribution.install_path().to_owned(),
238                        });
239                        continue;
240                    };
241                    Cow::Borrowed(metadata)
242                };
243
244                // Verify that the package is compatible with the current Python version.
245                if let Some(requires_python) = metadata.requires_python.as_ref() {
246                    if !requires_python.contains(markers.python_full_version()) {
247                        diagnostics.push(SitePackagesDiagnostic::IncompatiblePythonVersion {
248                            package: package.clone(),
249                            version: self.interpreter.python_version().clone(),
250                            requires_python: requires_python.clone(),
251                        });
252                    }
253                }
254
255                // Verify that the package is compatible with the current tags.
256                match distribution.read_tags() {
257                    Ok(Some(wheel_tags)) => {
258                        if !wheel_tags.is_compatible(tags) {
259                            // TODO(charlie): Show the expanded tag hint, that explains _why_ it doesn't match.
260                            diagnostics.push(SitePackagesDiagnostic::IncompatiblePlatform {
261                                package: package.clone(),
262                            });
263                        }
264                    }
265                    Ok(None) => {}
266                    Err(_) => {
267                        diagnostics.push(SitePackagesDiagnostic::TagsUnavailable {
268                            package: package.clone(),
269                            path: distribution.install_path().to_owned(),
270                        });
271                    }
272                }
273
274                // Verify that the dependencies are installed.
275                for dependency in &metadata.requires_dist {
276                    if !dependency.evaluate_markers(markers, &[]) {
277                        continue;
278                    }
279
280                    let installed = self.get_packages(&dependency.name);
281                    match installed.as_slice() {
282                        [] => {
283                            // No version installed.
284                            diagnostics.push(SitePackagesDiagnostic::MissingDependency {
285                                package: package.clone(),
286                                requirement: dependency.clone(),
287                            });
288                        }
289                        [installed] => {
290                            match &dependency.version_or_url {
291                                None | Some(VersionOrUrl::Url(_)) => {
292                                    // Nothing to do (accept any installed version).
293                                }
294                                Some(VersionOrUrl::VersionSpecifier(version_specifier)) => {
295                                    // The installed version doesn't satisfy the requirement.
296                                    if !version_specifier.contains(installed.version()) {
297                                        diagnostics.push(
298                                            SitePackagesDiagnostic::IncompatibleDependency {
299                                                package: package.clone(),
300                                                version: installed.version().clone(),
301                                                requirement: dependency.clone(),
302                                            },
303                                        );
304                                    }
305                                }
306                            }
307                        }
308                        _ => {
309                            // There are multiple installed distributions for the same package.
310                        }
311                    }
312                }
313            }
314        }
315
316        Ok(diagnostics)
317    }
318
319    /// Returns if the installed packages satisfy the given requirements.
320    pub fn satisfies_spec(
321        &self,
322        requirements: &[UnresolvedRequirementSpecification],
323        constraints: &[NameRequirementSpecification],
324        overrides: &[UnresolvedRequirementSpecification],
325        installation: InstallationStrategy,
326        markers: &ResolverMarkerEnvironment,
327        tags: &Tags,
328        config_settings: &ConfigSettings,
329        config_settings_package: &PackageConfigSettings,
330        extra_build_requires: &ExtraBuildRequires,
331        extra_build_variables: &ExtraBuildVariables,
332    ) -> Result<SatisfiesResult> {
333        // First, map all unnamed requirements to named requirements.
334        let requirements = {
335            let mut named = Vec::with_capacity(requirements.len());
336            for requirement in requirements {
337                match &requirement.requirement {
338                    UnresolvedRequirement::Named(requirement) => {
339                        named.push(Cow::Borrowed(requirement));
340                    }
341                    UnresolvedRequirement::Unnamed(requirement) => {
342                        match self.get_urls(requirement.url.verbatim.raw()).as_slice() {
343                            [] => {
344                                return Ok(SatisfiesResult::Unsatisfied(
345                                    requirement.url.verbatim.raw().to_string(),
346                                ));
347                            }
348                            [distribution] => {
349                                let requirement = uv_pep508::Requirement {
350                                    name: distribution.name().clone(),
351                                    version_or_url: Some(VersionOrUrl::Url(
352                                        requirement.url.clone(),
353                                    )),
354                                    marker: requirement.marker,
355                                    extras: requirement.extras.clone(),
356                                    origin: requirement.origin.clone(),
357                                };
358                                named.push(Cow::Owned(Requirement::from(requirement)));
359                            }
360                            _ => {
361                                return Ok(SatisfiesResult::Unsatisfied(
362                                    requirement.url.verbatim.raw().to_string(),
363                                ));
364                            }
365                        }
366                    }
367                }
368            }
369            named
370        };
371
372        // Second, map all overrides to named requirements. We assume that all overrides are
373        // relevant.
374        let overrides = {
375            let mut named = Vec::with_capacity(overrides.len());
376            for requirement in overrides {
377                match &requirement.requirement {
378                    UnresolvedRequirement::Named(requirement) => {
379                        named.push(Cow::Borrowed(requirement));
380                    }
381                    UnresolvedRequirement::Unnamed(requirement) => {
382                        match self.get_urls(requirement.url.verbatim.raw()).as_slice() {
383                            [] => {
384                                return Ok(SatisfiesResult::Unsatisfied(
385                                    requirement.url.verbatim.raw().to_string(),
386                                ));
387                            }
388                            [distribution] => {
389                                let requirement = uv_pep508::Requirement {
390                                    name: distribution.name().clone(),
391                                    version_or_url: Some(VersionOrUrl::Url(
392                                        requirement.url.clone(),
393                                    )),
394                                    marker: requirement.marker,
395                                    extras: requirement.extras.clone(),
396                                    origin: requirement.origin.clone(),
397                                };
398                                named.push(Cow::Owned(Requirement::from(requirement)));
399                            }
400                            _ => {
401                                return Ok(SatisfiesResult::Unsatisfied(
402                                    requirement.url.verbatim.raw().to_string(),
403                                ));
404                            }
405                        }
406                    }
407                }
408            }
409            named
410        };
411
412        self.satisfies_requirements(
413            requirements.iter().map(Cow::as_ref),
414            constraints.iter().map(|constraint| &constraint.requirement),
415            overrides.iter().map(Cow::as_ref),
416            installation,
417            markers,
418            tags,
419            config_settings,
420            config_settings_package,
421            extra_build_requires,
422            extra_build_variables,
423        )
424    }
425
426    /// Like [`SitePackages::satisfies_spec`], but with resolved names for all requirements.
427    pub fn satisfies_requirements<'a>(
428        &self,
429        requirements: impl ExactSizeIterator<Item = &'a Requirement>,
430        constraints: impl Iterator<Item = &'a Requirement>,
431        overrides: impl Iterator<Item = &'a Requirement>,
432        installation: InstallationStrategy,
433        markers: &ResolverMarkerEnvironment,
434        tags: &Tags,
435        config_settings: &ConfigSettings,
436        config_settings_package: &PackageConfigSettings,
437        extra_build_requires: &ExtraBuildRequires,
438        extra_build_variables: &ExtraBuildVariables,
439    ) -> Result<SatisfiesResult> {
440        // Collect the constraints and overrides by package name.
441        let constraints: FxHashMap<&PackageName, Vec<&Requirement>> =
442            constraints.fold(FxHashMap::default(), |mut constraints, constraint| {
443                constraints
444                    .entry(&constraint.name)
445                    .or_default()
446                    .push(constraint);
447                constraints
448            });
449        let overrides: FxHashMap<&PackageName, Vec<&Requirement>> =
450            overrides.fold(FxHashMap::default(), |mut overrides, r#override| {
451                overrides
452                    .entry(&r#override.name)
453                    .or_default()
454                    .push(r#override);
455                overrides
456            });
457
458        let mut stack = Vec::with_capacity(requirements.len());
459        let mut seen = FxHashSet::with_capacity_and_hasher(requirements.len(), FxBuildHasher);
460
461        // Add the direct requirements to the queue.
462        for requirement in requirements {
463            if let Some(r#overrides) = overrides.get(&requirement.name) {
464                for dependency in r#overrides {
465                    if dependency.evaluate_markers(Some(markers), &[]) {
466                        if seen.insert((*dependency).clone()) {
467                            stack.push(Cow::Borrowed(*dependency));
468                        }
469                    }
470                }
471            } else {
472                if requirement.evaluate_markers(Some(markers), &[]) {
473                    if seen.insert(requirement.clone()) {
474                        stack.push(Cow::Borrowed(requirement));
475                    }
476                }
477            }
478        }
479
480        // Verify that all non-editable requirements are met.
481        while let Some(requirement) = stack.pop() {
482            let name = &requirement.name;
483            let installed = self.get_packages(name);
484            match installed.as_slice() {
485                [] => {
486                    // The package isn't installed.
487                    return Ok(SatisfiesResult::Unsatisfied(requirement.to_string()));
488                }
489                [distribution] => {
490                    // Validate that the requirement is satisfied.
491                    if requirement.evaluate_markers(Some(markers), &[]) {
492                        match RequirementSatisfaction::check(
493                            name,
494                            distribution,
495                            &requirement.source,
496                            None,
497                            installation,
498                            tags,
499                            config_settings,
500                            config_settings_package,
501                            extra_build_requires,
502                            extra_build_variables,
503                        ) {
504                            RequirementSatisfaction::Mismatch
505                            | RequirementSatisfaction::OutOfDate
506                            | RequirementSatisfaction::CacheInvalid => {
507                                return Ok(SatisfiesResult::Unsatisfied(requirement.to_string()));
508                            }
509                            RequirementSatisfaction::Satisfied => {}
510                        }
511                    }
512
513                    // Validate that the installed version satisfies the constraints.
514                    for constraint in constraints.get(name).into_iter().flatten() {
515                        if constraint.evaluate_markers(Some(markers), &[]) {
516                            match RequirementSatisfaction::check(
517                                name,
518                                distribution,
519                                &constraint.source,
520                                None,
521                                installation,
522                                tags,
523                                config_settings,
524                                config_settings_package,
525                                extra_build_requires,
526                                extra_build_variables,
527                            ) {
528                                RequirementSatisfaction::Mismatch
529                                | RequirementSatisfaction::OutOfDate
530                                | RequirementSatisfaction::CacheInvalid => {
531                                    return Ok(SatisfiesResult::Unsatisfied(
532                                        requirement.to_string(),
533                                    ));
534                                }
535                                RequirementSatisfaction::Satisfied => {}
536                            }
537                        }
538                    }
539
540                    // Recurse into the dependencies.
541                    let metadata = distribution
542                        .read_metadata()
543                        .with_context(|| format!("Failed to read metadata for: {distribution}"))?;
544
545                    // Add the dependencies to the queue.
546                    for dependency in &metadata.requires_dist {
547                        let dependency = Requirement::from(dependency.clone());
548                        if let Some(r#overrides) = overrides.get(&dependency.name) {
549                            for dependency in r#overrides {
550                                if dependency.evaluate_markers(Some(markers), &requirement.extras) {
551                                    if seen.insert((*dependency).clone()) {
552                                        stack.push(Cow::Borrowed(*dependency));
553                                    }
554                                }
555                            }
556                        } else {
557                            if dependency.evaluate_markers(Some(markers), &requirement.extras) {
558                                if seen.insert(dependency.clone()) {
559                                    stack.push(Cow::Owned(dependency));
560                                }
561                            }
562                        }
563                    }
564                }
565                _ => {
566                    // There are multiple installed distributions for the same package.
567                    return Ok(SatisfiesResult::Unsatisfied(requirement.to_string()));
568                }
569            }
570        }
571
572        Ok(SatisfiesResult::Fresh {
573            recursive_requirements: seen,
574        })
575    }
576}
577
578#[derive(Debug, Clone, Copy, PartialEq, Eq)]
579pub enum InstallationStrategy {
580    /// A permissive installation strategy, which accepts existing installations even if the source
581    /// type differs, as in the `pip` and `uv pip` CLIs.
582    ///
583    /// In this strategy, packages that are already installed in the environment may be reused if
584    /// they implicitly match the requirements. For example, if the user installs `./path/to/idna`,
585    /// then runs `uv pip install anyio` (which depends on `idna`), the existing `idna` installation
586    /// will be reused if its version matches the requirement, even though it was installed from a
587    /// path and is being implicitly requested from a registry.
588    Permissive,
589
590    /// A strict installation strategy, which requires that existing installations match the source
591    /// type, as in the `uv sync` CLI.
592    ///
593    /// This strategy enforces that the installation source must match the requirement source.
594    /// It prevents reusing packages that were installed from different sources, ensuring
595    /// declarative and reproducible environments.
596    Strict,
597}
598
599/// We check if all requirements are already satisfied, recursing through the requirements tree.
600#[derive(Debug)]
601pub enum SatisfiesResult {
602    /// All requirements are recursively satisfied.
603    Fresh {
604        /// The flattened set (transitive closure) of all requirements checked.
605        recursive_requirements: FxHashSet<Requirement>,
606    },
607    /// We found an unsatisfied requirement. Since we exit early, we only know about the first
608    /// unsatisfied requirement.
609    Unsatisfied(String),
610}
611
612impl IntoIterator for SitePackages {
613    type Item = InstalledDist;
614    type IntoIter = Flatten<std::vec::IntoIter<Option<InstalledDist>>>;
615
616    fn into_iter(self) -> Self::IntoIter {
617        self.distributions.into_iter().flatten()
618    }
619}
620
621#[derive(Debug)]
622pub enum SitePackagesDiagnostic {
623    MetadataUnavailable {
624        /// The package that is missing metadata.
625        package: PackageName,
626        /// The path to the package.
627        path: PathBuf,
628    },
629    TagsUnavailable {
630        /// The package that is missing tags.
631        package: PackageName,
632        /// The path to the package.
633        path: PathBuf,
634    },
635    IncompatiblePythonVersion {
636        /// The package that requires a different version of Python.
637        package: PackageName,
638        /// The version of Python that is installed.
639        version: Version,
640        /// The version of Python that is required.
641        requires_python: VersionSpecifiers,
642    },
643    IncompatiblePlatform {
644        /// The package that was built for a different platform.
645        package: PackageName,
646    },
647    MissingDependency {
648        /// The package that is missing a dependency.
649        package: PackageName,
650        /// The dependency that is missing.
651        requirement: uv_pep508::Requirement<VerbatimParsedUrl>,
652    },
653    IncompatibleDependency {
654        /// The package that has an incompatible dependency.
655        package: PackageName,
656        /// The version of the package that is installed.
657        version: Version,
658        /// The dependency that is incompatible.
659        requirement: uv_pep508::Requirement<VerbatimParsedUrl>,
660    },
661    DuplicatePackage {
662        /// The package that has multiple installed distributions.
663        package: PackageName,
664        /// The installed versions of the package.
665        paths: Vec<PathBuf>,
666    },
667}
668
669impl Diagnostic for SitePackagesDiagnostic {
670    /// Convert the diagnostic into a user-facing message.
671    fn message(&self) -> String {
672        match self {
673            Self::MetadataUnavailable { package, path } => format!(
674                "The package `{package}` is broken or incomplete (unable to read `METADATA`). Consider recreating the virtualenv, or removing the package directory at: {}.",
675                path.display(),
676            ),
677            Self::TagsUnavailable { package, path } => format!(
678                "The package `{package}` is broken or incomplete (unable to read `WHEEL` file). Consider recreating the virtualenv, or removing the package directory at: {}.",
679                path.display(),
680            ),
681            Self::IncompatiblePythonVersion {
682                package,
683                version,
684                requires_python,
685            } => format!(
686                "The package `{package}` requires Python {requires_python}, but `{version}` is installed"
687            ),
688            Self::IncompatiblePlatform { package } => {
689                format!("The package `{package}` was built for a different platform")
690            }
691            Self::MissingDependency {
692                package,
693                requirement,
694            } => {
695                format!("The package `{package}` requires `{requirement}`, but it's not installed")
696            }
697            Self::IncompatibleDependency {
698                package,
699                version,
700                requirement,
701            } => format!(
702                "The package `{package}` requires `{requirement}`, but `{version}` is installed"
703            ),
704            Self::DuplicatePackage { package, paths } => {
705                let mut paths = paths.clone();
706                paths.sort();
707                format!(
708                    "The package `{package}` has multiple installed distributions: {}",
709                    paths.iter().fold(String::new(), |acc, path| acc
710                        + &format!("\n  - {}", path.display()))
711                )
712            }
713        }
714    }
715
716    /// Returns `true` if the [`PackageName`] is involved in this diagnostic.
717    fn includes(&self, name: &PackageName) -> bool {
718        match self {
719            Self::MetadataUnavailable { package, .. } => name == package,
720            Self::TagsUnavailable { package, .. } => name == package,
721            Self::IncompatiblePythonVersion { package, .. } => name == package,
722            Self::IncompatiblePlatform { package } => name == package,
723            Self::MissingDependency { package, .. } => name == package,
724            Self::IncompatibleDependency {
725                package,
726                requirement,
727                ..
728            } => name == package || &requirement.name == name,
729            Self::DuplicatePackage { package, .. } => name == package,
730        }
731    }
732}
733
734impl InstalledPackagesProvider for SitePackages {
735    fn iter(&self) -> impl Iterator<Item = &InstalledDist> {
736        self.iter()
737    }
738
739    fn get_packages(&self, name: &PackageName) -> Vec<&InstalledDist> {
740        self.get_packages(name)
741    }
742}