43181f1933
## Summary This index strategy resolves every package to the latest possible version across indexes. If a version is in multiple indexes, the first available index is selected. Implements #3137 This closely matches pip. ## Test Plan Good question. I'm hesitant to use my certifi example here, since that would inevitably break when torch removes this package. Please comment!
308 lines
10 KiB
Rust
308 lines
10 KiB
Rust
use std::fmt::{Display, Formatter};
|
|
|
|
use pep508_rs::PackageName;
|
|
|
|
use crate::{PackageNameSpecifier, PackageNameSpecifiers};
|
|
|
|
/// The strategy to use when building source distributions that lack a `pyproject.toml`.
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
pub enum SetupPyStrategy {
|
|
/// Perform a PEP 517 build.
|
|
#[default]
|
|
Pep517,
|
|
/// Perform a build by invoking `setuptools` directly.
|
|
Setuptools,
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
pub enum BuildKind {
|
|
/// A regular PEP 517 wheel build
|
|
#[default]
|
|
Wheel,
|
|
/// A PEP 660 editable installation wheel build
|
|
Editable,
|
|
}
|
|
|
|
impl Display for BuildKind {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Self::Wheel => f.write_str("wheel"),
|
|
Self::Editable => f.write_str("editable"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
|
pub enum NoBinary {
|
|
/// Allow installation of any wheel.
|
|
#[default]
|
|
None,
|
|
|
|
/// Do not allow installation from any wheels.
|
|
All,
|
|
|
|
/// Do not allow installation from the specific wheels.
|
|
Packages(Vec<PackageName>),
|
|
}
|
|
|
|
impl NoBinary {
|
|
/// Determine the binary installation strategy to use for the given arguments.
|
|
pub fn from_args(no_binary: Vec<PackageNameSpecifier>) -> Self {
|
|
let combined = PackageNameSpecifiers::from_iter(no_binary.into_iter());
|
|
match combined {
|
|
PackageNameSpecifiers::All => Self::All,
|
|
PackageNameSpecifiers::None => Self::None,
|
|
PackageNameSpecifiers::Packages(packages) => Self::Packages(packages),
|
|
}
|
|
}
|
|
|
|
/// Determine the binary installation strategy to use for the given argument.
|
|
pub fn from_arg(no_binary: PackageNameSpecifier) -> Self {
|
|
Self::from_args(vec![no_binary])
|
|
}
|
|
|
|
/// Combine a set of [`NoBinary`] values.
|
|
#[must_use]
|
|
pub fn combine(self, other: Self) -> Self {
|
|
match (self, other) {
|
|
// If both are `None`, the result is `None`.
|
|
(Self::None, Self::None) => Self::None,
|
|
// If either is `All`, the result is `All`.
|
|
(Self::All, _) | (_, Self::All) => Self::All,
|
|
// If one is `None`, the result is the other.
|
|
(Self::Packages(a), Self::None) => Self::Packages(a),
|
|
(Self::None, Self::Packages(b)) => Self::Packages(b),
|
|
// If both are `Packages`, the result is the union of the two.
|
|
(Self::Packages(mut a), Self::Packages(b)) => {
|
|
a.extend(b);
|
|
Self::Packages(a)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Extend a [`NoBinary`] value with another.
|
|
pub fn extend(&mut self, other: Self) {
|
|
match (&mut *self, other) {
|
|
// If either is `All`, the result is `All`.
|
|
(Self::All, _) | (_, Self::All) => *self = Self::All,
|
|
// If both are `None`, the result is `None`.
|
|
(Self::None, Self::None) => {
|
|
// Nothing to do.
|
|
}
|
|
// If one is `None`, the result is the other.
|
|
(Self::Packages(_), Self::None) => {
|
|
// Nothing to do.
|
|
}
|
|
(Self::None, Self::Packages(b)) => {
|
|
// Take ownership of `b`.
|
|
*self = Self::Packages(b);
|
|
}
|
|
// If both are `Packages`, the result is the union of the two.
|
|
(Self::Packages(a), Self::Packages(b)) => {
|
|
a.extend(b);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl NoBinary {
|
|
/// Returns `true` if all wheels are allowed.
|
|
pub fn is_none(&self) -> bool {
|
|
matches!(self, Self::None)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
|
pub enum NoBuild {
|
|
/// Allow building wheels from any source distribution.
|
|
#[default]
|
|
None,
|
|
|
|
/// Do not allow building wheels from any source distribution.
|
|
All,
|
|
|
|
/// Do not allow building wheels from the given package's source distributions.
|
|
Packages(Vec<PackageName>),
|
|
}
|
|
|
|
impl NoBuild {
|
|
/// Determine the build strategy to use for the given arguments.
|
|
pub fn from_args(only_binary: Vec<PackageNameSpecifier>, no_build: bool) -> Self {
|
|
if no_build {
|
|
Self::All
|
|
} else {
|
|
let combined = PackageNameSpecifiers::from_iter(only_binary.into_iter());
|
|
match combined {
|
|
PackageNameSpecifiers::All => Self::All,
|
|
PackageNameSpecifiers::None => Self::None,
|
|
PackageNameSpecifiers::Packages(packages) => Self::Packages(packages),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Determine the build strategy to use for the given argument.
|
|
pub fn from_arg(no_build: PackageNameSpecifier) -> Self {
|
|
Self::from_args(vec![no_build], false)
|
|
}
|
|
|
|
/// Combine a set of [`NoBuild`] values.
|
|
#[must_use]
|
|
pub fn combine(self, other: Self) -> Self {
|
|
match (self, other) {
|
|
// If both are `None`, the result is `None`.
|
|
(Self::None, Self::None) => Self::None,
|
|
// If either is `All`, the result is `All`.
|
|
(Self::All, _) | (_, Self::All) => Self::All,
|
|
// If one is `None`, the result is the other.
|
|
(Self::Packages(a), Self::None) => Self::Packages(a),
|
|
(Self::None, Self::Packages(b)) => Self::Packages(b),
|
|
// If both are `Packages`, the result is the union of the two.
|
|
(Self::Packages(mut a), Self::Packages(b)) => {
|
|
a.extend(b);
|
|
Self::Packages(a)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Extend a [`NoBuild`] value with another.
|
|
pub fn extend(&mut self, other: Self) {
|
|
match (&mut *self, other) {
|
|
// If either is `All`, the result is `All`.
|
|
(Self::All, _) | (_, Self::All) => *self = Self::All,
|
|
// If both are `None`, the result is `None`.
|
|
(Self::None, Self::None) => {
|
|
// Nothing to do.
|
|
}
|
|
// If one is `None`, the result is the other.
|
|
(Self::Packages(_), Self::None) => {
|
|
// Nothing to do.
|
|
}
|
|
(Self::None, Self::Packages(b)) => {
|
|
// Take ownership of `b`.
|
|
*self = Self::Packages(b);
|
|
}
|
|
// If both are `Packages`, the result is the union of the two.
|
|
(Self::Packages(a), Self::Packages(b)) => {
|
|
a.extend(b);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl NoBuild {
|
|
/// Returns `true` if all builds are allowed.
|
|
pub fn is_none(&self) -> bool {
|
|
matches!(self, Self::None)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone, Copy, Hash, Eq, PartialEq)]
|
|
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
|
|
#[cfg_attr(
|
|
feature = "serde",
|
|
serde(deny_unknown_fields, rename_all = "kebab-case")
|
|
)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
pub enum IndexStrategy {
|
|
/// Only use results from the first index that returns a match for a given package name.
|
|
///
|
|
/// While this differs from pip's behavior, it's the default index strategy as it's the most
|
|
/// secure.
|
|
#[default]
|
|
#[cfg_attr(feature = "clap", clap(alias = "first-match"))]
|
|
FirstIndex,
|
|
/// Search for every package name across all indexes, exhausting the versions from the first
|
|
/// index before moving on to the next.
|
|
///
|
|
/// In this strategy, we look for every package across all indexes. When resolving, we attempt
|
|
/// to use versions from the indexes in order, such that we exhaust all available versions from
|
|
/// the first index before moving on to the next. Further, if a version is found to be
|
|
/// incompatible in the first index, we do not reconsider that version in subsequent indexes,
|
|
/// even if the secondary index might contain compatible versions (e.g., variants of the same
|
|
/// versions with different ABI tags or Python version constraints).
|
|
///
|
|
/// See: https://peps.python.org/pep-0708/
|
|
#[cfg_attr(feature = "clap", clap(alias = "unsafe-any-match"))]
|
|
UnsafeFirstMatch,
|
|
/// Search for every package name across all indexes, preferring the "best" version found. If a
|
|
/// package version is in multiple indexes, only look at the entry for the first index.
|
|
///
|
|
/// In this strategy, we look for every package across all indexes. When resolving, we consider
|
|
/// all versions from all indexes, choosing the "best" version found (typically, the highest
|
|
/// compatible version).
|
|
///
|
|
/// This most closely matches pip's behavior, but exposes the resolver to "dependency confusion"
|
|
/// attacks whereby malicious actors can publish packages to public indexes with the same name
|
|
/// as internal packages, causing the resolver to install the malicious package in lieu of
|
|
/// the intended internal package.
|
|
///
|
|
/// See: https://peps.python.org/pep-0708/
|
|
UnsafeBestMatch,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::str::FromStr;
|
|
|
|
use anyhow::Error;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn no_build_from_args() -> Result<(), Error> {
|
|
assert_eq!(
|
|
NoBuild::from_args(vec![PackageNameSpecifier::from_str(":all:")?], false),
|
|
NoBuild::All,
|
|
);
|
|
assert_eq!(
|
|
NoBuild::from_args(vec![PackageNameSpecifier::from_str(":all:")?], true),
|
|
NoBuild::All,
|
|
);
|
|
assert_eq!(
|
|
NoBuild::from_args(vec![PackageNameSpecifier::from_str(":none:")?], true),
|
|
NoBuild::All,
|
|
);
|
|
assert_eq!(
|
|
NoBuild::from_args(vec![PackageNameSpecifier::from_str(":none:")?], false),
|
|
NoBuild::None,
|
|
);
|
|
assert_eq!(
|
|
NoBuild::from_args(
|
|
vec![
|
|
PackageNameSpecifier::from_str("foo")?,
|
|
PackageNameSpecifier::from_str("bar")?
|
|
],
|
|
false
|
|
),
|
|
NoBuild::Packages(vec![
|
|
PackageName::from_str("foo")?,
|
|
PackageName::from_str("bar")?
|
|
]),
|
|
);
|
|
assert_eq!(
|
|
NoBuild::from_args(
|
|
vec![
|
|
PackageNameSpecifier::from_str("test")?,
|
|
PackageNameSpecifier::All
|
|
],
|
|
false
|
|
),
|
|
NoBuild::All,
|
|
);
|
|
assert_eq!(
|
|
NoBuild::from_args(
|
|
vec![
|
|
PackageNameSpecifier::from_str("foo")?,
|
|
PackageNameSpecifier::from_str(":none:")?,
|
|
PackageNameSpecifier::from_str("bar")?
|
|
],
|
|
false
|
|
),
|
|
NoBuild::Packages(vec![PackageName::from_str("bar")?]),
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
}
|