mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-17 13:58:29 +00:00

## Summary These were moved as part of a broader refactor to create a single integration test module. That "single integration test module" did indeed have a big impact on compile times, which is great! But we aren't seeing any benefit from moving these tests into their own files (despite the claim in [this blog post](https://matklad.github.io/2021/02/27/delete-cargo-integration-tests.html), I see the same compilation pattern regardless of where the tests are located). Plus, we don't have many of these, and same-file tests is such a strong Rust convention.
419 lines
14 KiB
Rust
419 lines
14 KiB
Rust
use std::fmt::{Display, Formatter};
|
|
|
|
use uv_pep508::PackageName;
|
|
|
|
use crate::{PackageNameSpecifier, PackageNameSpecifiers};
|
|
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
|
pub enum BuildKind {
|
|
/// A PEP 517 wheel build.
|
|
#[default]
|
|
Wheel,
|
|
/// A PEP 517 source distribution build.
|
|
Sdist,
|
|
/// 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::Sdist => f.write_str("sdist"),
|
|
Self::Editable => f.write_str("editable"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
pub enum BuildOutput {
|
|
/// Send the build backend output to `stderr`.
|
|
Stderr,
|
|
/// Send the build backend output to `tracing`.
|
|
Debug,
|
|
/// Do not display the build backend output.
|
|
Quiet,
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
|
pub struct BuildOptions {
|
|
no_binary: NoBinary,
|
|
no_build: NoBuild,
|
|
}
|
|
|
|
impl BuildOptions {
|
|
pub fn new(no_binary: NoBinary, no_build: NoBuild) -> Self {
|
|
Self {
|
|
no_binary,
|
|
no_build,
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn combine(self, no_binary: NoBinary, no_build: NoBuild) -> Self {
|
|
Self {
|
|
no_binary: self.no_binary.combine(no_binary),
|
|
no_build: self.no_build.combine(no_build),
|
|
}
|
|
}
|
|
|
|
pub fn no_binary_package(&self, package_name: &PackageName) -> bool {
|
|
match &self.no_binary {
|
|
NoBinary::None => false,
|
|
NoBinary::All => match &self.no_build {
|
|
// Allow `all` to be overridden by specific build exclusions
|
|
NoBuild::Packages(packages) => !packages.contains(package_name),
|
|
_ => true,
|
|
},
|
|
NoBinary::Packages(packages) => packages.contains(package_name),
|
|
}
|
|
}
|
|
|
|
pub fn no_build_package(&self, package_name: &PackageName) -> bool {
|
|
match &self.no_build {
|
|
NoBuild::All => match &self.no_binary {
|
|
// Allow `all` to be overridden by specific binary exclusions
|
|
NoBinary::Packages(packages) => !packages.contains(package_name),
|
|
_ => true,
|
|
},
|
|
NoBuild::None => false,
|
|
NoBuild::Packages(packages) => packages.contains(package_name),
|
|
}
|
|
}
|
|
|
|
pub fn no_build_requirement(&self, package_name: Option<&PackageName>) -> bool {
|
|
match package_name {
|
|
Some(name) => self.no_build_package(name),
|
|
None => self.no_build_all(),
|
|
}
|
|
}
|
|
|
|
pub fn no_binary_requirement(&self, package_name: Option<&PackageName>) -> bool {
|
|
match package_name {
|
|
Some(name) => self.no_binary_package(name),
|
|
None => self.no_binary_all(),
|
|
}
|
|
}
|
|
|
|
pub fn no_build_all(&self) -> bool {
|
|
matches!(self.no_build, NoBuild::All)
|
|
}
|
|
|
|
pub fn no_binary_all(&self) -> bool {
|
|
matches!(self.no_binary, NoBinary::All)
|
|
}
|
|
|
|
/// Return the [`NoBuild`] strategy to use.
|
|
pub fn no_build(&self) -> &NoBuild {
|
|
&self.no_build
|
|
}
|
|
|
|
/// Return the [`NoBinary`] strategy to use.
|
|
pub fn no_binary(&self) -> &NoBinary {
|
|
&self.no_binary
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
|
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: Option<bool>, no_binary_package: Vec<PackageName>) -> Self {
|
|
match no_binary {
|
|
Some(true) => Self::All,
|
|
Some(false) => Self::None,
|
|
None => {
|
|
if no_binary_package.is_empty() {
|
|
Self::None
|
|
} else {
|
|
Self::Packages(no_binary_package)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Determine the binary installation strategy to use for the given arguments from the pip CLI.
|
|
pub fn from_pip_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 from the pip CLI.
|
|
pub fn from_pip_arg(no_binary: PackageNameSpecifier) -> Self {
|
|
Self::from_pip_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, serde::Serialize, serde::Deserialize)]
|
|
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
|
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(no_build: Option<bool>, no_build_package: Vec<PackageName>) -> Self {
|
|
match no_build {
|
|
Some(true) => Self::All,
|
|
Some(false) => Self::None,
|
|
None => {
|
|
if no_build_package.is_empty() {
|
|
Self::None
|
|
} else {
|
|
Self::Packages(no_build_package)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Determine the build strategy to use for the given arguments from the pip CLI.
|
|
pub fn from_pip_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 from the pip CLI.
|
|
pub fn from_pip_arg(no_build: PackageNameSpecifier) -> Self {
|
|
Self::from_pip_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, PartialEq, Eq, serde::Serialize, 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"))]
|
|
#[serde(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_pip_args(vec![PackageNameSpecifier::from_str(":all:")?], false),
|
|
NoBuild::All,
|
|
);
|
|
assert_eq!(
|
|
NoBuild::from_pip_args(vec![PackageNameSpecifier::from_str(":all:")?], true),
|
|
NoBuild::All,
|
|
);
|
|
assert_eq!(
|
|
NoBuild::from_pip_args(vec![PackageNameSpecifier::from_str(":none:")?], true),
|
|
NoBuild::All,
|
|
);
|
|
assert_eq!(
|
|
NoBuild::from_pip_args(vec![PackageNameSpecifier::from_str(":none:")?], false),
|
|
NoBuild::None,
|
|
);
|
|
assert_eq!(
|
|
NoBuild::from_pip_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_pip_args(
|
|
vec![
|
|
PackageNameSpecifier::from_str("test")?,
|
|
PackageNameSpecifier::All
|
|
],
|
|
false
|
|
),
|
|
NoBuild::All,
|
|
);
|
|
assert_eq!(
|
|
NoBuild::from_pip_args(
|
|
vec![
|
|
PackageNameSpecifier::from_str("foo")?,
|
|
PackageNameSpecifier::from_str(":none:")?,
|
|
PackageNameSpecifier::from_str("bar")?
|
|
],
|
|
false
|
|
),
|
|
NoBuild::Packages(vec![PackageName::from_str("bar")?]),
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
}
|