Add standalone CLI crate (#4456)

## Summary

This PR moves all the CLI code into its own crate, separate from the
`uv` crate. The `uv` crate is iterated on frequently, and the CLI code
comprises a significant portion of it but rarely changes. Removing the
CLI code reduces the `uv` crate size from 1.4MiB to 1.0MiB.
This commit is contained in:
Charlie Marsh 2024-06-24 13:16:22 +03:00 committed by GitHub
parent 3251690327
commit 1eee427c94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 976 additions and 754 deletions

26
Cargo.lock generated
View file

@ -4438,7 +4438,6 @@ dependencies = [
"byteorder",
"chrono",
"clap",
"clap_complete_command",
"distribution-types",
"filetime",
"flate2",
@ -4477,6 +4476,7 @@ dependencies = [
"url",
"uv-auth",
"uv-cache",
"uv-cli",
"uv-client",
"uv-configuration",
"uv-dispatch",
@ -4570,6 +4570,30 @@ dependencies = [
"walkdir",
]
[[package]]
name = "uv-cli"
version = "0.0.1"
dependencies = [
"anstream",
"anyhow",
"clap",
"clap_complete_command",
"distribution-types",
"fs-err",
"insta",
"install-wheel-rs",
"serde",
"url",
"uv-cache",
"uv-configuration",
"uv-normalize",
"uv-resolver",
"uv-settings",
"uv-toolchain",
"uv-version",
"uv-warnings",
]
[[package]]
name = "uv-client"
version = "0.0.1"

View file

@ -30,6 +30,7 @@ requirements-txt = { path = "crates/requirements-txt" }
uv-auth = { path = "crates/uv-auth" }
uv-build = { path = "crates/uv-build" }
uv-cache = { path = "crates/uv-cache" }
uv-cli = { path = "crates/uv-cli" }
uv-client = { path = "crates/uv-client" }
uv-configuration = { path = "crates/uv-configuration" }
uv-dispatch = { path = "crates/uv-dispatch" }

43
crates/uv-cli/Cargo.toml Normal file
View file

@ -0,0 +1,43 @@
[package]
name = "uv-cli"
version = "0.0.1"
description = "The command line interface for the uv binary."
edition = { workspace = true }
rust-version = { workspace = true }
homepage = { workspace = true }
documentation = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
[lints]
workspace = true
[dependencies]
distribution-types = { workspace = true }
install-wheel-rs = { workspace = true, features = ["clap"], default-features = false }
uv-cache = { workspace = true, features = ["clap"] }
uv-configuration = { workspace = true, features = ["clap"] }
uv-normalize = { workspace = true }
uv-resolver = { workspace = true, features = ["clap"] }
uv-settings = { workspace = true, features = ["schemars"] }
uv-toolchain = { workspace = true, features = ["clap", "schemars"]}
uv-version = { workspace = true }
uv-warnings = { workspace = true }
anstream = { workspace = true }
anyhow = { workspace = true }
clap = { workspace = true, features = ["derive"] }
clap_complete_command = { workspace = true }
serde = { workspace = true }
url = { workspace = true }
[dev-dependencies]
insta = { version = "1.36.1", features = ["filters", "json"] }
[features]
default = []
self-update = []
[build-dependencies]
fs-err = { workspace = true }

View file

@ -3,7 +3,7 @@ use clap::{Args, ValueEnum};
use uv_warnings::warn_user;
pub(crate) trait CompatArgs {
pub trait CompatArgs {
fn validate(&self) -> Result<()>;
}
@ -14,7 +14,7 @@ pub(crate) trait CompatArgs {
/// nice user experience to warn, rather than fail, when users pass `--allow-unsafe`.
#[derive(Args)]
#[allow(clippy::struct_excessive_bools)]
pub(crate) struct PipCompileCompatArgs {
pub struct PipCompileCompatArgs {
#[clap(long, hide = true)]
allow_unsafe: bool,
@ -175,7 +175,7 @@ impl CompatArgs for PipCompileCompatArgs {
/// These represent a subset of the `pip list` interface that uv supports by default.
#[derive(Args)]
#[allow(clippy::struct_excessive_bools)]
pub(crate) struct PipListCompatArgs {
pub struct PipListCompatArgs {
#[clap(long, hide = true)]
outdated: bool,
}
@ -200,7 +200,7 @@ impl CompatArgs for crate::compat::PipListCompatArgs {
/// These represent a subset of the `pip-sync` interface that uv supports by default.
#[derive(Args)]
#[allow(clippy::struct_excessive_bools)]
pub(crate) struct PipSyncCompatArgs {
pub struct PipSyncCompatArgs {
#[clap(short, long, hide = true)]
ask: bool,
@ -299,7 +299,7 @@ enum Resolver {
/// These represent a subset of the `virtualenv` interface that uv supports by default.
#[derive(Args)]
#[allow(clippy::struct_excessive_bools)]
pub(crate) struct VenvCompatArgs {
pub struct VenvCompatArgs {
#[clap(long, hide = true)]
clear: bool,
@ -358,7 +358,7 @@ impl CompatArgs for VenvCompatArgs {
/// These represent a subset of the `pip install` interface that uv supports by default.
#[derive(Args)]
#[allow(clippy::struct_excessive_bools)]
pub(crate) struct PipInstallCompatArgs {
pub struct PipInstallCompatArgs {
#[clap(long, hide = false)]
user: bool,
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,351 @@
use uv_cache::Refresh;
use uv_configuration::ConfigSettings;
use uv_resolver::PreReleaseMode;
use uv_settings::{InstallerOptions, PipOptions, ResolverInstallerOptions, ResolverOptions};
use crate::{
BuildArgs, IndexArgs, InstallerArgs, Maybe, RefreshArgs, ResolverArgs, ResolverInstallerArgs,
};
/// Given a boolean flag pair (like `--upgrade` and `--no-upgrade`), resolve the value of the flag.
pub fn flag(yes: bool, no: bool) -> Option<bool> {
match (yes, no) {
(true, false) => Some(true),
(false, true) => Some(false),
(false, false) => None,
(..) => unreachable!("Clap should make this impossible"),
}
}
impl From<RefreshArgs> for Refresh {
fn from(value: RefreshArgs) -> Self {
let RefreshArgs {
refresh,
no_refresh,
refresh_package,
} = value;
Self::from_args(flag(refresh, no_refresh), refresh_package)
}
}
impl From<ResolverArgs> for PipOptions {
fn from(args: ResolverArgs) -> Self {
let ResolverArgs {
index_args,
upgrade,
no_upgrade,
upgrade_package,
index_strategy,
keyring_provider,
resolution,
prerelease,
pre,
config_setting,
exclude_newer,
link_mode,
} = args;
Self {
upgrade: flag(upgrade, no_upgrade),
upgrade_package: Some(upgrade_package),
index_strategy,
keyring_provider,
resolution,
prerelease: if pre {
Some(PreReleaseMode::Allow)
} else {
prerelease
},
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
exclude_newer,
link_mode,
..PipOptions::from(index_args)
}
}
}
impl From<InstallerArgs> for PipOptions {
fn from(args: InstallerArgs) -> Self {
let InstallerArgs {
index_args,
reinstall,
no_reinstall,
reinstall_package,
index_strategy,
keyring_provider,
config_setting,
link_mode,
compile_bytecode,
no_compile_bytecode,
} = args;
Self {
reinstall: flag(reinstall, no_reinstall),
reinstall_package: Some(reinstall_package),
index_strategy,
keyring_provider,
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
link_mode,
compile_bytecode: flag(compile_bytecode, no_compile_bytecode),
..PipOptions::from(index_args)
}
}
}
impl From<ResolverInstallerArgs> for PipOptions {
fn from(args: ResolverInstallerArgs) -> Self {
let ResolverInstallerArgs {
index_args,
upgrade,
no_upgrade,
upgrade_package,
reinstall,
no_reinstall,
reinstall_package,
index_strategy,
keyring_provider,
resolution,
prerelease,
pre,
config_setting,
exclude_newer,
link_mode,
compile_bytecode,
no_compile_bytecode,
} = args;
Self {
upgrade: flag(upgrade, no_upgrade),
upgrade_package: Some(upgrade_package),
reinstall: flag(reinstall, no_reinstall),
reinstall_package: Some(reinstall_package),
index_strategy,
keyring_provider,
resolution,
prerelease: if pre {
Some(PreReleaseMode::Allow)
} else {
prerelease
},
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
exclude_newer,
link_mode,
compile_bytecode: flag(compile_bytecode, no_compile_bytecode),
..PipOptions::from(index_args)
}
}
}
impl From<IndexArgs> for PipOptions {
fn from(args: IndexArgs) -> Self {
let IndexArgs {
index_url,
extra_index_url,
no_index,
find_links,
} = args;
Self {
index_url: index_url.and_then(Maybe::into_option),
extra_index_url: extra_index_url.map(|extra_index_urls| {
extra_index_urls
.into_iter()
.filter_map(Maybe::into_option)
.collect()
}),
no_index: if no_index { Some(true) } else { None },
find_links,
..PipOptions::default()
}
}
}
/// Construct the [`InstallerOptions`] from the [`InstallerArgs`] and [`BuildArgs`].
pub fn installer_options(installer_args: InstallerArgs, build_args: BuildArgs) -> InstallerOptions {
let InstallerArgs {
index_args,
reinstall,
no_reinstall,
reinstall_package,
index_strategy,
keyring_provider,
config_setting,
link_mode,
compile_bytecode,
no_compile_bytecode,
} = installer_args;
let BuildArgs {
no_build,
build,
no_build_package,
no_binary,
binary,
no_binary_package,
} = build_args;
InstallerOptions {
index_url: index_args.index_url.and_then(Maybe::into_option),
extra_index_url: index_args.extra_index_url.map(|extra_index_urls| {
extra_index_urls
.into_iter()
.filter_map(Maybe::into_option)
.collect()
}),
no_index: if index_args.no_index {
Some(true)
} else {
None
},
find_links: index_args.find_links,
reinstall: flag(reinstall, no_reinstall),
reinstall_package: Some(reinstall_package),
index_strategy,
keyring_provider,
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
link_mode,
compile_bytecode: flag(compile_bytecode, no_compile_bytecode),
no_build: flag(no_build, build),
no_build_package: Some(no_build_package),
no_binary: flag(no_binary, binary),
no_binary_package: Some(no_binary_package),
}
}
/// Construct the [`ResolverOptions`] from the [`ResolverArgs`] and [`BuildArgs`].
pub fn resolver_options(resolver_args: ResolverArgs, build_args: BuildArgs) -> ResolverOptions {
let ResolverArgs {
index_args,
upgrade,
no_upgrade,
upgrade_package,
index_strategy,
keyring_provider,
resolution,
prerelease,
pre,
config_setting,
exclude_newer,
link_mode,
} = resolver_args;
let BuildArgs {
no_build,
build,
no_build_package,
no_binary,
binary,
no_binary_package,
} = build_args;
ResolverOptions {
index_url: index_args.index_url.and_then(Maybe::into_option),
extra_index_url: index_args.extra_index_url.map(|extra_index_urls| {
extra_index_urls
.into_iter()
.filter_map(Maybe::into_option)
.collect()
}),
no_index: if index_args.no_index {
Some(true)
} else {
None
},
find_links: index_args.find_links,
upgrade: flag(upgrade, no_upgrade),
upgrade_package: Some(upgrade_package),
index_strategy,
keyring_provider,
resolution,
prerelease: if pre {
Some(PreReleaseMode::Allow)
} else {
prerelease
},
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
exclude_newer,
link_mode,
no_build: flag(no_build, build),
no_build_package: Some(no_build_package),
no_binary: flag(no_binary, binary),
no_binary_package: Some(no_binary_package),
}
}
/// Construct the [`ResolverInstallerOptions`] from the [`ResolverInstallerArgs`] and [`BuildArgs`].
pub fn resolver_installer_options(
resolver_installer_args: ResolverInstallerArgs,
build_args: BuildArgs,
) -> ResolverInstallerOptions {
let ResolverInstallerArgs {
index_args,
upgrade,
no_upgrade,
upgrade_package,
reinstall,
no_reinstall,
reinstall_package,
index_strategy,
keyring_provider,
resolution,
prerelease,
pre,
config_setting,
exclude_newer,
link_mode,
compile_bytecode,
no_compile_bytecode,
} = resolver_installer_args;
let BuildArgs {
no_build,
build,
no_build_package,
no_binary,
binary,
no_binary_package,
} = build_args;
ResolverInstallerOptions {
index_url: index_args.index_url.and_then(Maybe::into_option),
extra_index_url: index_args.extra_index_url.map(|extra_index_urls| {
extra_index_urls
.into_iter()
.filter_map(Maybe::into_option)
.collect()
}),
no_index: if index_args.no_index {
Some(true)
} else {
None
},
find_links: index_args.find_links,
upgrade: flag(upgrade, no_upgrade),
upgrade_package: Some(upgrade_package),
reinstall: flag(reinstall, no_reinstall),
reinstall_package: Some(reinstall_package),
index_strategy,
keyring_provider,
resolution,
prerelease: if pre {
Some(PreReleaseMode::Allow)
} else {
prerelease
},
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
exclude_newer,
link_mode,
compile_bytecode: flag(compile_bytecode, no_compile_bytecode),
no_build: flag(no_build, build),
no_build_package: Some(no_build_package),
no_binary: flag(no_binary, binary),
no_binary_package: Some(no_binary_package),
}
}

View file

@ -0,0 +1,149 @@
//! Code for representing uv's release version number.
// See also <https://github.com/astral-sh/ruff/blob/8118d29419055b779719cc96cdf3dacb29ac47c9/crates/ruff/src/version.rs>
use std::fmt;
use serde::Serialize;
/// Information about the git repository where uv was built from.
#[derive(Serialize)]
pub(crate) struct CommitInfo {
short_commit_hash: String,
commit_hash: String,
commit_date: String,
last_tag: Option<String>,
commits_since_last_tag: u32,
}
/// uv's version.
#[derive(Serialize)]
pub struct VersionInfo {
/// uv's version, such as "0.5.1"
version: String,
/// Information about the git commit we may have been built from.
///
/// `None` if not built from a git repo or if retrieval failed.
commit_info: Option<CommitInfo>,
}
impl fmt::Display for VersionInfo {
/// Formatted version information: "<version>[+<commits>] (<commit> <date>)"
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.version)?;
if let Some(ref ci) = self.commit_info {
if ci.commits_since_last_tag > 0 {
write!(f, "+{}", ci.commits_since_last_tag)?;
}
write!(f, " ({} {})", ci.short_commit_hash, ci.commit_date)?;
}
Ok(())
}
}
impl From<VersionInfo> for clap::builder::Str {
fn from(val: VersionInfo) -> Self {
val.to_string().into()
}
}
/// Returns information about uv's version.
pub fn version() -> VersionInfo {
// Environment variables are only read at compile-time
macro_rules! option_env_str {
($name:expr) => {
option_env!($name).map(|s| s.to_string())
};
}
// This version is pulled from Cargo.toml and set by Cargo
let version = uv_version::version().to_string();
// Commit info is pulled from git and set by `build.rs`
let commit_info = option_env_str!("UV_COMMIT_HASH").map(|commit_hash| CommitInfo {
short_commit_hash: option_env_str!("UV_COMMIT_SHORT_HASH").unwrap(),
commit_hash,
commit_date: option_env_str!("UV_COMMIT_DATE").unwrap(),
last_tag: option_env_str!("UV_LAST_TAG"),
commits_since_last_tag: option_env_str!("UV_LAST_TAG_DISTANCE")
.as_deref()
.map_or(0, |value| value.parse::<u32>().unwrap_or(0)),
});
VersionInfo {
version,
commit_info,
}
}
#[cfg(test)]
mod tests {
use insta::{assert_json_snapshot, assert_snapshot};
use super::{CommitInfo, VersionInfo};
#[test]
fn version_formatting() {
let version = VersionInfo {
version: "0.0.0".to_string(),
commit_info: None,
};
assert_snapshot!(version, @"0.0.0");
}
#[test]
fn version_formatting_with_commit_info() {
let version = VersionInfo {
version: "0.0.0".to_string(),
commit_info: Some(CommitInfo {
short_commit_hash: "53b0f5d92".to_string(),
commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(),
last_tag: Some("v0.0.1".to_string()),
commit_date: "2023-10-19".to_string(),
commits_since_last_tag: 0,
}),
};
assert_snapshot!(version, @"0.0.0 (53b0f5d92 2023-10-19)");
}
#[test]
fn version_formatting_with_commits_since_last_tag() {
let version = VersionInfo {
version: "0.0.0".to_string(),
commit_info: Some(CommitInfo {
short_commit_hash: "53b0f5d92".to_string(),
commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(),
last_tag: Some("v0.0.1".to_string()),
commit_date: "2023-10-19".to_string(),
commits_since_last_tag: 24,
}),
};
assert_snapshot!(version, @"0.0.0+24 (53b0f5d92 2023-10-19)");
}
#[test]
fn version_serializable() {
let version = VersionInfo {
version: "0.0.0".to_string(),
commit_info: Some(CommitInfo {
short_commit_hash: "53b0f5d92".to_string(),
commit_hash: "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7".to_string(),
last_tag: Some("v0.0.1".to_string()),
commit_date: "2023-10-19".to_string(),
commits_since_last_tag: 0,
}),
};
assert_json_snapshot!(version, @r###"
{
"version": "0.0.0",
"commit_info": {
"short_commit_hash": "53b0f5d92",
"commit_hash": "53b0f5d924110e5b26fbf09f6fd3a03d67b475b7",
"commit_date": "2023-10-19",
"last_tag": "v0.0.1",
"commits_since_last_tag": 0
}
}
"###);
}
}

View file

@ -15,15 +15,16 @@ workspace = true
[dependencies]
distribution-types = { workspace = true }
install-wheel-rs = { workspace = true, features = ["clap"], default-features = false }
install-wheel-rs = { workspace = true, default-features = false }
pep440_rs = { workspace = true }
pep508_rs = { workspace = true }
platform-tags = { workspace = true }
pypi-types = { workspace = true }
uv-auth = { workspace = true }
uv-cache = { workspace = true, features = ["clap"] }
uv-cache = { workspace = true }
uv-cli = { workspace = true }
uv-client = { workspace = true }
uv-configuration = { workspace = true, features = ["clap"] }
uv-configuration = { workspace = true }
uv-dispatch = { workspace = true }
uv-distribution = { workspace = true }
uv-fs = { workspace = true }
@ -31,9 +32,9 @@ uv-git = { workspace = true }
uv-installer = { workspace = true }
uv-normalize = { workspace = true }
uv-requirements = { workspace = true }
uv-resolver = { workspace = true, features = ["clap"] }
uv-resolver = { workspace = true }
uv-settings = { workspace = true, features = ["schemars"] }
uv-toolchain = { workspace = true, features = ["clap", "schemars"]}
uv-toolchain = { workspace = true, features = ["schemars"]}
uv-types = { workspace = true }
uv-virtualenv = { workspace = true }
uv-warnings = { workspace = true }
@ -43,7 +44,6 @@ anyhow = { workspace = true }
axoupdater = { workspace = true, features = ["github_releases", "tokio"], optional = true }
chrono = { workspace = true }
clap = { workspace = true, features = ["derive", "string", "wrap_help"] }
clap_complete_command = { workspace = true }
flate2 = { workspace = true, default-features = false }
fs-err = { workspace = true, features = ["tokio"] }
futures = { workspace = true }
@ -100,7 +100,5 @@ pypi = []
# Introduces a dependency on Git.
git = []
# Adds self-update functionality.
self-update = ["axoupdater"]
self-update = ["axoupdater", "uv-cli/self-update"]
[build-dependencies]
fs-err = { workspace = true }

View file

@ -9,7 +9,7 @@ pub(crate) use cache_dir::cache_dir;
pub(crate) use cache_prune::cache_prune;
use distribution_types::InstalledMetadata;
pub(crate) use pip::check::pip_check;
pub(crate) use pip::compile::{extra_name_with_clap_error, pip_compile};
pub(crate) use pip::compile::pip_compile;
pub(crate) use pip::freeze::pip_freeze;
pub(crate) use pip::install::pip_install;
pub(crate) use pip::list::pip_list;
@ -111,24 +111,6 @@ pub(super) struct DryRunEvent<T: Display> {
kind: ChangeEventKind,
}
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
pub(crate) enum VersionFormat {
Text,
Json,
}
#[derive(Debug, Default, Clone, clap::ValueEnum)]
pub(crate) enum ListFormat {
/// Display the list of packages in a human-readable table.
#[default]
Columns,
/// Display the list of packages in a `pip freeze`-like format, with one package per line
/// alongside its version.
Freeze,
/// Display the list of packages in a machine-readable JSON format.
Json,
}
/// Compile all Python source files in site-packages to bytecode, to speed up the
/// initial run of any subsequent executions.
///

View file

@ -3,13 +3,11 @@ use std::fmt::Write;
use std::io::stdout;
use std::ops::Deref;
use std::path::Path;
use std::str::FromStr;
use anstream::{eprint, AutoStream, StripStream};
use anyhow::{anyhow, Result};
use itertools::Itertools;
use owo_colors::OwoColorize;
use pypi_types::Requirement;
use tracing::debug;
use distribution_types::{
@ -17,6 +15,7 @@ use distribution_types::{
Verbatim,
};
use install_wheel_rs::linker::LinkMode;
use pypi_types::Requirement;
use uv_auth::store_credentials_from_url;
use uv_cache::Cache;
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
@ -29,7 +28,7 @@ use uv_dispatch::BuildDispatch;
use uv_distribution::DistributionDatabase;
use uv_fs::Simplified;
use uv_git::GitResolver;
use uv_normalize::{ExtraName, PackageName};
use uv_normalize::PackageName;
use uv_requirements::{
upgrade::read_requirements_txt, LookaheadResolver, NamedRequirementsResolver,
RequirementsSource, RequirementsSpecification, SourceTreeResolver,
@ -721,15 +720,6 @@ impl OutputWriter {
}
}
pub(crate) fn extra_name_with_clap_error(arg: &str) -> Result<ExtraName> {
ExtraName::from_str(arg).map_err(|_err| {
anyhow!(
"Extra names must start and end with a letter or digit and may only \
contain -, _, ., and alphanumeric characters"
)
})
}
/// An owned or unowned [`InMemoryIndex`].
enum InMemoryIndexRef<'a> {
Owned(InMemoryIndex),

View file

@ -10,6 +10,7 @@ use unicode_width::UnicodeWidthStr;
use distribution_types::{Diagnostic, InstalledDist, Name};
use uv_cache::Cache;
use uv_cli::ListFormat;
use uv_configuration::PreviewMode;
use uv_fs::Simplified;
use uv_installer::SitePackages;
@ -18,7 +19,6 @@ use uv_toolchain::ToolchainRequest;
use uv_toolchain::{EnvironmentPreference, PythonEnvironment};
use crate::commands::ExitStatus;
use crate::commands::ListFormat;
use crate::printer::Printer;
/// Enumerate the installed packages in the current environment.

View file

@ -7,6 +7,7 @@ use tokio::process::Command;
use tracing::debug;
use uv_cache::Cache;
use uv_cli::ExternalCommand;
use uv_client::{BaseClientBuilder, Connectivity};
use uv_configuration::{Concurrency, ExtrasSpecification, PreviewMode};
use uv_distribution::{ProjectWorkspace, Workspace};
@ -17,7 +18,6 @@ use uv_toolchain::{
};
use uv_warnings::warn_user;
use crate::cli::ExternalCommand;
use crate::commands::pip::operations::Modifications;
use crate::commands::{project, ExitStatus};
use crate::printer::Printer;

View file

@ -6,6 +6,7 @@ use tokio::process::Command;
use tracing::debug;
use uv_cache::Cache;
use uv_cli::ExternalCommand;
use uv_client::Connectivity;
use uv_configuration::{Concurrency, PreviewMode};
use uv_requirements::RequirementsSource;
@ -14,7 +15,6 @@ use uv_toolchain::{
};
use uv_warnings::warn_user;
use crate::cli::ExternalCommand;
use crate::commands::project::update_environment;
use crate::commands::ExitStatus;
use crate::printer::Printer;

View file

@ -1,10 +1,10 @@
use anyhow::Result;
use crate::commands::VersionFormat;
use uv_cli::VersionFormat;
/// Display version information
pub(crate) fn version(output_format: VersionFormat, buffer: &mut dyn std::io::Write) -> Result<()> {
let version_info = crate::version::version();
let version_info = uv_cli::version::version();
match output_format {
VersionFormat::Text => {

View file

@ -9,23 +9,23 @@ use anyhow::Result;
use clap::error::{ContextKind, ContextValue};
use clap::{CommandFactory, Parser};
use owo_colors::OwoColorize;
use settings::PipTreeSettings;
use tracing::{debug, instrument};
use cli::{ToolCommand, ToolNamespace, ToolchainCommand, ToolchainNamespace};
use settings::PipTreeSettings;
use uv_cache::Cache;
use uv_cli::{
compat::CompatArgs, CacheCommand, CacheNamespace, Cli, Commands, PipCommand, PipNamespace,
ProjectCommand,
};
#[cfg(feature = "self-update")]
use uv_cli::{SelfCommand, SelfNamespace};
use uv_cli::{ToolCommand, ToolNamespace, ToolchainCommand, ToolchainNamespace};
use uv_configuration::Concurrency;
use uv_distribution::Workspace;
use uv_requirements::RequirementsSource;
use uv_settings::Combine;
use crate::cli::{
CacheCommand, CacheNamespace, Cli, Commands, PipCommand, PipNamespace, ProjectCommand,
};
#[cfg(feature = "self-update")]
use crate::cli::{SelfCommand, SelfNamespace};
use crate::commands::ExitStatus;
use crate::compat::CompatArgs;
use crate::settings::{
CacheSettings, GlobalSettings, PipCheckSettings, PipCompileSettings, PipFreezeSettings,
PipInstallSettings, PipListSettings, PipShowSettings, PipSyncSettings, PipUninstallSettings,
@ -47,9 +47,7 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
#[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
mod cli;
mod commands;
mod compat;
mod logging;
mod printer;
mod settings;

View file

@ -9,6 +9,13 @@ use install_wheel_rs::linker::LinkMode;
use pep508_rs::RequirementOrigin;
use pypi_types::Requirement;
use uv_cache::{CacheArgs, Refresh};
use uv_cli::options::{flag, installer_options, resolver_installer_options, resolver_options};
use uv_cli::{
AddArgs, ColorChoice, Commands, ExternalCommand, GlobalArgs, ListFormat, LockArgs, Maybe,
PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs,
PipSyncArgs, PipTreeArgs, PipUninstallArgs, RemoveArgs, RunArgs, SyncArgs, ToolRunArgs,
ToolchainFindArgs, ToolchainInstallArgs, ToolchainListArgs, VenvArgs,
};
use uv_client::Connectivity;
use uv_configuration::{
BuildOptions, Concurrency, ConfigSettings, ExtrasSpecification, IndexStrategy,
@ -24,15 +31,7 @@ use uv_settings::{
};
use uv_toolchain::{Prefix, PythonVersion, Target, ToolchainPreference};
use crate::cli::{
AddArgs, BuildArgs, ColorChoice, Commands, ExternalCommand, GlobalArgs, IndexArgs,
InstallerArgs, LockArgs, Maybe, PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs,
PipListArgs, PipShowArgs, PipSyncArgs, PipTreeArgs, PipUninstallArgs, RefreshArgs, RemoveArgs,
ResolverArgs, ResolverInstallerArgs, RunArgs, SyncArgs, ToolRunArgs, ToolchainFindArgs,
ToolchainInstallArgs, ToolchainListArgs, VenvArgs,
};
use crate::commands::pip::operations::Modifications;
use crate::commands::ListFormat;
/// The resolved global settings to use for any invocation of the CLI.
#[allow(clippy::struct_excessive_bools)]
@ -1707,346 +1706,3 @@ fn parse_failure(name: &str, expected: &str) -> ! {
eprintln!("error: invalid value for {name}, expected {expected}");
process::exit(1)
}
/// Given a boolean flag pair (like `--upgrade` and `--no-upgrade`), resolve the value of the flag.
fn flag(yes: bool, no: bool) -> Option<bool> {
match (yes, no) {
(true, false) => Some(true),
(false, true) => Some(false),
(false, false) => None,
(..) => unreachable!("Clap should make this impossible"),
}
}
impl From<RefreshArgs> for Refresh {
fn from(value: RefreshArgs) -> Self {
let RefreshArgs {
refresh,
no_refresh,
refresh_package,
} = value;
Self::from_args(flag(refresh, no_refresh), refresh_package)
}
}
impl From<ResolverArgs> for PipOptions {
fn from(args: ResolverArgs) -> Self {
let ResolverArgs {
index_args,
upgrade,
no_upgrade,
upgrade_package,
index_strategy,
keyring_provider,
resolution,
prerelease,
pre,
config_setting,
exclude_newer,
link_mode,
} = args;
Self {
upgrade: flag(upgrade, no_upgrade),
upgrade_package: Some(upgrade_package),
index_strategy,
keyring_provider,
resolution,
prerelease: if pre {
Some(PreReleaseMode::Allow)
} else {
prerelease
},
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
exclude_newer,
link_mode,
..PipOptions::from(index_args)
}
}
}
impl From<InstallerArgs> for PipOptions {
fn from(args: InstallerArgs) -> Self {
let InstallerArgs {
index_args,
reinstall,
no_reinstall,
reinstall_package,
index_strategy,
keyring_provider,
config_setting,
link_mode,
compile_bytecode,
no_compile_bytecode,
} = args;
Self {
reinstall: flag(reinstall, no_reinstall),
reinstall_package: Some(reinstall_package),
index_strategy,
keyring_provider,
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
link_mode,
compile_bytecode: flag(compile_bytecode, no_compile_bytecode),
..PipOptions::from(index_args)
}
}
}
impl From<ResolverInstallerArgs> for PipOptions {
fn from(args: ResolverInstallerArgs) -> Self {
let ResolverInstallerArgs {
index_args,
upgrade,
no_upgrade,
upgrade_package,
reinstall,
no_reinstall,
reinstall_package,
index_strategy,
keyring_provider,
resolution,
prerelease,
pre,
config_setting,
exclude_newer,
link_mode,
compile_bytecode,
no_compile_bytecode,
} = args;
Self {
upgrade: flag(upgrade, no_upgrade),
upgrade_package: Some(upgrade_package),
reinstall: flag(reinstall, no_reinstall),
reinstall_package: Some(reinstall_package),
index_strategy,
keyring_provider,
resolution,
prerelease: if pre {
Some(PreReleaseMode::Allow)
} else {
prerelease
},
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
exclude_newer,
link_mode,
compile_bytecode: flag(compile_bytecode, no_compile_bytecode),
..PipOptions::from(index_args)
}
}
}
impl From<IndexArgs> for PipOptions {
fn from(args: IndexArgs) -> Self {
let IndexArgs {
index_url,
extra_index_url,
no_index,
find_links,
} = args;
Self {
index_url: index_url.and_then(Maybe::into_option),
extra_index_url: extra_index_url.map(|extra_index_urls| {
extra_index_urls
.into_iter()
.filter_map(Maybe::into_option)
.collect()
}),
no_index: if no_index { Some(true) } else { None },
find_links,
..PipOptions::default()
}
}
}
/// Construct the [`InstallerOptions`] from the [`InstallerArgs`] and [`BuildArgs`].
fn installer_options(installer_args: InstallerArgs, build_args: BuildArgs) -> InstallerOptions {
let InstallerArgs {
index_args,
reinstall,
no_reinstall,
reinstall_package,
index_strategy,
keyring_provider,
config_setting,
link_mode,
compile_bytecode,
no_compile_bytecode,
} = installer_args;
let BuildArgs {
no_build,
build,
no_build_package,
no_binary,
binary,
no_binary_package,
} = build_args;
InstallerOptions {
index_url: index_args.index_url.and_then(Maybe::into_option),
extra_index_url: index_args.extra_index_url.map(|extra_index_urls| {
extra_index_urls
.into_iter()
.filter_map(Maybe::into_option)
.collect()
}),
no_index: if index_args.no_index {
Some(true)
} else {
None
},
find_links: index_args.find_links,
reinstall: flag(reinstall, no_reinstall),
reinstall_package: Some(reinstall_package),
index_strategy,
keyring_provider,
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
link_mode,
compile_bytecode: flag(compile_bytecode, no_compile_bytecode),
no_build: flag(no_build, build),
no_build_package: Some(no_build_package),
no_binary: flag(no_binary, binary),
no_binary_package: Some(no_binary_package),
}
}
/// Construct the [`ResolverOptions`] from the [`ResolverArgs`] and [`BuildArgs`].
fn resolver_options(resolver_args: ResolverArgs, build_args: BuildArgs) -> ResolverOptions {
let ResolverArgs {
index_args,
upgrade,
no_upgrade,
upgrade_package,
index_strategy,
keyring_provider,
resolution,
prerelease,
pre,
config_setting,
exclude_newer,
link_mode,
} = resolver_args;
let BuildArgs {
no_build,
build,
no_build_package,
no_binary,
binary,
no_binary_package,
} = build_args;
ResolverOptions {
index_url: index_args.index_url.and_then(Maybe::into_option),
extra_index_url: index_args.extra_index_url.map(|extra_index_urls| {
extra_index_urls
.into_iter()
.filter_map(Maybe::into_option)
.collect()
}),
no_index: if index_args.no_index {
Some(true)
} else {
None
},
find_links: index_args.find_links,
upgrade: flag(upgrade, no_upgrade),
upgrade_package: Some(upgrade_package),
index_strategy,
keyring_provider,
resolution,
prerelease: if pre {
Some(PreReleaseMode::Allow)
} else {
prerelease
},
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
exclude_newer,
link_mode,
no_build: flag(no_build, build),
no_build_package: Some(no_build_package),
no_binary: flag(no_binary, binary),
no_binary_package: Some(no_binary_package),
}
}
/// Construct the [`ResolverInstallerOptions`] from the [`ResolverInstallerArgs`] and [`BuildArgs`].
fn resolver_installer_options(
resolver_installer_args: ResolverInstallerArgs,
build_args: BuildArgs,
) -> ResolverInstallerOptions {
let ResolverInstallerArgs {
index_args,
upgrade,
no_upgrade,
upgrade_package,
reinstall,
no_reinstall,
reinstall_package,
index_strategy,
keyring_provider,
resolution,
prerelease,
pre,
config_setting,
exclude_newer,
link_mode,
compile_bytecode,
no_compile_bytecode,
} = resolver_installer_args;
let BuildArgs {
no_build,
build,
no_build_package,
no_binary,
binary,
no_binary_package,
} = build_args;
ResolverInstallerOptions {
index_url: index_args.index_url.and_then(Maybe::into_option),
extra_index_url: index_args.extra_index_url.map(|extra_index_urls| {
extra_index_urls
.into_iter()
.filter_map(Maybe::into_option)
.collect()
}),
no_index: if index_args.no_index {
Some(true)
} else {
None
},
find_links: index_args.find_links,
upgrade: flag(upgrade, no_upgrade),
upgrade_package: Some(upgrade_package),
reinstall: flag(reinstall, no_reinstall),
reinstall_package: Some(reinstall_package),
index_strategy,
keyring_provider,
resolution,
prerelease: if pre {
Some(PreReleaseMode::Allow)
} else {
prerelease
},
config_settings: config_setting
.map(|config_settings| config_settings.into_iter().collect::<ConfigSettings>()),
exclude_newer,
link_mode,
compile_bytecode: flag(compile_bytecode, no_compile_bytecode),
no_build: flag(no_build, build),
no_build_package: Some(no_build_package),
no_binary: flag(no_binary, binary),
no_binary_package: Some(no_binary_package),
}
}

View file

@ -66,5 +66,6 @@ changelog_contributors = false
version_files = [
"README.md",
"crates/uv/Cargo.toml",
"crates/uv-cli/Cargo.toml",
"crates/uv-version/Cargo.toml",
]