use anyhow::Result; use itertools::Itertools; use pubgrub::range::Range; use tracing::warn; use pep508_rs::{MarkerEnvironment, Requirement, VersionOrUrl}; use puffin_normalize::{ExtraName, PackageName}; pub(crate) use crate::pubgrub::package::PubGrubPackage; pub(crate) use crate::pubgrub::priority::{PubGrubPriorities, PubGrubPriority}; pub use crate::pubgrub::report::ResolutionFailureReporter; pub(crate) use crate::pubgrub::specifier::PubGrubSpecifier; pub(crate) use crate::pubgrub::version::{PubGrubVersion, MIN_VERSION}; mod package; mod priority; mod report; mod specifier; mod version; /// Convert a set of requirements to a set of `PubGrub` packages and ranges. pub(crate) fn iter_requirements<'a>( requirements: impl Iterator + 'a, extra: Option<&'a ExtraName>, source: Option<&'a PackageName>, env: &'a MarkerEnvironment, ) -> impl Iterator)> + 'a { requirements .filter(move |requirement| { if source.is_some_and(|source| source == &requirement.name) { // TODO(konstin): Warn only once here warn!("{} depends on itself", requirement.name); false } else { true } }) .filter(move |requirement| { // TODO(charlie): We shouldn't need a vector here. let extra = if let Some(extra) = extra { vec![extra.as_ref()] } else { vec![] }; requirement.evaluate_markers(env, &extra) }) .flat_map(|requirement| { std::iter::once(pubgrub_package(requirement, None).unwrap()).chain( requirement .extras .clone() .into_iter() .flatten() .map(|extra| { pubgrub_package(requirement, Some(ExtraName::new(extra))).unwrap() }), ) }) } /// Convert a PEP 508 specifier to a `PubGrub` range. pub(crate) fn version_range(specifiers: Option<&VersionOrUrl>) -> Result> { let Some(specifiers) = specifiers else { return Ok(Range::full()); }; let VersionOrUrl::VersionSpecifier(specifiers) = specifiers else { return Ok(Range::full()); }; specifiers .iter() .map(PubGrubSpecifier::try_from) .fold_ok(Range::full(), |range, specifier| { range.intersection(&specifier.into()) }) } /// Convert a [`Requirement`] to a `PubGrub`-compatible package and range. fn pubgrub_package( requirement: &Requirement, extra: Option, ) -> Result<(PubGrubPackage, Range)> { match requirement.version_or_url.as_ref() { // The requirement has no specifier (e.g., `flask`). None => Ok(( PubGrubPackage::Package(requirement.name.clone(), extra, None), Range::full(), )), // The requirement has a URL (e.g., `flask @ file:///path/to/flask`). Some(VersionOrUrl::Url(url)) => Ok(( PubGrubPackage::Package(requirement.name.clone(), extra, Some(url.clone())), Range::full(), )), // The requirement has a specifier (e.g., `flask>=1.0`). Some(VersionOrUrl::VersionSpecifier(specifiers)) => { let version = specifiers .iter() .map(PubGrubSpecifier::try_from) .fold_ok(Range::full(), |range, specifier| { range.intersection(&specifier.into()) })?; Ok(( PubGrubPackage::Package(requirement.name.clone(), extra, None), version, )) } } }