mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Rename uv-traits
and split into separate modules (#2674)
This is driving me a little crazy and is becoming a larger problem in #2596 where I need to move more types (like `Upgrade` and `Reinstall`) into this crate. Anything that's shared across our core resolver, install, and build crates needs to be defined in this crate to avoid cyclic dependencies. We've outgrown it being a single file with some shared traits. There are no behavioral changes here.
This commit is contained in:
parent
39769d82a0
commit
0b08ba1e67
44 changed files with 696 additions and 624 deletions
19
Cargo.lock
generated
19
Cargo.lock
generated
|
@ -4361,7 +4361,7 @@ dependencies = [
|
|||
"uv-normalize",
|
||||
"uv-requirements",
|
||||
"uv-resolver",
|
||||
"uv-traits",
|
||||
"uv-types",
|
||||
"uv-virtualenv",
|
||||
"uv-warnings",
|
||||
]
|
||||
|
@ -4411,7 +4411,7 @@ dependencies = [
|
|||
"tracing",
|
||||
"uv-fs",
|
||||
"uv-interpreter",
|
||||
"uv-traits",
|
||||
"uv-types",
|
||||
"uv-virtualenv",
|
||||
]
|
||||
|
||||
|
@ -4533,7 +4533,7 @@ dependencies = [
|
|||
"uv-interpreter",
|
||||
"uv-normalize",
|
||||
"uv-resolver",
|
||||
"uv-traits",
|
||||
"uv-types",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
|
@ -4554,7 +4554,7 @@ dependencies = [
|
|||
"uv-installer",
|
||||
"uv-interpreter",
|
||||
"uv-resolver",
|
||||
"uv-traits",
|
||||
"uv-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4590,7 +4590,7 @@ dependencies = [
|
|||
"uv-fs",
|
||||
"uv-git",
|
||||
"uv-normalize",
|
||||
"uv-traits",
|
||||
"uv-types",
|
||||
"zip",
|
||||
]
|
||||
|
||||
|
@ -4684,7 +4684,7 @@ dependencies = [
|
|||
"uv-fs",
|
||||
"uv-interpreter",
|
||||
"uv-normalize",
|
||||
"uv-traits",
|
||||
"uv-types",
|
||||
"uv-warnings",
|
||||
"walkdir",
|
||||
]
|
||||
|
@ -4757,7 +4757,7 @@ dependencies = [
|
|||
"uv-fs",
|
||||
"uv-normalize",
|
||||
"uv-resolver",
|
||||
"uv-traits",
|
||||
"uv-types",
|
||||
"uv-warnings",
|
||||
]
|
||||
|
||||
|
@ -4801,18 +4801,19 @@ dependencies = [
|
|||
"uv-distribution",
|
||||
"uv-interpreter",
|
||||
"uv-normalize",
|
||||
"uv-traits",
|
||||
"uv-types",
|
||||
"uv-warnings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uv-traits"
|
||||
name = "uv-types"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"distribution-types",
|
||||
"once-map",
|
||||
"pep508_rs",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"uv-cache",
|
||||
|
|
|
@ -43,7 +43,7 @@ uv-interpreter = { path = "crates/uv-interpreter" }
|
|||
uv-normalize = { path = "crates/uv-normalize" }
|
||||
uv-requirements = { path = "crates/uv-requirements" }
|
||||
uv-resolver = { path = "crates/uv-resolver" }
|
||||
uv-traits = { path = "crates/uv-traits" }
|
||||
uv-types = { path = "crates/uv-types" }
|
||||
uv-trampoline = { path = "crates/uv-trampoline" }
|
||||
uv-version = { path = "crates/uv-version" }
|
||||
uv-virtualenv = { path = "crates/uv-virtualenv" }
|
||||
|
|
|
@ -66,7 +66,7 @@ Development utilities for uv.
|
|||
## [uv-dispatch](./uv-dispatch)
|
||||
|
||||
A centralized `struct` for resolving and building source distributions in isolated environments.
|
||||
Implements the traits defined in `uv-traits`.
|
||||
Implements the traits defined in `uv-types`.
|
||||
|
||||
## [uv-distribution](./uv-distribution)
|
||||
|
||||
|
@ -109,7 +109,7 @@ Utilities for reading package requirements from `pyproject.toml` and `requiremen
|
|||
|
||||
Functionality for resolving Python packages and their dependencies.
|
||||
|
||||
## [uv-traits](./uv-traits)
|
||||
## [uv-types](./uv-types)
|
||||
|
||||
Shared traits for uv, to avoid circular dependencies.
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ pep440_rs = { workspace = true }
|
|||
pep508_rs = { workspace = true }
|
||||
uv-fs = { workspace = true }
|
||||
uv-interpreter = { workspace = true }
|
||||
uv-traits = { workspace = true, features = ["serde"] }
|
||||
uv-types = { workspace = true, features = ["serde"] }
|
||||
uv-virtualenv = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
|
|
|
@ -30,7 +30,7 @@ use pep440_rs::{Version, VersionSpecifiers};
|
|||
use pep508_rs::{PackageName, Requirement};
|
||||
use uv_fs::{PythonExt, Simplified};
|
||||
use uv_interpreter::{Interpreter, PythonEnvironment};
|
||||
use uv_traits::{
|
||||
use uv_types::{
|
||||
BuildContext, BuildIsolation, BuildKind, ConfigSettings, SetupPyStrategy, SourceBuildTrait,
|
||||
};
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ uv-installer = { workspace = true }
|
|||
uv-interpreter = { workspace = true }
|
||||
uv-normalize = { workspace = true }
|
||||
uv-resolver = { workspace = true }
|
||||
uv-traits = { workspace = true }
|
||||
uv-types = { workspace = true }
|
||||
|
||||
# Any dependencies that are exclusively used in `uv-dev` should be listed as non-workspace
|
||||
# dependencies, to ensure that we're forced to think twice before including them in other crates.
|
||||
|
|
|
@ -14,7 +14,7 @@ use uv_dispatch::BuildDispatch;
|
|||
use uv_installer::NoBinary;
|
||||
use uv_interpreter::PythonEnvironment;
|
||||
use uv_resolver::InMemoryIndex;
|
||||
use uv_traits::{
|
||||
use uv_types::{
|
||||
BuildContext, BuildIsolation, BuildKind, ConfigSettings, InFlight, NoBuild, SetupPyStrategy,
|
||||
};
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ use uv_installer::{Downloader, NoBinary};
|
|||
use uv_interpreter::PythonEnvironment;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_resolver::{DistFinder, InMemoryIndex};
|
||||
use uv_traits::{BuildContext, BuildIsolation, ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
use uv_types::{BuildContext, BuildIsolation, ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
|
||||
#[derive(Parser)]
|
||||
pub(crate) struct InstallManyArgs {
|
||||
|
|
|
@ -17,7 +17,7 @@ use uv_dispatch::BuildDispatch;
|
|||
use uv_installer::NoBinary;
|
||||
use uv_interpreter::PythonEnvironment;
|
||||
use uv_resolver::{InMemoryIndex, Manifest, Options, Resolver};
|
||||
use uv_traits::{BuildIsolation, ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
use uv_types::{BuildIsolation, ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
|
||||
#[derive(ValueEnum, Default, Clone)]
|
||||
pub(crate) enum ResolveCliFormat {
|
||||
|
|
|
@ -20,7 +20,7 @@ use uv_installer::NoBinary;
|
|||
use uv_interpreter::PythonEnvironment;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_resolver::InMemoryIndex;
|
||||
use uv_traits::{BuildContext, BuildIsolation, ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
use uv_types::{BuildContext, BuildIsolation, ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
|
||||
#[derive(Parser)]
|
||||
pub(crate) struct ResolveManyArgs {
|
||||
|
|
|
@ -22,7 +22,7 @@ uv-client = { workspace = true }
|
|||
uv-installer = { workspace = true }
|
||||
uv-interpreter = { workspace = true }
|
||||
uv-resolver = { workspace = true }
|
||||
uv-traits = { workspace = true }
|
||||
uv-types = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
|
|
|
@ -20,7 +20,7 @@ use uv_client::{FlatIndex, RegistryClient};
|
|||
use uv_installer::{Downloader, Installer, NoBinary, Plan, Planner, Reinstall, SitePackages};
|
||||
use uv_interpreter::{Interpreter, PythonEnvironment};
|
||||
use uv_resolver::{InMemoryIndex, Manifest, Options, Resolver};
|
||||
use uv_traits::{
|
||||
use uv_types::{
|
||||
BuildContext, BuildIsolation, BuildKind, ConfigSettings, InFlight, NoBuild, SetupPyStrategy,
|
||||
};
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ uv-extract = { workspace = true }
|
|||
uv-fs = { workspace = true, features = ["tokio"] }
|
||||
uv-git = { workspace = true, features = ["vendored-openssl"] }
|
||||
uv-normalize = { workspace = true }
|
||||
uv-traits = { workspace = true }
|
||||
uv-types = { workspace = true }
|
||||
pypi-types = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
|
|
|
@ -19,7 +19,7 @@ use pypi_types::Metadata23;
|
|||
use uv_cache::{ArchiveTarget, ArchiveTimestamp, Cache, CacheBucket, CacheEntry, WheelCache};
|
||||
use uv_client::{CacheControl, CachedClientError, Connectivity, RegistryClient};
|
||||
use uv_git::GitSource;
|
||||
use uv_traits::{BuildContext, NoBinary, NoBuild};
|
||||
use uv_types::{BuildContext, NoBinary, NoBuild};
|
||||
|
||||
use crate::download::{BuiltWheel, UnzippedWheel};
|
||||
use crate::locks::Locks;
|
||||
|
|
|
@ -33,7 +33,7 @@ use uv_client::{
|
|||
};
|
||||
use uv_fs::{write_atomic, LockedFile};
|
||||
use uv_git::{Fetch, GitSource};
|
||||
use uv_traits::{BuildContext, BuildKind, NoBuild, SourceBuildTrait};
|
||||
use uv_types::{BuildContext, BuildKind, NoBuild, SourceBuildTrait};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::reporter::Facade;
|
||||
|
|
|
@ -27,7 +27,7 @@ uv-extract = { workspace = true }
|
|||
uv-fs = { workspace = true }
|
||||
uv-interpreter = { workspace = true }
|
||||
uv-normalize = { workspace = true }
|
||||
uv-traits = { workspace = true }
|
||||
uv-types = { workspace = true }
|
||||
uv-warnings = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
|
|
|
@ -15,7 +15,7 @@ use platform_tags::Tags;
|
|||
use uv_cache::Cache;
|
||||
use uv_client::RegistryClient;
|
||||
use uv_distribution::{DistributionDatabase, LocalWheel, Unzip};
|
||||
use uv_traits::{BuildContext, InFlight};
|
||||
use uv_types::{BuildContext, InFlight};
|
||||
|
||||
use crate::editable::BuiltEditable;
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ pub use installer::{Installer, Reporter as InstallReporter};
|
|||
pub use plan::{Plan, Planner, Reinstall};
|
||||
pub use site_packages::{Diagnostic, SitePackages};
|
||||
pub use uninstall::{uninstall, UninstallError};
|
||||
pub use uv_traits::NoBinary;
|
||||
pub use uv_types::NoBinary;
|
||||
|
||||
mod compile;
|
||||
mod downloader;
|
||||
|
|
|
@ -17,7 +17,7 @@ use uv_distribution::{BuiltWheelIndex, RegistryWheelIndex};
|
|||
use uv_fs::Simplified;
|
||||
use uv_interpreter::PythonEnvironment;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_traits::NoBinary;
|
||||
use uv_types::NoBinary;
|
||||
|
||||
use crate::{ResolvedEditable, SitePackages};
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ uv-distribution = { workspace = true }
|
|||
uv-fs = { workspace = true }
|
||||
uv-normalize = { workspace = true }
|
||||
uv-resolver = { workspace = true, features = ["clap"] }
|
||||
uv-traits = { workspace = true }
|
||||
uv-types = { workspace = true }
|
||||
uv-warnings = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
|
|
|
@ -20,7 +20,7 @@ use pypi_types::Metadata10;
|
|||
use uv_client::RegistryClient;
|
||||
use uv_distribution::{Reporter, SourceDistCachedBuilder};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_traits::BuildContext;
|
||||
use uv_types::BuildContext;
|
||||
|
||||
/// Like [`RequirementsSpecification`], but with concrete names for all requirements.
|
||||
pub struct NamedRequirementsResolver {
|
||||
|
|
|
@ -10,7 +10,7 @@ use distribution_types::{BuildableSource, PathSourceUrl, SourceUrl};
|
|||
use pep508_rs::Requirement;
|
||||
use uv_client::RegistryClient;
|
||||
use uv_distribution::{Reporter, SourceDistCachedBuilder};
|
||||
use uv_traits::BuildContext;
|
||||
use uv_types::BuildContext;
|
||||
|
||||
use crate::ExtrasSpecification;
|
||||
|
||||
|
|
|
@ -1,48 +1,11 @@
|
|||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use requirements_txt::RequirementsTxt;
|
||||
use uv_client::{BaseClientBuilder, Connectivity};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_resolver::{Preference, PreferenceError};
|
||||
|
||||
/// Whether to allow package upgrades.
|
||||
#[derive(Debug)]
|
||||
pub enum Upgrade {
|
||||
/// Prefer pinned versions from the existing lockfile, if possible.
|
||||
None,
|
||||
|
||||
/// Allow package upgrades for all packages, ignoring the existing lockfile.
|
||||
All,
|
||||
|
||||
/// Allow package upgrades, but only for the specified packages.
|
||||
Packages(FxHashSet<PackageName>),
|
||||
}
|
||||
|
||||
impl Upgrade {
|
||||
/// Determine the upgrade strategy from the command-line arguments.
|
||||
pub fn from_args(upgrade: bool, upgrade_package: Vec<PackageName>) -> Self {
|
||||
if upgrade {
|
||||
Self::All
|
||||
} else if !upgrade_package.is_empty() {
|
||||
Self::Packages(upgrade_package.into_iter().collect())
|
||||
} else {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if no packages should be upgraded.
|
||||
pub fn is_none(&self) -> bool {
|
||||
matches!(self, Self::None)
|
||||
}
|
||||
|
||||
/// Returns `true` if all packages should be upgraded.
|
||||
pub fn is_all(&self) -> bool {
|
||||
matches!(self, Self::All)
|
||||
}
|
||||
}
|
||||
use uv_types::Upgrade;
|
||||
|
||||
/// Load the preferred requirements from an existing lockfile, applying the upgrade strategy.
|
||||
pub async fn read_lockfile(
|
||||
|
|
|
@ -27,7 +27,7 @@ uv-client = { workspace = true }
|
|||
uv-distribution = { workspace = true }
|
||||
uv-interpreter = { workspace = true }
|
||||
uv-normalize = { workspace = true }
|
||||
uv-traits = { workspace = true }
|
||||
uv-types = { workspace = true }
|
||||
uv-warnings = { workspace = true }
|
||||
|
||||
anstream = { workspace = true }
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
use anyhow::Result;
|
||||
use futures::{stream, Stream, StreamExt, TryStreamExt};
|
||||
use rustc_hash::FxHashMap;
|
||||
use uv_traits::{NoBinary, NoBuild};
|
||||
use uv_types::{NoBinary, NoBuild};
|
||||
|
||||
use distribution_filename::DistFilename;
|
||||
use distribution_types::{Dist, IndexUrl, Resolution};
|
||||
|
|
|
@ -31,7 +31,7 @@ use uv_client::{FlatIndex, RegistryClient};
|
|||
use uv_distribution::DistributionDatabase;
|
||||
use uv_interpreter::Interpreter;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_traits::BuildContext;
|
||||
use uv_types::BuildContext;
|
||||
|
||||
use crate::candidate_selector::{CandidateDist, CandidateSelector};
|
||||
use crate::constraints::Constraints;
|
||||
|
|
|
@ -10,7 +10,7 @@ use pypi_types::Metadata23;
|
|||
use uv_client::{FlatIndex, RegistryClient};
|
||||
use uv_distribution::DistributionDatabase;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_traits::{BuildContext, NoBinary, NoBuild};
|
||||
use uv_types::{BuildContext, NoBinary, NoBuild};
|
||||
|
||||
use crate::python_requirement::PythonRequirement;
|
||||
use crate::version_map::VersionMap;
|
||||
|
|
|
@ -16,7 +16,7 @@ use pypi_types::{Hashes, Yanked};
|
|||
use rkyv::{de::deserializers::SharedDeserializeMap, Deserialize};
|
||||
use uv_client::{FlatDistributions, OwnedArchive, SimpleMetadata, VersionFiles};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_traits::{NoBinary, NoBuild};
|
||||
use uv_types::{NoBinary, NoBuild};
|
||||
use uv_warnings::warn_user_once;
|
||||
|
||||
use crate::{python_requirement::PythonRequirement, yanks::AllowedYanks};
|
||||
|
|
|
@ -20,7 +20,7 @@ use uv_resolver::{
|
|||
DisplayResolutionGraph, InMemoryIndex, Manifest, Options, OptionsBuilder, PreReleaseMode,
|
||||
Preference, ResolutionGraph, ResolutionMode, Resolver,
|
||||
};
|
||||
use uv_traits::{
|
||||
use uv_types::{
|
||||
BuildContext, BuildIsolation, BuildKind, NoBinary, NoBuild, SetupPyStrategy, SourceBuildTrait,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,535 +0,0 @@
|
|||
//! Avoid cyclic crate dependencies between resolver, installer and builder.
|
||||
|
||||
use std::collections::btree_map::Entry;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::future::Future;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use distribution_types::{CachedDist, DistributionId, IndexLocations, Resolution, SourceDist};
|
||||
use once_map::OnceMap;
|
||||
use pep508_rs::Requirement;
|
||||
use uv_cache::Cache;
|
||||
use uv_interpreter::{Interpreter, PythonEnvironment};
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
/// Avoid cyclic crate dependencies between resolver, installer and builder.
|
||||
///
|
||||
/// To resolve the dependencies of a packages, we may need to build one or more source
|
||||
/// distributions. To building a source distribution, we need to create a virtual environment from
|
||||
/// the same base python as we use for the root resolution, resolve the build requirements
|
||||
/// (potentially which nested source distributions, recursing a level deeper), installing
|
||||
/// them and then build. The installer, the resolver and the source distribution builder are each in
|
||||
/// their own crate. To avoid circular crate dependencies, this type dispatches between the three
|
||||
/// crates with its three main methods ([`BuildContext::resolve`], [`BuildContext::install`] and
|
||||
/// [`BuildContext::setup_build`]).
|
||||
///
|
||||
/// The overall main crate structure looks like this:
|
||||
///
|
||||
/// ```text
|
||||
/// ┌────────────────┐
|
||||
/// │ uv │
|
||||
/// └───────▲────────┘
|
||||
/// │
|
||||
/// │
|
||||
/// ┌───────┴────────┐
|
||||
/// ┌─────────►│ uv-dispatch │◄─────────┐
|
||||
/// │ └───────▲────────┘ │
|
||||
/// │ │ │
|
||||
/// │ │ │
|
||||
/// ┌───────┴────────┐ ┌───────┴────────┐ ┌────────┴───────┐
|
||||
/// │ uv-resolver │ │ uv-installer │ │ uv-build │
|
||||
/// └───────▲────────┘ └───────▲────────┘ └────────▲───────┘
|
||||
/// │ │ │
|
||||
/// └─────────────┐ │ ┌──────────────┘
|
||||
/// ┌──┴────┴────┴───┐
|
||||
/// │ uv-traits │
|
||||
/// └────────────────┘
|
||||
/// ```
|
||||
///
|
||||
/// Put in a different way, this trait allows `uv-resolver` to depend on `uv-build` and
|
||||
/// `uv-build` to depend on `uv-resolver` which having actual crate dependencies between
|
||||
/// them.
|
||||
|
||||
pub trait BuildContext: Sync {
|
||||
type SourceDistBuilder: SourceBuildTrait + Send + Sync;
|
||||
|
||||
/// Return a reference to the cache.
|
||||
fn cache(&self) -> &Cache;
|
||||
|
||||
/// All (potentially nested) source distribution builds use the same base python and can reuse
|
||||
/// it's metadata (e.g. wheel compatibility tags).
|
||||
fn interpreter(&self) -> &Interpreter;
|
||||
|
||||
/// Whether to enforce build isolation when building source distributions.
|
||||
fn build_isolation(&self) -> BuildIsolation;
|
||||
|
||||
/// Whether source distribution building is disabled. This [`BuildContext::setup_build`] calls
|
||||
/// will fail in this case. This method exists to avoid fetching source distributions if we know
|
||||
/// we can't build them
|
||||
fn no_build(&self) -> &NoBuild;
|
||||
|
||||
/// Whether using pre-built wheels is disabled.
|
||||
fn no_binary(&self) -> &NoBinary;
|
||||
|
||||
/// The index locations being searched.
|
||||
fn index_locations(&self) -> &IndexLocations;
|
||||
|
||||
/// The strategy to use when building source distributions that lack a `pyproject.toml`.
|
||||
fn setup_py_strategy(&self) -> SetupPyStrategy;
|
||||
|
||||
/// Resolve the given requirements into a ready-to-install set of package versions.
|
||||
fn resolve<'a>(
|
||||
&'a self,
|
||||
requirements: &'a [Requirement],
|
||||
) -> impl Future<Output = Result<Resolution>> + Send + 'a;
|
||||
|
||||
/// Install the given set of package versions into the virtual environment. The environment must
|
||||
/// use the same base Python as [`BuildContext::interpreter`]
|
||||
fn install<'a>(
|
||||
&'a self,
|
||||
resolution: &'a Resolution,
|
||||
venv: &'a PythonEnvironment,
|
||||
) -> impl Future<Output = Result<()>> + Send + 'a;
|
||||
|
||||
/// Setup a source distribution build by installing the required dependencies. A wrapper for
|
||||
/// `uv_build::SourceBuild::setup`.
|
||||
///
|
||||
/// For PEP 517 builds, this calls `get_requires_for_build_wheel`.
|
||||
///
|
||||
/// `package_id` is for error reporting only.
|
||||
/// `dist` is for safety checks and may be null for editable builds.
|
||||
fn setup_build<'a>(
|
||||
&'a self,
|
||||
source: &'a Path,
|
||||
subdirectory: Option<&'a Path>,
|
||||
package_id: &'a str,
|
||||
dist: Option<&'a SourceDist>,
|
||||
build_kind: BuildKind,
|
||||
) -> impl Future<Output = Result<Self::SourceDistBuilder>> + Send + 'a;
|
||||
}
|
||||
|
||||
/// A wrapper for `uv_build::SourceBuild` to avoid cyclical crate dependencies.
|
||||
///
|
||||
/// You can either call only `wheel()` to build the wheel directly, call only `metadata()` to get
|
||||
/// the metadata without performing the actual or first call `metadata()` and then `wheel()`.
|
||||
pub trait SourceBuildTrait {
|
||||
/// A wrapper for `uv_build::SourceBuild::get_metadata_without_build`.
|
||||
///
|
||||
/// For PEP 517 builds, this calls `prepare_metadata_for_build_wheel`
|
||||
///
|
||||
/// Returns the metadata directory if we're having a PEP 517 build and the
|
||||
/// `prepare_metadata_for_build_wheel` hook exists
|
||||
fn metadata(&mut self) -> impl Future<Output = Result<Option<PathBuf>>> + Send;
|
||||
|
||||
/// A wrapper for `uv_build::SourceBuild::build`.
|
||||
///
|
||||
/// For PEP 517 builds, this calls `build_wheel`.
|
||||
///
|
||||
/// Returns the filename of the built wheel inside the given `wheel_dir`.
|
||||
fn wheel<'a>(&'a self, wheel_dir: &'a Path)
|
||||
-> impl Future<Output = Result<String>> + Send + 'a;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InFlight {
|
||||
/// The in-flight distribution downloads.
|
||||
pub downloads: OnceMap<DistributionId, Result<CachedDist, String>>,
|
||||
}
|
||||
|
||||
/// Whether to enforce build isolation when building source distributions.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum BuildIsolation<'a> {
|
||||
Isolated,
|
||||
Shared(&'a PythonEnvironment),
|
||||
}
|
||||
|
||||
impl<'a> BuildIsolation<'a> {
|
||||
/// Returns `true` if build isolation is enforced.
|
||||
pub fn is_isolated(&self) -> bool {
|
||||
matches!(self, Self::Isolated)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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, Clone)]
|
||||
pub enum PackageNameSpecifier {
|
||||
All,
|
||||
None,
|
||||
Package(PackageName),
|
||||
}
|
||||
|
||||
impl FromStr for PackageNameSpecifier {
|
||||
type Err = uv_normalize::InvalidNameError;
|
||||
|
||||
fn from_str(name: &str) -> Result<Self, Self::Err> {
|
||||
match name {
|
||||
":all:" => Ok(Self::All),
|
||||
":none:" => Ok(Self::None),
|
||||
_ => Ok(Self::Package(PackageName::from_str(name)?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PackageNameSpecifiers {
|
||||
All,
|
||||
None,
|
||||
Packages(Vec<PackageName>),
|
||||
}
|
||||
|
||||
impl PackageNameSpecifiers {
|
||||
fn from_iter(specifiers: impl Iterator<Item = PackageNameSpecifier>) -> Self {
|
||||
let mut packages = Vec::new();
|
||||
let mut all: bool = false;
|
||||
|
||||
for specifier in specifiers {
|
||||
match specifier {
|
||||
PackageNameSpecifier::None => {
|
||||
packages.clear();
|
||||
all = false;
|
||||
}
|
||||
PackageNameSpecifier::All => {
|
||||
all = true;
|
||||
}
|
||||
PackageNameSpecifier::Package(name) => {
|
||||
packages.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if all {
|
||||
Self::All
|
||||
} else if packages.is_empty() {
|
||||
Self::None
|
||||
} else {
|
||||
Self::Packages(packages)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum NoBinary {
|
||||
/// Allow installation of any wheel.
|
||||
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.
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NoBinary {
|
||||
/// Returns `true` if all wheels are allowed.
|
||||
pub fn is_none(&self) -> bool {
|
||||
matches!(self, Self::None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum NoBuild {
|
||||
/// Allow building wheels from any source distribution.
|
||||
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.
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NoBuild {
|
||||
/// Returns `true` if all builds are allowed.
|
||||
pub fn is_none(&self) -> bool {
|
||||
matches!(self, Self::None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConfigSettingEntry {
|
||||
/// The key of the setting. For example, given `key=value`, this would be `key`.
|
||||
key: String,
|
||||
/// The value of the setting. For example, given `key=value`, this would be `value`.
|
||||
value: String,
|
||||
}
|
||||
|
||||
impl FromStr for ConfigSettingEntry {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let Some((key, value)) = s.split_once('=') else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid config setting: {s} (expected `KEY=VALUE`)"
|
||||
));
|
||||
};
|
||||
Ok(Self {
|
||||
key: key.trim().to_string(),
|
||||
value: value.trim().to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum ConfigSettingValue {
|
||||
/// The value consists of a single string.
|
||||
String(String),
|
||||
/// The value consists of a list of strings.
|
||||
List(Vec<String>),
|
||||
}
|
||||
|
||||
/// Settings to pass to a PEP 517 build backend, structured as a map from (string) key to string or
|
||||
/// list of strings.
|
||||
///
|
||||
/// See: <https://peps.python.org/pep-0517/#config-settings>
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct ConfigSettings(BTreeMap<String, ConfigSettingValue>);
|
||||
|
||||
impl FromIterator<ConfigSettingEntry> for ConfigSettings {
|
||||
fn from_iter<T: IntoIterator<Item = ConfigSettingEntry>>(iter: T) -> Self {
|
||||
let mut config = BTreeMap::default();
|
||||
for entry in iter {
|
||||
match config.entry(entry.key) {
|
||||
Entry::Vacant(vacant) => {
|
||||
vacant.insert(ConfigSettingValue::String(entry.value));
|
||||
}
|
||||
Entry::Occupied(mut occupied) => match occupied.get_mut() {
|
||||
ConfigSettingValue::String(existing) => {
|
||||
let existing = existing.clone();
|
||||
occupied.insert(ConfigSettingValue::List(vec![existing, entry.value]));
|
||||
}
|
||||
ConfigSettingValue::List(existing) => {
|
||||
existing.push(entry.value);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Self(config)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl ConfigSettings {
|
||||
/// Convert the settings to a string that can be passed directly to a PEP 517 build backend.
|
||||
pub fn escape_for_python(&self) -> String {
|
||||
serde_json::to_string(self).expect("Failed to serialize config settings")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for ConfigSettings {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
use serde::ser::SerializeMap;
|
||||
|
||||
let mut map = serializer.serialize_map(Some(self.0.len()))?;
|
||||
for (key, value) in &self.0 {
|
||||
match value {
|
||||
ConfigSettingValue::String(value) => {
|
||||
map.serialize_entry(&key, &value)?;
|
||||
}
|
||||
ConfigSettingValue::List(values) => {
|
||||
map.serialize_entry(&key, &values)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
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(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_config_settings() {
|
||||
let settings: ConfigSettings = vec![
|
||||
ConfigSettingEntry {
|
||||
key: "key".to_string(),
|
||||
value: "value".to_string(),
|
||||
},
|
||||
ConfigSettingEntry {
|
||||
key: "key".to_string(),
|
||||
value: "value2".to_string(),
|
||||
},
|
||||
ConfigSettingEntry {
|
||||
key: "list".to_string(),
|
||||
value: "value3".to_string(),
|
||||
},
|
||||
ConfigSettingEntry {
|
||||
key: "list".to_string(),
|
||||
value: "value4".to_string(),
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
assert_eq!(
|
||||
settings.0.get("key"),
|
||||
Some(&ConfigSettingValue::List(vec![
|
||||
"value".to_string(),
|
||||
"value2".to_string()
|
||||
]))
|
||||
);
|
||||
assert_eq!(
|
||||
settings.0.get("list"),
|
||||
Some(&ConfigSettingValue::List(vec![
|
||||
"value3".to_string(),
|
||||
"value4".to_string()
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "serde")]
|
||||
fn escape_for_python() {
|
||||
let mut settings = ConfigSettings::default();
|
||||
settings.0.insert(
|
||||
"key".to_string(),
|
||||
ConfigSettingValue::String("value".to_string()),
|
||||
);
|
||||
settings.0.insert(
|
||||
"list".to_string(),
|
||||
ConfigSettingValue::List(vec!["value1".to_string(), "value2".to_string()]),
|
||||
);
|
||||
assert_eq!(
|
||||
settings.escape_for_python(),
|
||||
r#"{"key":"value","list":["value1","value2"]}"#
|
||||
);
|
||||
|
||||
let mut settings = ConfigSettings::default();
|
||||
settings.0.insert(
|
||||
"key".to_string(),
|
||||
ConfigSettingValue::String("Hello, \"world!\"".to_string()),
|
||||
);
|
||||
settings.0.insert(
|
||||
"list".to_string(),
|
||||
ConfigSettingValue::List(vec!["'value1'".to_string()]),
|
||||
);
|
||||
assert_eq!(
|
||||
settings.escape_for_python(),
|
||||
r#"{"key":"Hello, \"world!\"","list":["'value1'"]}"#
|
||||
);
|
||||
|
||||
let mut settings = ConfigSettings::default();
|
||||
settings.0.insert(
|
||||
"key".to_string(),
|
||||
ConfigSettingValue::String("val\\1 {}ue".to_string()),
|
||||
);
|
||||
assert_eq!(settings.escape_for_python(), r#"{"key":"val\\1 {}ue"}"#);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "uv-traits"
|
||||
name = "uv-types"
|
||||
version = "0.0.1"
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
|
@ -21,6 +21,7 @@ uv-interpreter = { workspace = true }
|
|||
uv-normalize = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
|
179
crates/uv-types/src/build_options.rs
Normal file
179
crates/uv-types/src/build_options.rs
Normal file
|
@ -0,0 +1,179 @@
|
|||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use pep508_rs::PackageName;
|
||||
use uv_interpreter::PythonEnvironment;
|
||||
|
||||
use crate::{PackageNameSpecifier, PackageNameSpecifiers};
|
||||
|
||||
/// Whether to enforce build isolation when building source distributions.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum BuildIsolation<'a> {
|
||||
Isolated,
|
||||
Shared(&'a PythonEnvironment),
|
||||
}
|
||||
|
||||
impl<'a> BuildIsolation<'a> {
|
||||
/// Returns `true` if build isolation is enforced.
|
||||
pub fn is_isolated(&self) -> bool {
|
||||
matches!(self, Self::Isolated)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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, Clone)]
|
||||
pub enum NoBinary {
|
||||
/// Allow installation of any wheel.
|
||||
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.
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NoBinary {
|
||||
/// Returns `true` if all wheels are allowed.
|
||||
pub fn is_none(&self) -> bool {
|
||||
matches!(self, Self::None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum NoBuild {
|
||||
/// Allow building wheels from any source distribution.
|
||||
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.
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NoBuild {
|
||||
/// Returns `true` if all builds are allowed.
|
||||
pub fn is_none(&self) -> bool {
|
||||
matches!(self, Self::None)
|
||||
}
|
||||
}
|
||||
|
||||
#[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(())
|
||||
}
|
||||
}
|
176
crates/uv-types/src/config_settings.rs
Normal file
176
crates/uv-types/src/config_settings.rs
Normal file
|
@ -0,0 +1,176 @@
|
|||
use std::{
|
||||
collections::{btree_map::Entry, BTreeMap},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConfigSettingEntry {
|
||||
/// The key of the setting. For example, given `key=value`, this would be `key`.
|
||||
key: String,
|
||||
/// The value of the setting. For example, given `key=value`, this would be `value`.
|
||||
value: String,
|
||||
}
|
||||
|
||||
impl FromStr for ConfigSettingEntry {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let Some((key, value)) = s.split_once('=') else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid config setting: {s} (expected `KEY=VALUE`)"
|
||||
));
|
||||
};
|
||||
Ok(Self {
|
||||
key: key.trim().to_string(),
|
||||
value: value.trim().to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum ConfigSettingValue {
|
||||
/// The value consists of a single string.
|
||||
String(String),
|
||||
/// The value consists of a list of strings.
|
||||
List(Vec<String>),
|
||||
}
|
||||
|
||||
/// Settings to pass to a PEP 517 build backend, structured as a map from (string) key to string or
|
||||
/// list of strings.
|
||||
///
|
||||
/// See: <https://peps.python.org/pep-0517/#config-settings>
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct ConfigSettings(BTreeMap<String, ConfigSettingValue>);
|
||||
|
||||
impl FromIterator<ConfigSettingEntry> for ConfigSettings {
|
||||
fn from_iter<T: IntoIterator<Item = ConfigSettingEntry>>(iter: T) -> Self {
|
||||
let mut config = BTreeMap::default();
|
||||
for entry in iter {
|
||||
match config.entry(entry.key) {
|
||||
Entry::Vacant(vacant) => {
|
||||
vacant.insert(ConfigSettingValue::String(entry.value));
|
||||
}
|
||||
Entry::Occupied(mut occupied) => match occupied.get_mut() {
|
||||
ConfigSettingValue::String(existing) => {
|
||||
let existing = existing.clone();
|
||||
occupied.insert(ConfigSettingValue::List(vec![existing, entry.value]));
|
||||
}
|
||||
ConfigSettingValue::List(existing) => {
|
||||
existing.push(entry.value);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Self(config)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl ConfigSettings {
|
||||
/// Convert the settings to a string that can be passed directly to a PEP 517 build backend.
|
||||
pub fn escape_for_python(&self) -> String {
|
||||
serde_json::to_string(self).expect("Failed to serialize config settings")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for ConfigSettings {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
use serde::ser::SerializeMap;
|
||||
|
||||
let mut map = serializer.serialize_map(Some(self.0.len()))?;
|
||||
for (key, value) in &self.0 {
|
||||
match value {
|
||||
ConfigSettingValue::String(value) => {
|
||||
map.serialize_entry(&key, &value)?;
|
||||
}
|
||||
ConfigSettingValue::List(values) => {
|
||||
map.serialize_entry(&key, &values)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn collect_config_settings() {
|
||||
let settings: ConfigSettings = vec![
|
||||
ConfigSettingEntry {
|
||||
key: "key".to_string(),
|
||||
value: "value".to_string(),
|
||||
},
|
||||
ConfigSettingEntry {
|
||||
key: "key".to_string(),
|
||||
value: "value2".to_string(),
|
||||
},
|
||||
ConfigSettingEntry {
|
||||
key: "list".to_string(),
|
||||
value: "value3".to_string(),
|
||||
},
|
||||
ConfigSettingEntry {
|
||||
key: "list".to_string(),
|
||||
value: "value4".to_string(),
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
assert_eq!(
|
||||
settings.0.get("key"),
|
||||
Some(&ConfigSettingValue::List(vec![
|
||||
"value".to_string(),
|
||||
"value2".to_string()
|
||||
]))
|
||||
);
|
||||
assert_eq!(
|
||||
settings.0.get("list"),
|
||||
Some(&ConfigSettingValue::List(vec![
|
||||
"value3".to_string(),
|
||||
"value4".to_string()
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "serde")]
|
||||
fn escape_for_python() {
|
||||
let mut settings = ConfigSettings::default();
|
||||
settings.0.insert(
|
||||
"key".to_string(),
|
||||
ConfigSettingValue::String("value".to_string()),
|
||||
);
|
||||
settings.0.insert(
|
||||
"list".to_string(),
|
||||
ConfigSettingValue::List(vec!["value1".to_string(), "value2".to_string()]),
|
||||
);
|
||||
assert_eq!(
|
||||
settings.escape_for_python(),
|
||||
r#"{"key":"value","list":["value1","value2"]}"#
|
||||
);
|
||||
|
||||
let mut settings = ConfigSettings::default();
|
||||
settings.0.insert(
|
||||
"key".to_string(),
|
||||
ConfigSettingValue::String("Hello, \"world!\"".to_string()),
|
||||
);
|
||||
settings.0.insert(
|
||||
"list".to_string(),
|
||||
ConfigSettingValue::List(vec!["'value1'".to_string()]),
|
||||
);
|
||||
assert_eq!(
|
||||
settings.escape_for_python(),
|
||||
r#"{"key":"Hello, \"world!\"","list":["'value1'"]}"#
|
||||
);
|
||||
|
||||
let mut settings = ConfigSettings::default();
|
||||
settings.0.insert(
|
||||
"key".to_string(),
|
||||
ConfigSettingValue::String("val\\1 {}ue".to_string()),
|
||||
);
|
||||
assert_eq!(settings.escape_for_python(), r#"{"key":"val\\1 {}ue"}"#);
|
||||
}
|
||||
}
|
8
crates/uv-types/src/downloads.rs
Normal file
8
crates/uv-types/src/downloads.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
use distribution_types::{CachedDist, DistributionId};
|
||||
use once_map::OnceMap;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InFlight {
|
||||
/// The in-flight distribution downloads.
|
||||
pub downloads: OnceMap<DistributionId, Result<CachedDist, String>>,
|
||||
}
|
14
crates/uv-types/src/lib.rs
Normal file
14
crates/uv-types/src/lib.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
//! Fundamental types shared across `uv` crates.
|
||||
pub use build_options::*;
|
||||
pub use config_settings::*;
|
||||
pub use downloads::*;
|
||||
pub use name_specifiers::*;
|
||||
pub use package_options::*;
|
||||
pub use traits::*;
|
||||
|
||||
mod build_options;
|
||||
mod config_settings;
|
||||
mod downloads;
|
||||
mod name_specifiers;
|
||||
mod package_options;
|
||||
mod traits;
|
63
crates/uv-types/src/name_specifiers.rs
Normal file
63
crates/uv-types/src/name_specifiers.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use pep508_rs::PackageName;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PackageNameSpecifier {
|
||||
All,
|
||||
None,
|
||||
Package(PackageName),
|
||||
}
|
||||
|
||||
impl FromStr for PackageNameSpecifier {
|
||||
type Err = uv_normalize::InvalidNameError;
|
||||
|
||||
fn from_str(name: &str) -> Result<Self, Self::Err> {
|
||||
match name {
|
||||
":all:" => Ok(Self::All),
|
||||
":none:" => Ok(Self::None),
|
||||
_ => Ok(Self::Package(PackageName::from_str(name)?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Package name specification.
|
||||
///
|
||||
/// Consumes both package names and selection directives for compatibility with pip flags
|
||||
/// such as `--no-binary`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PackageNameSpecifiers {
|
||||
All,
|
||||
None,
|
||||
Packages(Vec<PackageName>),
|
||||
}
|
||||
|
||||
impl PackageNameSpecifiers {
|
||||
pub(crate) fn from_iter(specifiers: impl Iterator<Item = PackageNameSpecifier>) -> Self {
|
||||
let mut packages = Vec::new();
|
||||
let mut all: bool = false;
|
||||
|
||||
for specifier in specifiers {
|
||||
match specifier {
|
||||
PackageNameSpecifier::None => {
|
||||
packages.clear();
|
||||
all = false;
|
||||
}
|
||||
PackageNameSpecifier::All => {
|
||||
all = true;
|
||||
}
|
||||
PackageNameSpecifier::Package(name) => {
|
||||
packages.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if all {
|
||||
Self::All
|
||||
} else if packages.is_empty() {
|
||||
Self::None
|
||||
} else {
|
||||
Self::Packages(packages)
|
||||
}
|
||||
}
|
||||
}
|
74
crates/uv-types/src/package_options.rs
Normal file
74
crates/uv-types/src/package_options.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
use pep508_rs::PackageName;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
/// Whether to reinstall packages.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Reinstall {
|
||||
/// Don't reinstall any packages; respect the existing installation.
|
||||
None,
|
||||
|
||||
/// Reinstall all packages in the plan.
|
||||
All,
|
||||
|
||||
/// Reinstall only the specified packages.
|
||||
Packages(Vec<PackageName>),
|
||||
}
|
||||
|
||||
impl Reinstall {
|
||||
/// Determine the reinstall strategy to use.
|
||||
pub fn from_args(reinstall: bool, reinstall_package: Vec<PackageName>) -> Self {
|
||||
if reinstall {
|
||||
Self::All
|
||||
} else if !reinstall_package.is_empty() {
|
||||
Self::Packages(reinstall_package)
|
||||
} else {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if no packages should be reinstalled.
|
||||
pub fn is_none(&self) -> bool {
|
||||
matches!(self, Self::None)
|
||||
}
|
||||
|
||||
/// Returns `true` if all packages should be reinstalled.
|
||||
pub fn is_all(&self) -> bool {
|
||||
matches!(self, Self::All)
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether to allow package upgrades.
|
||||
#[derive(Debug)]
|
||||
pub enum Upgrade {
|
||||
/// Prefer pinned versions from the existing lockfile, if possible.
|
||||
None,
|
||||
|
||||
/// Allow package upgrades for all packages, ignoring the existing lockfile.
|
||||
All,
|
||||
|
||||
/// Allow package upgrades, but only for the specified packages.
|
||||
Packages(FxHashSet<PackageName>),
|
||||
}
|
||||
|
||||
impl Upgrade {
|
||||
/// Determine the upgrade strategy from the command-line arguments.
|
||||
pub fn from_args(upgrade: bool, upgrade_package: Vec<PackageName>) -> Self {
|
||||
if upgrade {
|
||||
Self::All
|
||||
} else if !upgrade_package.is_empty() {
|
||||
Self::Packages(upgrade_package.into_iter().collect())
|
||||
} else {
|
||||
Self::None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if no packages should be upgraded.
|
||||
pub fn is_none(&self) -> bool {
|
||||
matches!(self, Self::None)
|
||||
}
|
||||
|
||||
/// Returns `true` if all packages should be upgraded.
|
||||
pub fn is_all(&self) -> bool {
|
||||
matches!(self, Self::All)
|
||||
}
|
||||
}
|
129
crates/uv-types/src/traits.rs
Normal file
129
crates/uv-types/src/traits.rs
Normal file
|
@ -0,0 +1,129 @@
|
|||
use std::future::Future;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use distribution_types::{IndexLocations, Resolution, SourceDist};
|
||||
|
||||
use pep508_rs::Requirement;
|
||||
use uv_cache::Cache;
|
||||
use uv_interpreter::{Interpreter, PythonEnvironment};
|
||||
|
||||
use crate::{BuildIsolation, BuildKind, NoBinary, NoBuild, SetupPyStrategy};
|
||||
|
||||
/// Avoids cyclic crate dependencies between resolver, installer and builder.
|
||||
///
|
||||
/// To resolve the dependencies of a packages, we may need to build one or more source
|
||||
/// distributions. To building a source distribution, we need to create a virtual environment from
|
||||
/// the same base python as we use for the root resolution, resolve the build requirements
|
||||
/// (potentially which nested source distributions, recursing a level deeper), installing
|
||||
/// them and then build. The installer, the resolver and the source distribution builder are each in
|
||||
/// their own crate. To avoid circular crate dependencies, this type dispatches between the three
|
||||
/// crates with its three main methods ([`BuildContext::resolve`], [`BuildContext::install`] and
|
||||
/// [`BuildContext::setup_build`]).
|
||||
///
|
||||
/// The overall main crate structure looks like this:
|
||||
///
|
||||
/// ```text
|
||||
/// ┌────────────────┐
|
||||
/// │ uv │
|
||||
/// └───────▲────────┘
|
||||
/// │
|
||||
/// │
|
||||
/// ┌───────┴────────┐
|
||||
/// ┌─────────►│ uv-dispatch │◄─────────┐
|
||||
/// │ └───────▲────────┘ │
|
||||
/// │ │ │
|
||||
/// │ │ │
|
||||
/// ┌───────┴────────┐ ┌───────┴────────┐ ┌────────┴───────┐
|
||||
/// │ uv-resolver │ │ uv-installer │ │ uv-build │
|
||||
/// └───────▲────────┘ └───────▲────────┘ └────────▲───────┘
|
||||
/// │ │ │
|
||||
/// └─────────────┐ │ ┌──────────────┘
|
||||
/// ┌──┴────┴────┴───┐
|
||||
/// │ uv-types │
|
||||
/// └────────────────┘
|
||||
/// ```
|
||||
///
|
||||
/// Put in a different way, the types here allow `uv-resolver` to depend on `uv-build` and
|
||||
/// `uv-build` to depend on `uv-resolver` which having actual crate dependencies between
|
||||
/// them.
|
||||
pub trait BuildContext: Sync {
|
||||
type SourceDistBuilder: SourceBuildTrait + Send + Sync;
|
||||
|
||||
/// Return a reference to the cache.
|
||||
fn cache(&self) -> &Cache;
|
||||
|
||||
/// All (potentially nested) source distribution builds use the same base python and can reuse
|
||||
/// it's metadata (e.g. wheel compatibility tags).
|
||||
fn interpreter(&self) -> &Interpreter;
|
||||
|
||||
/// Whether to enforce build isolation when building source distributions.
|
||||
fn build_isolation(&self) -> BuildIsolation;
|
||||
|
||||
/// Whether source distribution building is disabled. This [`BuildContext::setup_build`] calls
|
||||
/// will fail in this case. This method exists to avoid fetching source distributions if we know
|
||||
/// we can't build them
|
||||
fn no_build(&self) -> &NoBuild;
|
||||
|
||||
/// Whether using pre-built wheels is disabled.
|
||||
fn no_binary(&self) -> &NoBinary;
|
||||
|
||||
/// The index locations being searched.
|
||||
fn index_locations(&self) -> &IndexLocations;
|
||||
|
||||
/// The strategy to use when building source distributions that lack a `pyproject.toml`.
|
||||
fn setup_py_strategy(&self) -> SetupPyStrategy;
|
||||
|
||||
/// Resolve the given requirements into a ready-to-install set of package versions.
|
||||
fn resolve<'a>(
|
||||
&'a self,
|
||||
requirements: &'a [Requirement],
|
||||
) -> impl Future<Output = Result<Resolution>> + Send + 'a;
|
||||
|
||||
/// Install the given set of package versions into the virtual environment. The environment must
|
||||
/// use the same base Python as [`BuildContext::interpreter`]
|
||||
fn install<'a>(
|
||||
&'a self,
|
||||
resolution: &'a Resolution,
|
||||
venv: &'a PythonEnvironment,
|
||||
) -> impl Future<Output = Result<()>> + Send + 'a;
|
||||
|
||||
/// Setup a source distribution build by installing the required dependencies. A wrapper for
|
||||
/// `uv_build::SourceBuild::setup`.
|
||||
///
|
||||
/// For PEP 517 builds, this calls `get_requires_for_build_wheel`.
|
||||
///
|
||||
/// `package_id` is for error reporting only.
|
||||
/// `dist` is for safety checks and may be null for editable builds.
|
||||
fn setup_build<'a>(
|
||||
&'a self,
|
||||
source: &'a Path,
|
||||
subdirectory: Option<&'a Path>,
|
||||
package_id: &'a str,
|
||||
dist: Option<&'a SourceDist>,
|
||||
build_kind: BuildKind,
|
||||
) -> impl Future<Output = Result<Self::SourceDistBuilder>> + Send + 'a;
|
||||
}
|
||||
|
||||
/// A wrapper for `uv_build::SourceBuild` to avoid cyclical crate dependencies.
|
||||
///
|
||||
/// You can either call only `wheel()` to build the wheel directly, call only `metadata()` to get
|
||||
/// the metadata without performing the actual or first call `metadata()` and then `wheel()`.
|
||||
pub trait SourceBuildTrait {
|
||||
/// A wrapper for `uv_build::SourceBuild::get_metadata_without_build`.
|
||||
///
|
||||
/// For PEP 517 builds, this calls `prepare_metadata_for_build_wheel`
|
||||
///
|
||||
/// Returns the metadata directory if we're having a PEP 517 build and the
|
||||
/// `prepare_metadata_for_build_wheel` hook exists
|
||||
fn metadata(&mut self) -> impl Future<Output = Result<Option<PathBuf>>> + Send;
|
||||
|
||||
/// A wrapper for `uv_build::SourceBuild::build`.
|
||||
///
|
||||
/// For PEP 517 builds, this calls `build_wheel`.
|
||||
///
|
||||
/// Returns the filename of the built wheel inside the given `wheel_dir`.
|
||||
fn wheel<'a>(&'a self, wheel_dir: &'a Path)
|
||||
-> impl Future<Output = Result<String>> + Send + 'a;
|
||||
}
|
|
@ -31,7 +31,7 @@ uv-interpreter = { workspace = true }
|
|||
uv-normalize = { workspace = true }
|
||||
uv-requirements = { workspace = true }
|
||||
uv-resolver = { workspace = true, features = ["clap"] }
|
||||
uv-traits = { workspace = true }
|
||||
uv-types = { workspace = true }
|
||||
uv-virtualenv = { workspace = true }
|
||||
uv-warnings = { workspace = true }
|
||||
|
||||
|
|
|
@ -28,15 +28,14 @@ use uv_installer::{Downloader, NoBinary};
|
|||
use uv_interpreter::{find_best_python, PythonEnvironment, PythonVersion};
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
use uv_requirements::{
|
||||
upgrade::{read_lockfile, Upgrade},
|
||||
ExtrasSpecification, NamedRequirementsResolver, RequirementsSource, RequirementsSpecification,
|
||||
SourceTreeResolver,
|
||||
upgrade::read_lockfile, ExtrasSpecification, NamedRequirementsResolver, RequirementsSource,
|
||||
RequirementsSpecification, SourceTreeResolver,
|
||||
};
|
||||
use uv_resolver::{
|
||||
AnnotationStyle, DependencyMode, DisplayResolutionGraph, InMemoryIndex, Manifest,
|
||||
OptionsBuilder, PreReleaseMode, PythonRequirement, ResolutionMode, Resolver,
|
||||
};
|
||||
use uv_traits::{BuildIsolation, ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
use uv_types::{BuildIsolation, ConfigSettings, InFlight, NoBuild, SetupPyStrategy, Upgrade};
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
use crate::commands::reporters::{DownloadReporter, ResolverReporter};
|
||||
|
|
|
@ -34,14 +34,14 @@ use uv_installer::{
|
|||
use uv_interpreter::{Interpreter, PythonEnvironment};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_requirements::{
|
||||
upgrade::Upgrade, ExtrasSpecification, NamedRequirementsResolver, RequirementsSource,
|
||||
RequirementsSpecification, SourceTreeResolver,
|
||||
ExtrasSpecification, NamedRequirementsResolver, RequirementsSource, RequirementsSpecification,
|
||||
SourceTreeResolver,
|
||||
};
|
||||
use uv_resolver::{
|
||||
DependencyMode, InMemoryIndex, Manifest, Options, OptionsBuilder, PreReleaseMode, Preference,
|
||||
ResolutionGraph, ResolutionMode, Resolver,
|
||||
};
|
||||
use uv_traits::{BuildIsolation, ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
use uv_types::{BuildIsolation, ConfigSettings, InFlight, NoBuild, SetupPyStrategy, Upgrade};
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
use crate::commands::reporters::{DownloadReporter, InstallReporter, ResolverReporter};
|
||||
|
|
|
@ -27,7 +27,7 @@ use uv_requirements::{
|
|||
SourceTreeResolver,
|
||||
};
|
||||
use uv_resolver::InMemoryIndex;
|
||||
use uv_traits::{BuildIsolation, ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
use uv_types::{BuildIsolation, ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
use crate::commands::reporters::{
|
||||
|
|
|
@ -21,7 +21,7 @@ use uv_fs::Simplified;
|
|||
use uv_installer::NoBinary;
|
||||
use uv_interpreter::{find_default_python, find_requested_python, Error};
|
||||
use uv_resolver::{InMemoryIndex, OptionsBuilder};
|
||||
use uv_traits::{BuildContext, BuildIsolation, ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
use uv_types::{BuildContext, BuildIsolation, ConfigSettings, InFlight, NoBuild, SetupPyStrategy};
|
||||
|
||||
use crate::commands::ExitStatus;
|
||||
use crate::printer::Printer;
|
||||
|
|
|
@ -19,10 +19,10 @@ use uv_client::Connectivity;
|
|||
use uv_installer::{NoBinary, Reinstall};
|
||||
use uv_interpreter::PythonVersion;
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
use uv_requirements::{upgrade::Upgrade, ExtrasSpecification, RequirementsSource};
|
||||
use uv_requirements::{ExtrasSpecification, RequirementsSource};
|
||||
use uv_resolver::{AnnotationStyle, DependencyMode, PreReleaseMode, ResolutionMode};
|
||||
use uv_traits::{
|
||||
ConfigSettingEntry, ConfigSettings, NoBuild, PackageNameSpecifier, SetupPyStrategy,
|
||||
use uv_types::{
|
||||
ConfigSettingEntry, ConfigSettings, NoBuild, PackageNameSpecifier, SetupPyStrategy, Upgrade,
|
||||
};
|
||||
|
||||
use crate::commands::{extra_name_with_clap_error, ExitStatus, ListFormat, VersionFormat};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue