Merge workspace settings with CLI settings (#3045)

## Summary

This PR adds the structs and logic necessary to respect settings from
the workspace. It's a ton of code, but it's mostly mechanical. And,
believe it or not, I pulled out a few refactors in advance to trim down
the code and complexity.

The highlights are:

- All CLI arguments are now `Option`, so that we can detect whether they
were provided (i.e., we can't let Clap fill in the defaults).
- We now have a `*Settings` struct for each command, which merges the
CLI and workspace options (e.g., `PipCompileSettings`).

I've only implemented `PipCompileSettings` for now. If approved, I'll
implement the others prior to merging, but it's very mechanical and I
both didn't want to do the conversion prior to receiving feedback _and_
realized it would make the PR harder to review.
This commit is contained in:
Charlie Marsh 2024-04-17 13:03:29 -04:00 committed by GitHub
parent dcc2c6865c
commit e5d4ea55ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1160 additions and 245 deletions

View File

@ -16,7 +16,7 @@ pub struct CacheArgs {
alias = "no-cache-dir",
env = "UV_NO_CACHE"
)]
no_cache: bool,
pub no_cache: Option<bool>,
/// Path to the cache directory.
///
@ -24,7 +24,31 @@ pub struct CacheArgs {
/// Linux, and `$HOME/.cache/<project_path> {FOLDERID_LocalAppData}/<project_path>/cache/uv`
/// on Windows.
#[arg(global = true, long, env = "UV_CACHE_DIR")]
pub cache_dir: Option<PathBuf>,
}
impl Cache {
/// Prefer, in order:
/// 1. A temporary cache directory, if the user requested `--no-cache`.
/// 2. The specific cache directory specified by the user via `--cache-dir` or `UV_CACHE_DIR`.
/// 3. The system-appropriate cache directory.
/// 4. A `.uv_cache` directory in the current working directory.
///
/// Returns an absolute cache dir.
pub fn from_settings(
no_cache: Option<bool>,
cache_dir: Option<PathBuf>,
) -> Result<Self, io::Error> {
if no_cache.unwrap_or(false) {
Cache::temp()
} else if let Some(cache_dir) = cache_dir {
Cache::from_path(cache_dir)
} else if let Some(project_dirs) = ProjectDirs::from("", "", "uv") {
Cache::from_path(project_dirs.cache_dir())
} else {
Cache::from_path(".uv_cache")
}
}
}
impl TryFrom<CacheArgs> for Cache {
@ -38,14 +62,6 @@ impl TryFrom<CacheArgs> for Cache {
///
/// Returns an absolute cache dir.
fn try_from(value: CacheArgs) -> Result<Self, Self::Error> {
if value.no_cache {
Self::temp()
} else if let Some(cache_dir) = value.cache_dir {
Self::from_path(cache_dir)
} else if let Some(project_dirs) = ProjectDirs::from("", "", "uv") {
Self::from_path(project_dirs.cache_dir())
} else {
Self::from_path(".uv_cache")
}
Cache::from_settings(value.no_cache, value.cache_dir)
}
}

View File

@ -5,7 +5,7 @@ use serde::Deserialize;
use distribution_types::{FlatIndexLocation, IndexUrl};
use install_wheel_rs::linker::LinkMode;
use uv_configuration::{ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier};
use uv_normalize::PackageName;
use uv_normalize::{ExtraName, PackageName};
use uv_resolver::{AnnotationStyle, ExcludeNewer, PreReleaseMode, ResolutionMode};
use uv_toolchain::PythonVersion;
@ -28,10 +28,8 @@ pub(crate) struct Tools {
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
pub quiet: Option<bool>,
pub verbose: Option<bool>,
pub native_tls: Option<bool>,
pub no_cache: bool,
pub no_cache: Option<bool>,
pub cache_dir: Option<PathBuf>,
pub pip: Option<PipOptions>,
}
@ -41,10 +39,12 @@ pub struct Options {
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct PipOptions {
pub python: Option<String>,
pub system: Option<bool>,
pub break_system_packages: Option<bool>,
pub offline: Option<bool>,
pub index_url: Option<IndexUrl>,
pub extra_index_url: Option<IndexUrl>,
pub extra_index_url: Option<Vec<IndexUrl>>,
pub no_index: Option<bool>,
pub find_links: Option<Vec<FlatIndexLocation>>,
pub index_strategy: Option<IndexStrategy>,
@ -53,21 +53,29 @@ pub struct PipOptions {
pub no_binary: Option<Vec<PackageNameSpecifier>>,
pub only_binary: Option<Vec<PackageNameSpecifier>>,
pub no_build_isolation: Option<bool>,
pub strict: Option<bool>,
pub extra: Option<Vec<ExtraName>>,
pub all_extras: Option<bool>,
pub no_deps: Option<bool>,
pub resolution: Option<ResolutionMode>,
pub prerelease: Option<PreReleaseMode>,
pub output_file: Option<PathBuf>,
pub no_strip_extras: Option<bool>,
pub no_annotate: Option<bool>,
pub no_header: Option<bool>,
pub custom_compile_command: Option<String>,
pub generate_hashes: Option<bool>,
pub legacy_setup_py: Option<bool>,
pub config_setting: Option<ConfigSettings>,
pub config_settings: Option<ConfigSettings>,
pub python_version: Option<PythonVersion>,
pub exclude_newer: Option<ExcludeNewer>,
pub no_emit_package: Option<Vec<PackageName>>,
pub emit_index_url: Option<bool>,
pub emit_find_links: Option<bool>,
pub emit_marker_expression: Option<bool>,
pub emit_index_annotation: Option<bool>,
pub annotation_style: Option<AnnotationStyle>,
pub require_hashes: Option<bool>,
pub link_mode: Option<LinkMode>,
pub compile_bytecode: Option<bool>,
pub require_hashes: Option<bool>,
}

View File

@ -10,8 +10,8 @@ use crate::{Options, PyProjectToml};
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct Workspace {
options: Options,
root: PathBuf,
pub options: Options,
pub root: PathBuf,
}
impl Workspace {

View File

@ -248,7 +248,7 @@ pub(crate) struct PipCompileArgs {
/// Include optional dependencies in the given extra group name; may be provided more than once.
#[arg(long, conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)]
pub(crate) extra: Vec<ExtraName>,
pub(crate) extra: Option<Vec<ExtraName>>,
/// Include all optional dependencies.
#[arg(long, conflicts_with = "extra")]
@ -259,11 +259,20 @@ pub(crate) struct PipCompileArgs {
#[arg(long)]
pub(crate) no_deps: bool,
#[arg(long, value_enum, default_value_t = ResolutionMode::default(), env = "UV_RESOLUTION")]
pub(crate) resolution: ResolutionMode,
/// The strategy to use when selecting between the different compatible versions for a given
/// package requirement.
///
/// By default, `uv` will use the latest compatible version of each package (`highest`).
#[arg(long, value_enum, env = "UV_RESOLUTION")]
pub(crate) resolution: Option<ResolutionMode>,
#[arg(long, value_enum, default_value_t = PreReleaseMode::default(), env = "UV_PRERELEASE")]
pub(crate) prerelease: PreReleaseMode,
/// The strategy to use when considering pre-release versions.
///
/// By default, `uv` will accept pre-releases for packages that _only_ publish pre-releases,
/// along with first-party requirements that contain an explicit pre-release marker in the
/// declared specifiers (`if-necessary-or-explicit`).
#[arg(long, value_enum, env = "UV_PRERELEASE")]
pub(crate) prerelease: Option<PreReleaseMode>,
#[arg(long, hide = true)]
pub(crate) pre: bool,
@ -289,8 +298,10 @@ pub(crate) struct PipCompileArgs {
pub(crate) no_header: bool,
/// Choose the style of the annotation comments, which indicate the source of each package.
#[arg(long, default_value_t=AnnotationStyle::Split, value_enum)]
pub(crate) annotation_style: AnnotationStyle,
///
/// Defaults to `split`.
#[arg(long, value_enum)]
pub(crate) annotation_style: Option<AnnotationStyle>,
/// Change header comment to reflect custom command wrapping `uv pip compile`.
#[arg(long, env = "UV_CUSTOM_COMPILE_COMMAND")]
@ -311,7 +322,7 @@ pub(crate) struct PipCompileArgs {
/// Refresh cached data for a specific package.
#[arg(long)]
pub(crate) refresh_package: Vec<PackageName>,
pub(crate) refresh_package: Option<Vec<PackageName>>,
/// The method to use when installing packages from the global cache.
///
@ -319,8 +330,8 @@ pub(crate) struct PipCompileArgs {
///
/// Defaults to `clone` (also known as Copy-on-Write) on macOS, and `hardlink` on Linux and
/// Windows.
#[arg(long, value_enum, default_value_t = install_wheel_rs::linker::LinkMode::default())]
pub(crate) link_mode: install_wheel_rs::linker::LinkMode,
#[arg(long, value_enum)]
pub(crate) link_mode: Option<install_wheel_rs::linker::LinkMode>,
/// The URL of the Python package index (by default: <https://pypi.org/simple>).
///
@ -343,7 +354,7 @@ pub(crate) struct PipCompileArgs {
/// as it finds it in an index. That is, it isn't possible for `uv` to
/// consider versions of the same package across multiple indexes.
#[arg(long, env = "UV_EXTRA_INDEX_URL", value_delimiter = ' ', value_parser = parse_index_url)]
pub(crate) extra_index_url: Vec<Maybe<IndexUrl>>,
pub(crate) extra_index_url: Option<Vec<Maybe<IndexUrl>>>,
/// Ignore the registry index (e.g., PyPI), instead relying on direct URL dependencies and those
/// discovered via `--find-links`.
@ -353,18 +364,20 @@ pub(crate) struct PipCompileArgs {
/// The strategy to use when resolving against multiple index URLs.
///
/// By default, `uv` will stop at the first index on which a given package is available, and
/// limit resolutions to those present on that first index. This prevents "dependency confusion"
/// attacks, whereby an attack can upload a malicious package under the same name to a secondary
/// index.
#[arg(long, default_value_t, value_enum, env = "UV_INDEX_STRATEGY")]
pub(crate) index_strategy: IndexStrategy,
/// limit resolutions to those present on that first index (`first-match`. This prevents
/// "dependency confusion" attacks, whereby an attack can upload a malicious package under the
/// same name to a secondary
#[arg(long, value_enum, env = "UV_INDEX_STRATEGY")]
pub(crate) index_strategy: Option<IndexStrategy>,
/// Attempt to use `keyring` for authentication for index urls
/// Attempt to use `keyring` for authentication for index URLs.
///
/// Due to not having Python imports, only `--keyring-provider subprocess` argument is currently
/// implemented `uv` will try to use `keyring` via CLI when this flag is used.
#[arg(long, default_value_t, value_enum, env = "UV_KEYRING_PROVIDER")]
pub(crate) keyring_provider: KeyringProviderType,
///
/// Defaults to `disabled`.
#[arg(long, value_enum, env = "UV_KEYRING_PROVIDER")]
pub(crate) keyring_provider: Option<KeyringProviderType>,
/// Locations to search for candidate distributions, beyond those found in the indexes.
///
@ -373,7 +386,7 @@ pub(crate) struct PipCompileArgs {
///
/// If a URL, the page must contain a flat list of links to package files.
#[arg(long, short)]
pub(crate) find_links: Vec<FlatIndexLocation>,
pub(crate) find_links: Option<Vec<FlatIndexLocation>>,
/// Allow package upgrades, ignoring pinned versions in the existing output file.
#[arg(long, short = 'U')]
@ -382,7 +395,7 @@ pub(crate) struct PipCompileArgs {
/// Allow upgrades for a specific package, ignoring pinned versions in the existing output
/// file.
#[arg(long, short = 'P')]
pub(crate) upgrade_package: Vec<PackageName>,
pub(crate) upgrade_package: Option<Vec<PackageName>>,
/// Include distribution hashes in the output file.
#[arg(long)]
@ -418,11 +431,11 @@ pub(crate) struct PipCompileArgs {
/// Multiple packages may be provided. Disable binaries for all packages with `:all:`.
/// Clear previously specified packages with `:none:`.
#[arg(long, conflicts_with = "no_build")]
pub(crate) only_binary: Vec<PackageNameSpecifier>,
pub(crate) only_binary: Option<Vec<PackageNameSpecifier>>,
/// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs.
#[arg(long, short = 'C', alias = "config-settings")]
pub(crate) config_setting: Vec<ConfigSettingEntry>,
pub(crate) config_setting: Option<Vec<ConfigSettingEntry>>,
/// The minimum Python version that should be supported by the compiled requirements (e.g.,
/// `3.7` or `3.7.9`).
@ -442,7 +455,7 @@ pub(crate) struct PipCompileArgs {
/// Specify a package to omit from the output resolution. Its dependencies will still be
/// included in the resolution. Equivalent to pip-compile's `--unsafe-package` option.
#[arg(long, alias = "unsafe-package")]
pub(crate) no_emit_package: Vec<PackageName>,
pub(crate) no_emit_package: Option<Vec<PackageName>>,
/// Include `--index-url` and `--extra-index-url` entries in the generated output file.
#[arg(long)]
@ -506,8 +519,8 @@ pub(crate) struct PipSyncArgs {
///
/// Defaults to `clone` (also known as Copy-on-Write) on macOS, and `hardlink` on Linux and
/// Windows.
#[arg(long, value_enum, default_value_t = install_wheel_rs::linker::LinkMode::default())]
pub(crate) link_mode: install_wheel_rs::linker::LinkMode,
#[arg(long, value_enum)]
pub(crate) link_mode: Option<install_wheel_rs::linker::LinkMode>,
/// The URL of the Python package index (by default: <https://pypi.org/simple>).
///
@ -530,7 +543,7 @@ pub(crate) struct PipSyncArgs {
/// as it finds it in an index. That is, it isn't possible for `uv` to
/// consider versions of the same package across multiple indexes.
#[arg(long, env = "UV_EXTRA_INDEX_URL", value_delimiter = ' ', value_parser = parse_index_url)]
pub(crate) extra_index_url: Vec<Maybe<IndexUrl>>,
pub(crate) extra_index_url: Option<Vec<Maybe<IndexUrl>>>,
/// Locations to search for candidate distributions, beyond those found in the indexes.
///
@ -539,7 +552,7 @@ pub(crate) struct PipSyncArgs {
///
/// If a URL, the page must contain a flat list of links to package files.
#[arg(long, short)]
pub(crate) find_links: Vec<FlatIndexLocation>,
pub(crate) find_links: Option<Vec<FlatIndexLocation>>,
/// Ignore the registry index (e.g., PyPI), instead relying on direct URL dependencies and those
/// discovered via `--find-links`.
@ -549,11 +562,11 @@ pub(crate) struct PipSyncArgs {
/// The strategy to use when resolving against multiple index URLs.
///
/// By default, `uv` will stop at the first index on which a given package is available, and
/// limit resolutions to those present on that first index. This prevents "dependency confusion"
/// attacks, whereby an attack can upload a malicious package under the same name to a secondary
/// index.
#[arg(long, default_value_t, value_enum, env = "UV_INDEX_STRATEGY")]
pub(crate) index_strategy: IndexStrategy,
/// limit resolutions to those present on that first index (`first-match`. This prevents
/// "dependency confusion" attacks, whereby an attack can upload a malicious package under the
/// same name to a secondary
#[arg(long, value_enum, env = "UV_INDEX_STRATEGY")]
pub(crate) index_strategy: Option<IndexStrategy>,
/// Require a matching hash for each requirement.
///
@ -569,12 +582,14 @@ pub(crate) struct PipSyncArgs {
#[arg(long)]
pub(crate) require_hashes: bool,
/// Attempt to use `keyring` for authentication for index urls
/// Attempt to use `keyring` for authentication for index URLs.
///
/// Function's similar to `pip`'s `--keyring-provider subprocess` argument,
/// `uv` will try to use `keyring` via CLI when this flag is used.
#[arg(long, default_value_t, value_enum, env = "UV_KEYRING_PROVIDER")]
pub(crate) keyring_provider: KeyringProviderType,
///
/// Defaults to `disabled`.
#[arg(long, value_enum, env = "UV_KEYRING_PROVIDER")]
pub(crate) keyring_provider: Option<KeyringProviderType>,
/// The Python interpreter into which packages should be installed.
///
@ -640,7 +655,7 @@ pub(crate) struct PipSyncArgs {
/// Multiple packages may be provided. Disable binaries for all packages with `:all:`.
/// Clear previously specified packages with `:none:`.
#[arg(long, conflicts_with = "no_build")]
pub(crate) no_binary: Vec<PackageNameSpecifier>,
pub(crate) no_binary: Option<Vec<PackageNameSpecifier>>,
/// Only use pre-built wheels; don't build source distributions.
///
@ -651,7 +666,7 @@ pub(crate) struct PipSyncArgs {
/// Multiple packages may be provided. Disable binaries for all packages with `:all:`.
/// Clear previously specified packages with `:none:`.
#[arg(long, conflicts_with = "no_build")]
pub(crate) only_binary: Vec<PackageNameSpecifier>,
pub(crate) only_binary: Option<Vec<PackageNameSpecifier>>,
/// Compile Python files to bytecode.
///
@ -671,7 +686,7 @@ pub(crate) struct PipSyncArgs {
/// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs.
#[arg(long, short = 'C', alias = "config-settings")]
pub(crate) config_setting: Vec<ConfigSettingEntry>,
pub(crate) config_setting: Option<Vec<ConfigSettingEntry>>,
/// Validate the virtual environment after completing the installation, to detect packages with
/// missing dependencies or other issues.
@ -722,7 +737,7 @@ pub(crate) struct PipInstallArgs {
/// Include optional dependencies in the given extra group name; may be provided more than once.
#[arg(long, conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)]
pub(crate) extra: Vec<ExtraName>,
pub(crate) extra: Option<Vec<ExtraName>>,
/// Include all optional dependencies.
#[arg(long, conflicts_with = "extra")]
@ -734,7 +749,7 @@ pub(crate) struct PipInstallArgs {
/// Allow upgrade of a specific package.
#[arg(long, short = 'P')]
pub(crate) upgrade_package: Vec<PackageName>,
pub(crate) upgrade_package: Option<Vec<PackageName>>,
/// Reinstall all packages, regardless of whether they're already installed.
#[arg(long, alias = "force-reinstall")]
@ -742,7 +757,7 @@ pub(crate) struct PipInstallArgs {
/// Reinstall a specific package, regardless of whether it's already installed.
#[arg(long)]
pub(crate) reinstall_package: Vec<PackageName>,
pub(crate) reinstall_package: Option<Vec<PackageName>>,
/// Run offline, i.e., without accessing the network.
#[arg(
@ -759,7 +774,7 @@ pub(crate) struct PipInstallArgs {
/// Refresh cached data for a specific package.
#[arg(long)]
pub(crate) refresh_package: Vec<PackageName>,
pub(crate) refresh_package: Option<Vec<PackageName>>,
/// Ignore package dependencies, instead only installing those packages explicitly listed
/// on the command line or in the requirements files.
@ -770,14 +785,23 @@ pub(crate) struct PipInstallArgs {
///
/// Defaults to `clone` (also known as Copy-on-Write) on macOS, and `hardlink` on Linux and
/// Windows.
#[arg(long, value_enum, default_value_t = install_wheel_rs::linker::LinkMode::default())]
pub(crate) link_mode: install_wheel_rs::linker::LinkMode,
#[arg(long, value_enum)]
pub(crate) link_mode: Option<install_wheel_rs::linker::LinkMode>,
#[arg(long, value_enum, default_value_t = ResolutionMode::default(), env = "UV_RESOLUTION")]
pub(crate) resolution: ResolutionMode,
/// The strategy to use when selecting between the different compatible versions for a given
/// package requirement.
///
/// By default, `uv` will use the latest compatible version of each package (`highest`).
#[arg(long, value_enum, env = "UV_RESOLUTION")]
pub(crate) resolution: Option<ResolutionMode>,
#[arg(long, value_enum, default_value_t = PreReleaseMode::default(), env = "UV_PRERELEASE")]
pub(crate) prerelease: PreReleaseMode,
/// The strategy to use when considering pre-release versions.
///
/// By default, `uv` will accept pre-releases for packages that _only_ publish pre-releases,
/// along with first-party requirements that contain an explicit pre-release marker in the
/// declared specifiers (`if-necessary-or-explicit`).
#[arg(long, value_enum, env = "UV_PRERELEASE")]
pub(crate) prerelease: Option<PreReleaseMode>,
#[arg(long, hide = true)]
pub(crate) pre: bool,
@ -803,7 +827,7 @@ pub(crate) struct PipInstallArgs {
/// as it finds it in an index. That is, it isn't possible for `uv` to
/// consider versions of the same package across multiple indexes.
#[arg(long, env = "UV_EXTRA_INDEX_URL", value_delimiter = ' ', value_parser = parse_index_url)]
pub(crate) extra_index_url: Vec<Maybe<IndexUrl>>,
pub(crate) extra_index_url: Option<Vec<Maybe<IndexUrl>>>,
/// Locations to search for candidate distributions, beyond those found in the indexes.
///
@ -812,7 +836,7 @@ pub(crate) struct PipInstallArgs {
///
/// If a URL, the page must contain a flat list of links to package files.
#[arg(long, short)]
pub(crate) find_links: Vec<FlatIndexLocation>,
pub(crate) find_links: Option<Vec<FlatIndexLocation>>,
/// Ignore the registry index (e.g., PyPI), instead relying on direct URL dependencies and those
/// discovered via `--find-links`.
@ -822,11 +846,11 @@ pub(crate) struct PipInstallArgs {
/// The strategy to use when resolving against multiple index URLs.
///
/// By default, `uv` will stop at the first index on which a given package is available, and
/// limit resolutions to those present on that first index. This prevents "dependency confusion"
/// attacks, whereby an attack can upload a malicious package under the same name to a secondary
/// index.
#[arg(long, default_value_t, value_enum, env = "UV_INDEX_STRATEGY")]
pub(crate) index_strategy: IndexStrategy,
/// limit resolutions to those present on that first index (`first-match`. This prevents
/// "dependency confusion" attacks, whereby an attack can upload a malicious package under the
/// same name to a secondary
#[arg(long, value_enum, env = "UV_INDEX_STRATEGY")]
pub(crate) index_strategy: Option<IndexStrategy>,
/// Require a matching hash for each requirement.
///
@ -842,12 +866,14 @@ pub(crate) struct PipInstallArgs {
#[arg(long)]
pub(crate) require_hashes: bool,
/// Attempt to use `keyring` for authentication for index urls
/// Attempt to use `keyring` for authentication for index URLs.
///
/// Due to not having Python imports, only `--keyring-provider subprocess` argument is currently
/// implemented `uv` will try to use `keyring` via CLI when this flag is used.
#[arg(long, default_value_t, value_enum, env = "UV_KEYRING_PROVIDER")]
pub(crate) keyring_provider: KeyringProviderType,
///
/// Defaults to `disabled`.
#[arg(long, value_enum, env = "UV_KEYRING_PROVIDER")]
pub(crate) keyring_provider: Option<KeyringProviderType>,
/// The Python interpreter into which packages should be installed.
///
@ -913,7 +939,7 @@ pub(crate) struct PipInstallArgs {
/// Multiple packages may be provided. Disable binaries for all packages with `:all:`.
/// Clear previously specified packages with `:none:`.
#[arg(long, conflicts_with = "no_build")]
pub(crate) no_binary: Vec<PackageNameSpecifier>,
pub(crate) no_binary: Option<Vec<PackageNameSpecifier>>,
/// Only use pre-built wheels; don't build source distributions.
///
@ -924,7 +950,7 @@ pub(crate) struct PipInstallArgs {
/// Multiple packages may be provided. Disable binaries for all packages with `:all:`.
/// Clear previously specified packages with `:none:`.
#[arg(long, conflicts_with = "no_build")]
pub(crate) only_binary: Vec<PackageNameSpecifier>,
pub(crate) only_binary: Option<Vec<PackageNameSpecifier>>,
/// Compile Python files to bytecode.
///
@ -944,7 +970,7 @@ pub(crate) struct PipInstallArgs {
/// Settings to pass to the PEP 517 build backend, specified as `KEY=VALUE` pairs.
#[arg(long, short = 'C', alias = "config-settings")]
pub(crate) config_setting: Vec<ConfigSettingEntry>,
pub(crate) config_setting: Option<Vec<ConfigSettingEntry>>,
/// Validate the virtual environment after completing the installation, to detect packages with
/// missing dependencies or other issues.
@ -995,8 +1021,10 @@ pub(crate) struct PipUninstallArgs {
///
/// Due to not having Python imports, only `--keyring-provider subprocess` argument is currently
/// implemented `uv` will try to use `keyring` via CLI when this flag is used.
#[arg(long, default_value_t, value_enum, env = "UV_KEYRING_PROVIDER")]
pub(crate) keyring_provider: KeyringProviderType,
///
/// Defaults to `disabled`.
#[arg(long, value_enum, env = "UV_KEYRING_PROVIDER")]
pub(crate) keyring_provider: Option<KeyringProviderType>,
/// Use the system Python to uninstall packages.
///
@ -1209,7 +1237,7 @@ pub(crate) struct VenvArgs {
/// WARNING: `--system` is intended for use in continuous integration (CI) environments and
/// should be used with caution, as it can modify the system Python installation.
#[arg(long, env = "UV_SYSTEM_PYTHON", group = "discovery")]
system: bool,
pub(crate) system: bool,
/// Install seed packages (`pip`, `setuptools`, and `wheel`) into the virtual environment.
#[arg(long)]
@ -1247,8 +1275,8 @@ pub(crate) struct VenvArgs {
///
/// Defaults to `clone` (also known as Copy-on-Write) on macOS, and `hardlink` on Linux and
/// Windows.
#[arg(long, value_enum, default_value_t = install_wheel_rs::linker::LinkMode::default())]
pub(crate) link_mode: install_wheel_rs::linker::LinkMode,
#[arg(long, value_enum)]
pub(crate) link_mode: Option<install_wheel_rs::linker::LinkMode>,
/// The URL of the Python package index (by default: <https://pypi.org/simple>).
///
@ -1271,7 +1299,7 @@ pub(crate) struct VenvArgs {
/// as it finds it in an index. That is, it isn't possible for `uv` to
/// consider versions of the same package across multiple indexes.
#[arg(long, env = "UV_EXTRA_INDEX_URL", value_delimiter = ' ', value_parser = parse_index_url)]
pub(crate) extra_index_url: Vec<Maybe<IndexUrl>>,
pub(crate) extra_index_url: Option<Vec<Maybe<IndexUrl>>>,
/// Ignore the registry index (e.g., PyPI), instead relying on direct URL dependencies and those
/// discovered via `--find-links`.
@ -1281,18 +1309,20 @@ pub(crate) struct VenvArgs {
/// The strategy to use when resolving against multiple index URLs.
///
/// By default, `uv` will stop at the first index on which a given package is available, and
/// limit resolutions to those present on that first index. This prevents "dependency confusion"
/// attacks, whereby an attack can upload a malicious package under the same name to a secondary
/// index.
#[arg(long, default_value_t, value_enum, env = "UV_INDEX_STRATEGY")]
pub(crate) index_strategy: IndexStrategy,
/// limit resolutions to those present on that first index (`first-match`. This prevents
/// "dependency confusion" attacks, whereby an attack can upload a malicious package under the
/// same name to a secondary
#[arg(long, value_enum, env = "UV_INDEX_STRATEGY")]
pub(crate) index_strategy: Option<IndexStrategy>,
/// Attempt to use `keyring` for authentication for index urls
/// Attempt to use `keyring` for authentication for index URLs.
///
/// Due to not having Python imports, only `--keyring-provider subprocess` argument is currently
/// implemented `uv` will try to use `keyring` via CLI when this flag is used.
#[arg(long, default_value_t, value_enum, env = "UV_KEYRING_PROVIDER")]
pub(crate) keyring_provider: KeyringProviderType,
///
/// Defaults to `disabled`.
#[arg(long, value_enum, env = "UV_KEYRING_PROVIDER")]
pub(crate) keyring_provider: Option<KeyringProviderType>,
/// Run offline, i.e., without accessing the network.
#[arg(global = true, long)]

View File

@ -79,9 +79,9 @@ pub(crate) async fn pip_compile(
python_version: Option<PythonVersion>,
exclude_newer: Option<ExcludeNewer>,
annotation_style: AnnotationStyle,
link_mode: LinkMode,
native_tls: bool,
quiet: bool,
link_mode: LinkMode,
cache: Cache,
printer: Printer,
) -> Result<ExitStatus> {

View File

@ -13,15 +13,19 @@ use tracing::instrument;
use distribution_types::IndexLocations;
use uv_cache::{Cache, Refresh};
use uv_client::Connectivity;
use uv_configuration::{ConfigSettings, NoBinary, NoBuild, Reinstall, SetupPyStrategy, Upgrade};
use uv_configuration::{NoBinary, NoBuild, Reinstall, SetupPyStrategy, Upgrade};
use uv_requirements::{ExtrasSpecification, RequirementsSource};
use uv_resolver::{DependencyMode, PreReleaseMode};
use uv_resolver::DependencyMode;
use crate::cli::{CacheCommand, CacheNamespace, Cli, Commands, Maybe, PipCommand, PipNamespace};
use crate::cli::{CacheCommand, CacheNamespace, Cli, Commands, PipCommand, PipNamespace};
#[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,
};
#[cfg(target_os = "windows")]
#[global_allocator]
@ -44,6 +48,7 @@ mod commands;
mod compat;
mod logging;
mod printer;
mod settings;
mod shell;
mod version;
@ -104,7 +109,11 @@ async fn run() -> Result<ExitStatus> {
}
};
let globals = cli.global_args;
// Load the workspace settings.
let workspace = uv_workspace::Workspace::find(env::current_dir()?)?;
// Resolve the global settings.
let globals = GlobalSettings::resolve(cli.global_args, workspace.as_ref());
// Configure the `tracing` crate, which controls internal logging.
#[cfg(feature = "tracing-durations-export")]
@ -134,11 +143,7 @@ async fn run() -> Result<ExitStatus> {
uv_warnings::enable();
}
if globals.no_color {
anstream::ColorChoice::write_global(anstream::ColorChoice::Never);
} else {
anstream::ColorChoice::write_global(globals.color.into());
}
miette::set_hook(Box::new(|_| {
Box::new(
@ -151,7 +156,9 @@ async fn run() -> Result<ExitStatus> {
)
}))?;
let cache = Cache::try_from(cli.cache_args)?;
// Resolve the cache settings.
let cache = CacheSettings::resolve(cli.cache_args, workspace.as_ref());
let cache = Cache::from_settings(cache.no_cache, cache.cache_dir)?;
match cli.command {
Commands::Pip(PipNamespace {
@ -159,6 +166,9 @@ async fn run() -> Result<ExitStatus> {
}) => {
args.compat_args.validate()?;
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipCompileSettings::resolve(args, workspace);
let cache = cache.with_refresh(Refresh::from_args(args.refresh, args.refresh_package));
let requirements = args
.src_file
@ -176,77 +186,70 @@ async fn run() -> Result<ExitStatus> {
.map(RequirementsSource::from_overrides_txt)
.collect::<Vec<_>>();
let index_urls = IndexLocations::new(
args.index_url.and_then(Maybe::into_option),
args.extra_index_url
.into_iter()
.filter_map(Maybe::into_option)
.collect(),
args.find_links,
args.no_index,
args.shared.index_url,
args.shared.extra_index_url,
args.shared.find_links,
args.shared.no_index,
);
let extras = if args.all_extras {
// TODO(charlie): Move into `PipCompileSettings::resolve`.
let extras = if args.shared.all_extras {
ExtrasSpecification::All
} else if args.extra.is_empty() {
} else if args.shared.extra.is_empty() {
ExtrasSpecification::None
} else {
ExtrasSpecification::Some(&args.extra)
ExtrasSpecification::Some(&args.shared.extra)
};
let upgrade = Upgrade::from_args(args.upgrade, args.upgrade_package);
let no_build = NoBuild::from_args(args.only_binary, args.no_build);
let dependency_mode = if args.no_deps {
let no_build = NoBuild::from_args(args.shared.only_binary, args.shared.no_build);
let dependency_mode = if args.shared.no_deps {
DependencyMode::Direct
} else {
DependencyMode::Transitive
};
let prerelease = if args.pre {
PreReleaseMode::Allow
} else {
args.prerelease
};
let setup_py = if args.legacy_setup_py {
let setup_py = if args.shared.legacy_setup_py {
SetupPyStrategy::Setuptools
} else {
SetupPyStrategy::Pep517
};
let config_settings = args.config_setting.into_iter().collect::<ConfigSettings>();
commands::pip_compile(
&requirements,
&constraints,
&overrides,
extras,
args.output_file.as_deref(),
args.resolution,
prerelease,
args.shared.output_file.as_deref(),
args.shared.resolution,
args.shared.prerelease,
dependency_mode,
upgrade,
args.generate_hashes,
args.no_emit_package,
args.no_strip_extras,
!args.no_annotate,
!args.no_header,
args.custom_compile_command,
args.emit_index_url,
args.emit_find_links,
args.emit_marker_expression,
args.emit_index_annotation,
args.shared.generate_hashes,
args.shared.no_emit_package,
args.shared.no_strip_extras,
!args.shared.no_annotate,
!args.shared.no_header,
args.shared.custom_compile_command,
args.shared.emit_index_url,
args.shared.emit_find_links,
args.shared.emit_marker_expression,
args.shared.emit_index_annotation,
index_urls,
args.index_strategy,
args.keyring_provider,
args.shared.index_strategy,
args.shared.keyring_provider,
setup_py,
config_settings,
if args.offline {
args.shared.config_setting,
if args.shared.offline {
Connectivity::Offline
} else {
Connectivity::Online
},
args.no_build_isolation,
args.shared.no_build_isolation,
no_build,
args.python_version,
args.exclude_newer,
args.annotation_style,
args.shared.python_version,
args.shared.exclude_newer,
args.shared.annotation_style,
args.shared.link_mode,
globals.native_tls,
globals.quiet,
args.link_mode,
cache,
printer,
)
@ -257,15 +260,15 @@ async fn run() -> Result<ExitStatus> {
}) => {
args.compat_args.validate()?;
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipSyncSettings::resolve(args, workspace);
let cache = cache.with_refresh(Refresh::from_args(args.refresh, args.refresh_package));
let index_urls = IndexLocations::new(
args.index_url.and_then(Maybe::into_option),
args.extra_index_url
.into_iter()
.filter_map(Maybe::into_option)
.collect(),
args.find_links,
args.no_index,
args.shared.index_url,
args.shared.extra_index_url,
args.shared.find_links,
args.shared.no_index,
);
let sources = args
.src_file
@ -273,38 +276,37 @@ async fn run() -> Result<ExitStatus> {
.map(RequirementsSource::from_requirements_file)
.collect::<Vec<_>>();
let reinstall = Reinstall::from_args(args.reinstall, args.reinstall_package);
let no_binary = NoBinary::from_args(args.no_binary);
let no_build = NoBuild::from_args(args.only_binary, args.no_build);
let setup_py = if args.legacy_setup_py {
let no_binary = NoBinary::from_args(args.shared.no_binary);
let no_build = NoBuild::from_args(args.shared.only_binary, args.shared.no_build);
let setup_py = if args.shared.legacy_setup_py {
SetupPyStrategy::Setuptools
} else {
SetupPyStrategy::Pep517
};
let config_settings = args.config_setting.into_iter().collect::<ConfigSettings>();
commands::pip_sync(
&sources,
&reinstall,
args.link_mode,
args.compile,
args.require_hashes,
args.shared.link_mode,
args.shared.compile_bytecode,
args.shared.require_hashes,
index_urls,
args.index_strategy,
args.keyring_provider,
args.shared.index_strategy,
args.shared.keyring_provider,
setup_py,
if args.offline {
if args.shared.offline {
Connectivity::Offline
} else {
Connectivity::Online
},
&config_settings,
args.no_build_isolation,
&args.shared.config_setting,
args.shared.no_build_isolation,
no_build,
no_binary,
args.strict,
args.python,
args.system,
args.break_system_packages,
args.shared.strict,
args.shared.python,
args.shared.system,
args.shared.break_system_packages,
globals.native_tls,
cache,
printer,
@ -314,6 +316,9 @@ async fn run() -> Result<ExitStatus> {
Commands::Pip(PipNamespace {
command: PipCommand::Install(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipInstallSettings::resolve(args, workspace);
let cache = cache.with_refresh(Refresh::from_args(args.refresh, args.refresh_package));
let requirements = args
.package
@ -337,73 +342,64 @@ async fn run() -> Result<ExitStatus> {
.map(RequirementsSource::from_overrides_txt)
.collect::<Vec<_>>();
let index_urls = IndexLocations::new(
args.index_url.and_then(Maybe::into_option),
args.extra_index_url
.into_iter()
.filter_map(Maybe::into_option)
.collect(),
args.find_links,
args.no_index,
args.shared.index_url,
args.shared.extra_index_url,
args.shared.find_links,
args.shared.no_index,
);
let extras = if args.all_extras {
let extras = if args.shared.all_extras {
ExtrasSpecification::All
} else if args.extra.is_empty() {
} else if args.shared.extra.is_empty() {
ExtrasSpecification::None
} else {
ExtrasSpecification::Some(&args.extra)
ExtrasSpecification::Some(&args.shared.extra)
};
let reinstall = Reinstall::from_args(args.reinstall, args.reinstall_package);
let upgrade = Upgrade::from_args(args.upgrade, args.upgrade_package);
let no_binary = NoBinary::from_args(args.no_binary);
let no_build = NoBuild::from_args(args.only_binary, args.no_build);
let dependency_mode = if args.no_deps {
let no_binary = NoBinary::from_args(args.shared.no_binary);
let no_build = NoBuild::from_args(args.shared.only_binary, args.shared.no_build);
let dependency_mode = if args.shared.no_deps {
DependencyMode::Direct
} else {
DependencyMode::Transitive
};
let prerelease = if args.pre {
PreReleaseMode::Allow
} else {
args.prerelease
};
let setup_py = if args.legacy_setup_py {
let setup_py = if args.shared.legacy_setup_py {
SetupPyStrategy::Setuptools
} else {
SetupPyStrategy::Pep517
};
let config_settings = args.config_setting.into_iter().collect::<ConfigSettings>();
commands::pip_install(
&requirements,
&constraints,
&overrides,
&extras,
args.resolution,
prerelease,
args.shared.resolution,
args.shared.prerelease,
dependency_mode,
upgrade,
index_urls,
args.index_strategy,
args.keyring_provider,
args.shared.index_strategy,
args.shared.keyring_provider,
reinstall,
args.link_mode,
args.compile,
args.require_hashes,
args.shared.link_mode,
args.shared.compile_bytecode,
args.shared.require_hashes,
setup_py,
if args.offline {
if args.shared.offline {
Connectivity::Offline
} else {
Connectivity::Online
},
&config_settings,
args.no_build_isolation,
&args.shared.config_setting,
args.shared.no_build_isolation,
no_build,
no_binary,
args.strict,
args.exclude_newer,
args.python,
args.system,
args.break_system_packages,
args.shared.strict,
args.shared.exclude_newer,
args.shared.python,
args.shared.system,
args.shared.break_system_packages,
globals.native_tls,
cache,
args.dry_run,
@ -414,6 +410,9 @@ async fn run() -> Result<ExitStatus> {
Commands::Pip(PipNamespace {
command: PipCommand::Uninstall(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipUninstallSettings::resolve(args, workspace);
let sources = args
.package
.into_iter()
@ -426,61 +425,84 @@ async fn run() -> Result<ExitStatus> {
.collect::<Vec<_>>();
commands::pip_uninstall(
&sources,
args.python,
args.system,
args.break_system_packages,
args.shared.python,
args.shared.system,
args.shared.break_system_packages,
cache,
if args.offline {
if args.shared.offline {
Connectivity::Offline
} else {
Connectivity::Online
},
globals.native_tls,
args.keyring_provider,
args.shared.keyring_provider,
printer,
)
.await
}
Commands::Pip(PipNamespace {
command: PipCommand::Freeze(args),
}) => commands::pip_freeze(
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipFreezeSettings::resolve(args, workspace);
commands::pip_freeze(
args.exclude_editable,
args.strict,
args.python.as_deref(),
args.system,
args.shared.strict,
args.shared.python.as_deref(),
args.shared.system,
&cache,
printer,
),
)
}
Commands::Pip(PipNamespace {
command: PipCommand::List(args),
}) => {
args.compat_args.validate()?;
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipListSettings::resolve(args, workspace);
commands::pip_list(
args.editable,
args.exclude_editable,
&args.exclude,
&args.format,
args.strict,
args.python.as_deref(),
args.system,
args.shared.strict,
args.shared.python.as_deref(),
args.shared.system,
&cache,
printer,
)
}
Commands::Pip(PipNamespace {
command: PipCommand::Show(args),
}) => commands::pip_show(
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipShowSettings::resolve(args, workspace);
commands::pip_show(
args.package,
args.strict,
args.python.as_deref(),
args.system,
args.shared.strict,
args.shared.python.as_deref(),
args.shared.system,
&cache,
printer,
),
)
}
Commands::Pip(PipNamespace {
command: PipCommand::Check(args),
}) => commands::pip_check(args.python.as_deref(), args.system, &cache, printer),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = PipCheckSettings::resolve(args, workspace);
commands::pip_check(
args.shared.python.as_deref(),
args.shared.system,
&cache,
printer,
)
}
Commands::Cache(CacheNamespace {
command: CacheCommand::Clean(args),
})
@ -497,15 +519,14 @@ async fn run() -> Result<ExitStatus> {
Commands::Venv(args) => {
args.compat_args.validate()?;
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::VenvSettings::resolve(args, workspace);
let index_locations = IndexLocations::new(
args.index_url.and_then(Maybe::into_option),
args.extra_index_url
.into_iter()
.filter_map(Maybe::into_option)
.collect(),
// No find links for the venv subcommand, to keep things simple
Vec::new(),
args.no_index,
args.shared.index_url,
args.shared.extra_index_url,
args.shared.find_links,
args.shared.no_index,
);
// Since we use ".venv" as the default name, we use "." as the default prompt.
@ -519,20 +540,20 @@ async fn run() -> Result<ExitStatus> {
commands::venv(
&args.name,
args.python.as_deref(),
args.link_mode,
args.shared.python.as_deref(),
args.shared.link_mode,
&index_locations,
args.index_strategy,
args.keyring_provider,
args.shared.index_strategy,
args.shared.keyring_provider,
uv_virtualenv::Prompt::from_args(prompt),
args.system_site_packages,
if args.offline {
if args.shared.offline {
Connectivity::Offline
} else {
Connectivity::Online
},
args.seed,
args.exclude_newer,
args.shared.exclude_newer,
globals.native_tls,
&cache,
printer,

840
crates/uv/src/settings.rs Normal file
View File

@ -0,0 +1,840 @@
use std::path::PathBuf;
use distribution_types::{FlatIndexLocation, IndexUrl};
use install_wheel_rs::linker::LinkMode;
use uv_cache::CacheArgs;
use uv_configuration::{ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier};
use uv_normalize::{ExtraName, PackageName};
use uv_resolver::{AnnotationStyle, ExcludeNewer, PreReleaseMode, ResolutionMode};
use uv_toolchain::PythonVersion;
use uv_workspace::{PipOptions, Workspace};
use crate::cli::{
ColorChoice, GlobalArgs, Maybe, PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs,
PipListArgs, PipShowArgs, PipSyncArgs, PipUninstallArgs, VenvArgs,
};
use crate::commands::ListFormat;
/// The resolved global settings to use for any invocation of the CLI.
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)]
pub(crate) struct GlobalSettings {
pub(crate) quiet: bool,
pub(crate) verbose: u8,
pub(crate) color: ColorChoice,
pub(crate) native_tls: bool,
}
impl GlobalSettings {
/// Resolve the [`GlobalSettings`] from the CLI and workspace configuration.
pub(crate) fn resolve(args: GlobalArgs, workspace: Option<&Workspace>) -> Self {
Self {
quiet: args.quiet,
verbose: args.verbose,
color: if args.no_color {
ColorChoice::Never
} else {
args.color
},
native_tls: args.native_tls
|| workspace
.and_then(|workspace| workspace.options.native_tls)
.unwrap_or(false),
}
}
}
/// The resolved cache settings to use for any invocation of the CLI.
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)]
pub(crate) struct CacheSettings {
pub(crate) no_cache: Option<bool>,
pub(crate) cache_dir: Option<PathBuf>,
}
impl CacheSettings {
/// Resolve the [`CacheSettings`] from the CLI and workspace configuration.
pub(crate) fn resolve(args: CacheArgs, workspace: Option<&Workspace>) -> Self {
Self {
no_cache: args
.no_cache
.or(workspace.and_then(|workspace| workspace.options.no_cache)),
cache_dir: args
.cache_dir
.or_else(|| workspace.and_then(|workspace| workspace.options.cache_dir.clone())),
}
}
}
/// The resolved settings to use for a `pip compile` invocation.
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)]
pub(crate) struct PipCompileSettings {
// CLI-only settings.
pub(crate) src_file: Vec<PathBuf>,
pub(crate) constraint: Vec<PathBuf>,
pub(crate) r#override: Vec<PathBuf>,
pub(crate) refresh: bool,
pub(crate) refresh_package: Vec<PackageName>,
pub(crate) upgrade: bool,
pub(crate) upgrade_package: Vec<PackageName>,
// Shared settings.
pub(crate) shared: PipSharedSettings,
}
impl PipCompileSettings {
/// Resolve the [`PipCompileSettings`] from the CLI and workspace configuration.
pub(crate) fn resolve(args: PipCompileArgs, workspace: Option<Workspace>) -> Self {
let PipCompileArgs {
src_file,
constraint,
r#override,
extra,
all_extras,
no_deps,
resolution,
prerelease,
pre,
output_file,
no_strip_extras,
no_annotate,
no_header,
annotation_style,
custom_compile_command,
offline,
refresh,
refresh_package,
link_mode,
index_url,
extra_index_url,
no_index,
index_strategy,
keyring_provider,
find_links,
upgrade,
upgrade_package,
generate_hashes,
legacy_setup_py,
no_build_isolation,
no_build,
only_binary,
config_setting,
python_version,
exclude_newer,
no_emit_package,
emit_index_url,
emit_find_links,
emit_marker_expression,
emit_index_annotation,
compat_args: _,
} = args;
Self {
// CLI-only settings.
src_file,
constraint,
r#override,
refresh,
refresh_package: refresh_package.unwrap_or_default(),
upgrade,
upgrade_package: upgrade_package.unwrap_or_default(),
// Shared settings.
shared: PipSharedSettings::combine(
PipOptions {
offline: Some(offline),
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: Some(no_index),
find_links,
index_strategy,
keyring_provider,
no_build: Some(no_build),
only_binary,
no_build_isolation: Some(no_build_isolation),
extra,
all_extras: Some(all_extras),
no_deps: Some(no_deps),
resolution,
prerelease: if pre {
Some(PreReleaseMode::Allow)
} else {
prerelease
},
output_file,
no_strip_extras: Some(no_strip_extras),
no_annotate: Some(no_annotate),
no_header: Some(no_header),
custom_compile_command,
generate_hashes: Some(generate_hashes),
legacy_setup_py: Some(legacy_setup_py),
config_settings: config_setting.map(|config_settings| {
config_settings.into_iter().collect::<ConfigSettings>()
}),
python_version,
exclude_newer,
no_emit_package,
emit_index_url: Some(emit_index_url),
emit_find_links: Some(emit_find_links),
emit_marker_expression: Some(emit_marker_expression),
emit_index_annotation: Some(emit_index_annotation),
annotation_style,
link_mode,
..PipOptions::default()
},
workspace,
),
}
}
}
/// The resolved settings to use for a `pip sync` invocation.
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)]
pub(crate) struct PipSyncSettings {
// CLI-only settings.
pub(crate) src_file: Vec<PathBuf>,
pub(crate) reinstall: bool,
pub(crate) reinstall_package: Vec<PackageName>,
pub(crate) refresh: bool,
pub(crate) refresh_package: Vec<PackageName>,
// Shared settings.
pub(crate) shared: PipSharedSettings,
}
impl PipSyncSettings {
/// Resolve the [`PipSyncSettings`] from the CLI and workspace configuration.
pub(crate) fn resolve(args: PipSyncArgs, workspace: Option<Workspace>) -> Self {
let PipSyncArgs {
src_file,
reinstall,
reinstall_package,
offline,
refresh,
refresh_package,
link_mode,
index_url,
extra_index_url,
find_links,
no_index,
index_strategy,
require_hashes,
keyring_provider,
python,
system,
break_system_packages,
legacy_setup_py,
no_build_isolation,
no_build,
no_binary,
only_binary,
compile,
no_compile: _,
config_setting,
strict,
compat_args: _,
} = args;
Self {
// CLI-only settings.
src_file,
reinstall,
reinstall_package,
refresh,
refresh_package,
// Shared settings.
shared: PipSharedSettings::combine(
PipOptions {
python,
system: Some(system),
break_system_packages: Some(break_system_packages),
offline: Some(offline),
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: Some(no_index),
find_links,
index_strategy,
keyring_provider,
no_build: Some(no_build),
no_binary,
only_binary,
no_build_isolation: Some(no_build_isolation),
strict: Some(strict),
legacy_setup_py: Some(legacy_setup_py),
config_settings: config_setting.map(|config_settings| {
config_settings.into_iter().collect::<ConfigSettings>()
}),
link_mode,
compile_bytecode: Some(compile),
require_hashes: Some(require_hashes),
..PipOptions::default()
},
workspace,
),
}
}
}
/// The resolved settings to use for a `pip install` invocation.
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)]
pub(crate) struct PipInstallSettings {
// CLI-only settings.
pub(crate) package: Vec<String>,
pub(crate) requirement: Vec<PathBuf>,
pub(crate) editable: Vec<String>,
pub(crate) constraint: Vec<PathBuf>,
pub(crate) r#override: Vec<PathBuf>,
pub(crate) upgrade: bool,
pub(crate) upgrade_package: Vec<PackageName>,
pub(crate) reinstall: bool,
pub(crate) reinstall_package: Vec<PackageName>,
pub(crate) refresh: bool,
pub(crate) refresh_package: Vec<PackageName>,
pub(crate) dry_run: bool,
// Shared settings.
pub(crate) shared: PipSharedSettings,
}
impl PipInstallSettings {
/// Resolve the [`PipInstallSettings`] from the CLI and workspace configuration.
pub(crate) fn resolve(args: PipInstallArgs, workspace: Option<Workspace>) -> Self {
let PipInstallArgs {
package,
requirement,
editable,
constraint,
r#override,
extra,
all_extras,
upgrade,
upgrade_package,
reinstall,
reinstall_package,
offline,
refresh,
refresh_package,
no_deps,
link_mode,
resolution,
prerelease,
pre,
index_url,
extra_index_url,
find_links,
no_index,
index_strategy,
require_hashes,
keyring_provider,
python,
system,
break_system_packages,
legacy_setup_py,
no_build_isolation,
no_build,
no_binary,
only_binary,
compile,
no_compile: _,
config_setting,
strict,
exclude_newer,
dry_run,
} = args;
Self {
// CLI-only settings.
package,
requirement,
editable,
constraint,
r#override,
upgrade,
upgrade_package: upgrade_package.unwrap_or_default(),
reinstall,
reinstall_package: reinstall_package.unwrap_or_default(),
refresh,
refresh_package: refresh_package.unwrap_or_default(),
dry_run,
// Shared settings.
shared: PipSharedSettings::combine(
PipOptions {
python,
system: Some(system),
break_system_packages: Some(break_system_packages),
offline: Some(offline),
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: Some(no_index),
find_links,
index_strategy,
keyring_provider,
no_build: Some(no_build),
no_binary,
only_binary,
no_build_isolation: Some(no_build_isolation),
strict: Some(strict),
extra,
all_extras: Some(all_extras),
no_deps: Some(no_deps),
resolution,
prerelease: if pre {
Some(PreReleaseMode::Allow)
} else {
prerelease
},
legacy_setup_py: Some(legacy_setup_py),
config_settings: config_setting.map(|config_settings| {
config_settings.into_iter().collect::<ConfigSettings>()
}),
exclude_newer,
link_mode,
compile_bytecode: Some(compile),
require_hashes: Some(require_hashes),
..PipOptions::default()
},
workspace,
),
}
}
}
/// The resolved settings to use for a `pip uninstall` invocation.
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)]
pub(crate) struct PipUninstallSettings {
// CLI-only settings.
pub(crate) package: Vec<String>,
pub(crate) requirement: Vec<PathBuf>,
// Shared settings.
pub(crate) shared: PipSharedSettings,
}
impl PipUninstallSettings {
/// Resolve the [`PipUninstallSettings`] from the CLI and workspace configuration.
pub(crate) fn resolve(args: PipUninstallArgs, workspace: Option<Workspace>) -> Self {
let PipUninstallArgs {
package,
requirement,
python,
keyring_provider,
system,
break_system_packages,
offline,
} = args;
Self {
// CLI-only settings.
package,
requirement,
// Shared settings.
shared: PipSharedSettings::combine(
PipOptions {
python,
system: Some(system),
break_system_packages: Some(break_system_packages),
offline: Some(offline),
keyring_provider,
..PipOptions::default()
},
workspace,
),
}
}
}
/// The resolved settings to use for a `pip freeze` invocation.
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)]
pub(crate) struct PipFreezeSettings {
// CLI-only settings.
pub(crate) exclude_editable: bool,
// Shared settings.
pub(crate) shared: PipSharedSettings,
}
impl PipFreezeSettings {
/// Resolve the [`PipFreezeSettings`] from the CLI and workspace configuration.
pub(crate) fn resolve(args: PipFreezeArgs, workspace: Option<Workspace>) -> Self {
let PipFreezeArgs {
exclude_editable,
strict,
python,
system,
} = args;
Self {
// CLI-only settings.
exclude_editable,
// Shared settings.
shared: PipSharedSettings::combine(
PipOptions {
python,
system: Some(system),
strict: Some(strict),
..PipOptions::default()
},
workspace,
),
}
}
}
/// The resolved settings to use for a `pip list` invocation.
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)]
pub(crate) struct PipListSettings {
// CLI-only settings.
pub(crate) editable: bool,
pub(crate) exclude_editable: bool,
pub(crate) exclude: Vec<PackageName>,
pub(crate) format: ListFormat,
// CLI-only settings.
pub(crate) shared: PipSharedSettings,
}
impl PipListSettings {
/// Resolve the [`PipListSettings`] from the CLI and workspace configuration.
pub(crate) fn resolve(args: PipListArgs, workspace: Option<Workspace>) -> Self {
let PipListArgs {
editable,
exclude_editable,
exclude,
format,
strict,
python,
system,
compat_args: _,
} = args;
Self {
// CLI-only settings.
editable,
exclude_editable,
exclude,
format,
// Shared settings.
shared: PipSharedSettings::combine(
PipOptions {
python,
system: Some(system),
strict: Some(strict),
..PipOptions::default()
},
workspace,
),
}
}
}
/// The resolved settings to use for a `pip show` invocation.
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)]
pub(crate) struct PipShowSettings {
// CLI-only settings.
pub(crate) package: Vec<PackageName>,
// CLI-only settings.
pub(crate) shared: PipSharedSettings,
}
impl PipShowSettings {
/// Resolve the [`PipShowSettings`] from the CLI and workspace configuration.
pub(crate) fn resolve(args: PipShowArgs, workspace: Option<Workspace>) -> Self {
let PipShowArgs {
package,
strict,
python,
system,
} = args;
Self {
// CLI-only settings.
package,
// Shared settings.
shared: PipSharedSettings::combine(
PipOptions {
python,
system: Some(system),
strict: Some(strict),
..PipOptions::default()
},
workspace,
),
}
}
}
/// The resolved settings to use for a `pip check` invocation.
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)]
pub(crate) struct PipCheckSettings {
// CLI-only settings.
// Shared settings.
pub(crate) shared: PipSharedSettings,
}
impl PipCheckSettings {
/// Resolve the [`PipCheckSettings`] from the CLI and workspace configuration.
pub(crate) fn resolve(args: PipCheckArgs, workspace: Option<Workspace>) -> Self {
let PipCheckArgs { python, system } = args;
Self {
// Shared settings.
shared: PipSharedSettings::combine(
PipOptions {
python,
system: Some(system),
..PipOptions::default()
},
workspace,
),
}
}
}
/// The resolved settings to use for a `pip check` invocation.
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)]
pub(crate) struct VenvSettings {
// CLI-only settings.
pub(crate) seed: bool,
pub(crate) name: PathBuf,
pub(crate) prompt: Option<String>,
pub(crate) system_site_packages: bool,
// CLI-only settings.
pub(crate) shared: PipSharedSettings,
}
impl VenvSettings {
/// Resolve the [`VenvSettings`] from the CLI and workspace configuration.
pub(crate) fn resolve(args: VenvArgs, workspace: Option<Workspace>) -> Self {
let VenvArgs {
python,
system,
seed,
name,
prompt,
system_site_packages,
link_mode,
index_url,
extra_index_url,
no_index,
index_strategy,
keyring_provider,
offline,
exclude_newer,
compat_args: _,
} = args;
Self {
// CLI-only settings.
seed,
name,
prompt,
system_site_packages,
// Shared settings.
shared: PipSharedSettings::combine(
PipOptions {
python,
system: Some(system),
offline: Some(offline),
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: Some(no_index),
index_strategy,
keyring_provider,
exclude_newer,
link_mode,
..PipOptions::default()
},
workspace,
),
}
}
}
/// The resolved settings to use for an invocation of the `pip` CLI.
///
/// Represents the shared settings that are used across all `pip` commands.
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone)]
pub(crate) struct PipSharedSettings {
pub(crate) python: Option<String>,
pub(crate) system: bool,
pub(crate) break_system_packages: bool,
pub(crate) offline: bool,
pub(crate) index_url: Option<IndexUrl>,
pub(crate) extra_index_url: Vec<IndexUrl>,
pub(crate) no_index: bool,
pub(crate) find_links: Vec<FlatIndexLocation>,
pub(crate) index_strategy: IndexStrategy,
pub(crate) keyring_provider: KeyringProviderType,
pub(crate) no_build: bool,
pub(crate) no_binary: Vec<PackageNameSpecifier>,
pub(crate) only_binary: Vec<PackageNameSpecifier>,
pub(crate) no_build_isolation: bool,
pub(crate) strict: bool,
pub(crate) extra: Vec<ExtraName>,
pub(crate) all_extras: bool,
pub(crate) no_deps: bool,
pub(crate) resolution: ResolutionMode,
pub(crate) prerelease: PreReleaseMode,
pub(crate) output_file: Option<PathBuf>,
pub(crate) no_strip_extras: bool,
pub(crate) no_annotate: bool,
pub(crate) no_header: bool,
pub(crate) custom_compile_command: Option<String>,
pub(crate) generate_hashes: bool,
pub(crate) legacy_setup_py: bool,
pub(crate) config_setting: ConfigSettings,
pub(crate) python_version: Option<PythonVersion>,
pub(crate) exclude_newer: Option<ExcludeNewer>,
pub(crate) no_emit_package: Vec<PackageName>,
pub(crate) emit_index_url: bool,
pub(crate) emit_find_links: bool,
pub(crate) emit_marker_expression: bool,
pub(crate) emit_index_annotation: bool,
pub(crate) annotation_style: AnnotationStyle,
pub(crate) link_mode: LinkMode,
pub(crate) compile_bytecode: bool,
pub(crate) require_hashes: bool,
}
impl PipSharedSettings {
/// Resolve the [`PipSharedSettings`] from the CLI and workspace configuration.
pub(crate) fn combine(args: PipOptions, workspace: Option<Workspace>) -> Self {
let PipOptions {
python,
system,
break_system_packages,
offline,
index_url,
extra_index_url,
no_index,
find_links,
index_strategy,
keyring_provider,
no_build,
no_binary,
only_binary,
no_build_isolation,
strict,
extra,
all_extras,
no_deps,
resolution,
prerelease,
output_file,
no_strip_extras,
no_annotate,
no_header,
custom_compile_command,
generate_hashes,
legacy_setup_py,
config_settings,
python_version,
exclude_newer,
no_emit_package,
emit_index_url,
emit_find_links,
emit_marker_expression,
emit_index_annotation,
annotation_style,
link_mode,
compile_bytecode,
require_hashes,
} = workspace
.and_then(|workspace| workspace.options.pip)
.unwrap_or_default();
Self {
extra: args.extra.or(extra).unwrap_or_default(),
all_extras: args.all_extras.unwrap_or(false) || all_extras.unwrap_or(false),
no_deps: args.no_deps.unwrap_or(false) || no_deps.unwrap_or(false),
resolution: args.resolution.or(resolution).unwrap_or_default(),
prerelease: args.prerelease.or(prerelease).unwrap_or_default(),
output_file: args.output_file.or(output_file),
no_strip_extras: args.no_strip_extras.unwrap_or(false)
|| no_strip_extras.unwrap_or(false),
no_annotate: args.no_annotate.unwrap_or(false) || no_annotate.unwrap_or(false),
no_header: args.no_header.unwrap_or(false) || no_header.unwrap_or(false),
custom_compile_command: args.custom_compile_command.or(custom_compile_command),
annotation_style: args
.annotation_style
.or(annotation_style)
.unwrap_or_default(),
offline: args.offline.unwrap_or(false) || offline.unwrap_or(false),
index_url: args.index_url.or(index_url),
extra_index_url: args.extra_index_url.or(extra_index_url).unwrap_or_default(),
no_index: args.no_index.unwrap_or(false) || no_index.unwrap_or(false),
index_strategy: args.index_strategy.or(index_strategy).unwrap_or_default(),
keyring_provider: args
.keyring_provider
.or(keyring_provider)
.unwrap_or_default(),
find_links: args.find_links.or(find_links).unwrap_or_default(),
generate_hashes: args.generate_hashes.unwrap_or(false)
|| generate_hashes.unwrap_or(false),
legacy_setup_py: args.legacy_setup_py.unwrap_or(false)
|| legacy_setup_py.unwrap_or(false),
no_build_isolation: args.no_build_isolation.unwrap_or(false)
|| no_build_isolation.unwrap_or(false),
no_build: args.no_build.unwrap_or(false) || no_build.unwrap_or(false),
only_binary: args.only_binary.or(only_binary).unwrap_or_default(),
config_setting: args.config_settings.or(config_settings).unwrap_or_default(),
python_version: args.python_version.or(python_version),
exclude_newer: args.exclude_newer.or(exclude_newer),
no_emit_package: args.no_emit_package.or(no_emit_package).unwrap_or_default(),
emit_index_url: args.emit_index_url.unwrap_or(false) || emit_index_url.unwrap_or(false),
emit_find_links: args.emit_find_links.unwrap_or(false)
|| emit_find_links.unwrap_or(false),
emit_marker_expression: args.emit_marker_expression.unwrap_or(false)
|| emit_marker_expression.unwrap_or(false),
emit_index_annotation: args.emit_index_annotation.unwrap_or(false)
|| emit_index_annotation.unwrap_or(false),
link_mode: args.link_mode.or(link_mode).unwrap_or_default(),
require_hashes: args.require_hashes.unwrap_or(false) || require_hashes.unwrap_or(false),
python: args.python.or(python),
system: args.system.unwrap_or(false) || system.unwrap_or(false),
break_system_packages: args.break_system_packages.unwrap_or(false)
|| break_system_packages.unwrap_or(false),
no_binary: args.no_binary.or(no_binary).unwrap_or_default(),
compile_bytecode: args.compile_bytecode.unwrap_or(false)
|| compile_bytecode.unwrap_or(false),
strict: args.strict.unwrap_or(false) || strict.unwrap_or(false),
}
}
}