mirror of https://github.com/astral-sh/uv
304 lines
10 KiB
Rust
304 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, serde::Deserialize)]
|
|
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
|
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
|
#[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(())
|
|
}
|
|
}
|