Manually parse and reconcile Boolean environment variables (#17321)

## Summary

This gives us more flexibility since we can avoid erroring on
"conflicts" when one option is disabled (e.g., `UV_FROZEN=0 uv lock
--check`).

Closes https://github.com/astral-sh/uv/issues/13385.

Closes https://github.com/astral-sh/uv/issues/13316.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Charlie Marsh
2026-01-06 15:43:04 -05:00
committed by GitHub
parent e67dbce3fe
commit cd55d1ce12
19 changed files with 1037 additions and 342 deletions

View File

@@ -9,6 +9,24 @@ doc-valid-idents = [
"ROCm",
"XPU",
"PowerShell",
"UV_DEV",
"UV_FROZEN",
"UV_ISOLATED",
"UV_LOCKED",
"UV_MANAGED_PYTHON",
"UV_NATIVE_TLS",
"UV_NO_DEV",
"UV_NO_EDITABLE",
"UV_NO_ENV_FILE",
"UV_NO_INSTALLER_METADATA",
"UV_NO_MANAGED_PYTHON",
"UV_NO_PROGRESS",
"UV_NO_SYNC",
"UV_OFFLINE",
"UV_PREVIEW",
"UV_SHOW_RESOLUTION",
"UV_VENV_CLEAR",
"UV_VENV_SEED",
".." # Include the defaults
]

View File

@@ -165,33 +165,27 @@ pub struct GlobalArgs {
)]
pub python_preference: Option<PythonPreference>,
/// Require use of uv-managed Python versions.
/// Require use of uv-managed Python versions [env: UV_MANAGED_PYTHON=]
///
/// By default, uv prefers using Python versions it manages. However, it
/// will use system Python versions if a uv-managed Python is not
/// installed. This option disables use of system Python versions.
/// By default, uv prefers using Python versions it manages. However, it will use system Python
/// versions if a uv-managed Python is not installed. This option disables use of system Python
/// versions.
#[arg(
global = true,
long,
help_heading = "Python options",
env = EnvVars::UV_MANAGED_PYTHON,
value_parser = clap::builder::BoolishValueParser::new(),
overrides_with = "no_managed_python",
conflicts_with = "python_preference"
overrides_with = "no_managed_python"
)]
pub managed_python: bool,
/// Disable use of uv-managed Python versions.
/// Disable use of uv-managed Python versions [env: UV_NO_MANAGED_PYTHON=]
///
/// Instead, uv will search for a suitable Python version on the system.
#[arg(
global = true,
long,
help_heading = "Python options",
env = EnvVars::UV_NO_MANAGED_PYTHON,
value_parser = clap::builder::BoolishValueParser::new(),
overrides_with = "managed_python",
conflicts_with = "python_preference"
overrides_with = "managed_python"
)]
pub no_managed_python: bool,
@@ -241,7 +235,7 @@ pub struct GlobalArgs {
)]
pub color: Option<ColorChoice>,
/// Whether to load TLS certificates from the platform's native certificate store.
/// Whether to load TLS certificates from the platform's native store [env: UV_NATIVE_TLS=]
///
/// By default, uv loads certificates from the bundled `webpki-roots` crate. The
/// `webpki-roots` are a reliable set of trust roots from Mozilla, and including them in uv
@@ -250,16 +244,16 @@ pub struct GlobalArgs {
/// However, in some cases, you may want to use the platform's native certificate store,
/// especially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's
/// included in your system's certificate store.
#[arg(global = true, long, env = EnvVars::UV_NATIVE_TLS, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_native_tls"))]
#[arg(global = true, long, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_native_tls"))]
pub native_tls: bool,
#[arg(global = true, long, overrides_with("native_tls"), hide = true)]
pub no_native_tls: bool,
/// Disable network access.
/// Disable network access [env: UV_OFFLINE=]
///
/// When disabled, uv will only use locally cached data and locally available files.
#[arg(global = true, long, overrides_with("no_offline"), env = EnvVars::UV_OFFLINE, value_parser = clap::builder::BoolishValueParser::new())]
#[arg(global = true, long, overrides_with("no_offline"))]
pub offline: bool,
#[arg(global = true, long, overrides_with("offline"), hide = true)]
@@ -286,10 +280,10 @@ pub struct GlobalArgs {
)]
pub allow_insecure_host: Option<Vec<Maybe<TrustedHost>>>,
/// Whether to enable all experimental preview features.
/// Whether to enable all experimental preview features [env: UV_PREVIEW=]
///
/// Preview features may change without warning.
#[arg(global = true, long, hide = true, env = EnvVars::UV_PREVIEW, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_preview"))]
#[arg(global = true, long, hide = true, value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_preview"))]
pub preview: bool,
#[arg(global = true, long, overrides_with("preview"), hide = true)]
@@ -314,13 +308,13 @@ pub struct GlobalArgs {
)]
pub preview_features: Vec<PreviewFeatures>,
/// Avoid discovering a `pyproject.toml` or `uv.toml` file.
/// Avoid discovering a `pyproject.toml` or `uv.toml` file [env: UV_ISOLATED=]
///
/// Normally, configuration files are discovered in the current directory,
/// parent directories, or user configuration directories.
///
/// This option is deprecated in favor of `--no-config`.
#[arg(global = true, long, hide = true, env = EnvVars::UV_ISOLATED, value_parser = clap::builder::BoolishValueParser::new())]
#[arg(global = true, long, hide = true, value_parser = clap::builder::BoolishValueParser::new())]
pub isolated: bool,
/// Show the resolved settings for the current command.
@@ -329,14 +323,15 @@ pub struct GlobalArgs {
#[arg(global = true, long, hide = true)]
pub show_settings: bool,
/// Hide all progress outputs.
/// Hide all progress outputs [env: UV_NO_PROGRESS=]
///
/// For example, spinners or progress bars.
#[arg(global = true, long, env = EnvVars::UV_NO_PROGRESS, value_parser = clap::builder::BoolishValueParser::new())]
#[arg(global = true, long, value_parser = clap::builder::BoolishValueParser::new())]
pub no_progress: bool,
/// Skip writing `uv` installer metadata files (e.g., `INSTALLER`, `REQUESTED`, and `direct_url.json`) to site-packages `.dist-info` directories.
#[arg(global = true, long, hide = true, env = EnvVars::UV_NO_INSTALLER_METADATA, value_parser = clap::builder::BoolishValueParser::new())]
/// Skip writing `uv` installer metadata files (e.g., `INSTALLER`, `REQUESTED`, and
/// `direct_url.json`) to site-packages `.dist-info` directories [env: UV_NO_INSTALLER_METADATA=]
#[arg(global = true, long, hide = true, value_parser = clap::builder::BoolishValueParser::new())]
pub no_installer_metadata: bool,
/// Change to the given directory prior to running the command.
@@ -612,8 +607,8 @@ pub struct VersionArgs {
#[arg(long, value_enum, default_value = "text")]
pub output_format: VersionFormat,
/// Avoid syncing the virtual environment after re-locking the project.
#[arg(long, env = EnvVars::UV_NO_SYNC, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with = "frozen")]
/// Avoid syncing the virtual environment after re-locking the project [env: UV_NO_SYNC=]
#[arg(long)]
pub no_sync: bool,
/// Prefer the active virtual environment over the project's virtual environment.
@@ -629,17 +624,17 @@ pub struct VersionArgs {
#[arg(long, overrides_with = "active", hide = true)]
pub no_active: bool,
/// Assert that the `uv.lock` will remain unchanged.
/// Assert that the `uv.lock` will remain unchanged [env: UV_LOCKED=]
///
/// Requires that the lockfile is up-to-date. If the lockfile is missing or needs to be updated,
/// uv will exit with an error.
#[arg(long, env = EnvVars::UV_LOCKED, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["frozen", "upgrade"])]
#[arg(long, conflicts_with_all = ["frozen", "upgrade"])]
pub locked: bool,
/// Update the version without re-locking the project.
/// Update the version without re-locking the project [env: UV_FROZEN=]
///
/// The project environment will not be synced.
#[arg(long, env = EnvVars::UV_FROZEN, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["locked", "upgrade", "no_sources"])]
#[arg(long, conflicts_with_all = ["locked", "upgrade", "no_sources"])]
pub frozen: bool,
#[command(flatten)]
@@ -3088,18 +3083,19 @@ pub struct VenvArgs {
#[arg(long, alias = "no-workspace")]
pub no_project: bool,
/// Install seed packages (one or more of: `pip`, `setuptools`, and `wheel`) into the virtual environment.
/// Install seed packages (one or more of: `pip`, `setuptools`, and `wheel`) into the virtual
/// environment [env: UV_VENV_SEED=]
///
/// Note that `setuptools` and `wheel` are not included in Python 3.12+ environments.
#[arg(long, value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_VENV_SEED)]
#[arg(long, value_parser = clap::builder::BoolishValueParser::new())]
pub seed: bool,
/// Remove any existing files or directories at the target path.
/// Remove any existing files or directories at the target path [env: UV_VENV_CLEAR=]
///
/// By default, `uv venv` will exit with an error if the given path is non-empty. The
/// `--clear` option will instead clear a non-empty path before creating a new virtual
/// environment.
#[clap(long, short, overrides_with = "allow_existing", value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_VENV_CLEAR)]
#[clap(long, short, overrides_with = "allow_existing", value_parser = clap::builder::BoolishValueParser::new())]
pub clear: bool,
/// Fail without prompting if any existing files or directories are present at the target path.
@@ -3481,7 +3477,7 @@ pub struct RunArgs {
#[arg(long, overrides_with("all_extras"), hide = true)]
pub no_all_extras: bool,
/// Include the development dependency group.
/// Include the development dependency group [env: UV_DEV=]
///
/// Development dependencies are defined via `dependency-groups.dev` or
/// `tool.uv.dev-dependencies` in a `pyproject.toml`.
@@ -3489,16 +3485,16 @@ pub struct RunArgs {
/// This option is an alias for `--group dev`.
///
/// This option is only available when running in a project.
#[arg(long, overrides_with("no_dev"), hide = true, env = EnvVars::UV_DEV, value_parser = clap::builder::BoolishValueParser::new())]
#[arg(long, overrides_with("no_dev"), hide = true, value_parser = clap::builder::BoolishValueParser::new())]
pub dev: bool,
/// Disable the development dependency group.
/// Disable the development dependency group [env: UV_NO_DEV=]
///
/// This option is an alias of `--no-group dev`.
/// See `--no-default-groups` to disable all default groups instead.
///
/// This option is only available when running in a project.
#[arg(long, overrides_with("dev"), env = EnvVars::UV_NO_DEV, value_parser = clap::builder::BoolishValueParser::new())]
#[arg(long, overrides_with("dev"), value_parser = clap::builder::BoolishValueParser::new())]
pub no_dev: bool,
/// Include dependencies from the specified dependency group.
@@ -3557,8 +3553,8 @@ pub struct RunArgs {
pub editable: bool,
/// Install any editable dependencies, including the project and any workspace members, as
/// non-editable.
#[arg(long, overrides_with = "editable", value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_NO_EDITABLE)]
/// non-editable [env: UV_NO_EDITABLE=]
#[arg(long, overrides_with = "editable", value_parser = clap::builder::BoolishValueParser::new())]
pub no_editable: bool,
/// Do not remove extraneous packages present in the environment.
@@ -3579,8 +3575,8 @@ pub struct RunArgs {
#[arg(long, env = EnvVars::UV_ENV_FILE, value_hint = ValueHint::FilePath)]
pub env_file: Vec<String>,
/// Avoid reading environment variables from a `.env` file.
#[arg(long, value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_NO_ENV_FILE)]
/// Avoid reading environment variables from a `.env` file [env: UV_NO_ENV_FILE=]
#[arg(long, value_parser = clap::builder::BoolishValueParser::new())]
pub no_env_file: bool,
/// The command to run.
@@ -3617,7 +3613,7 @@ pub struct RunArgs {
#[arg(long, value_delimiter = ',', value_parser = parse_maybe_file_path, value_hint = ValueHint::FilePath)]
pub with_requirements: Vec<Maybe<PathBuf>>,
/// Run the command in an isolated virtual environment.
/// Run the command in an isolated virtual environment [env: UV_ISOLATED=]
///
/// Usually, the project environment is reused for performance. This option forces a fresh
/// environment to be used for the project, enforcing strict isolation between dependencies and
@@ -3627,7 +3623,7 @@ pub struct RunArgs {
///
/// When used with `--with` or `--with-requirements`, the additional dependencies will still be
/// layered in a second environment.
#[arg(long, env = EnvVars::UV_ISOLATED, value_parser = clap::builder::BoolishValueParser::new())]
#[arg(long, value_parser = clap::builder::BoolishValueParser::new())]
pub isolated: bool,
/// Prefer the active virtual environment over the project's virtual environment.
@@ -3643,27 +3639,27 @@ pub struct RunArgs {
#[arg(long, overrides_with = "active", hide = true)]
pub no_active: bool,
/// Avoid syncing the virtual environment.
/// Avoid syncing the virtual environment [env: UV_NO_SYNC=]
///
/// Implies `--frozen`, as the project dependencies will be ignored (i.e., the lockfile will not
/// be updated, since the environment will not be synced regardless).
#[arg(long, env = EnvVars::UV_NO_SYNC, value_parser = clap::builder::BoolishValueParser::new())]
#[arg(long, value_parser = clap::builder::BoolishValueParser::new())]
pub no_sync: bool,
/// Assert that the `uv.lock` will remain unchanged.
/// Assert that the `uv.lock` will remain unchanged [env: UV_LOCKED=]
///
/// Requires that the lockfile is up-to-date. If the lockfile is missing or
/// needs to be updated, uv will exit with an error.
#[arg(long, env = EnvVars::UV_LOCKED, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["frozen", "upgrade"])]
#[arg(long, conflicts_with_all = ["frozen", "upgrade"])]
pub locked: bool,
/// Run without updating the `uv.lock` file.
/// Run without updating the `uv.lock` file [env: UV_FROZEN=]
///
/// Instead of checking if the lockfile is up-to-date, uses the versions in the lockfile as the
/// source of truth. If the lockfile is missing, uv will exit with an error. If the
/// `pyproject.toml` includes changes to dependencies that have not been included in the
/// lockfile yet, they will not be present in the environment.
#[arg(long, env = EnvVars::UV_FROZEN, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["locked", "upgrade", "no_sources"])]
#[arg(long, conflicts_with_all = ["locked", "upgrade", "no_sources"])]
pub frozen: bool,
/// Run the given path as a Python script.
@@ -3731,10 +3727,11 @@ pub struct RunArgs {
)]
pub python: Option<Maybe<String>>,
/// Whether to show resolver and installer output from any environment modifications.
/// Whether to show resolver and installer output from any environment modifications [env:
/// UV_SHOW_RESOLUTION=]
///
/// By default, environment modifications are omitted, but enabled under `--verbose`.
#[arg(long, env = EnvVars::UV_SHOW_RESOLUTION, value_parser = clap::builder::BoolishValueParser::new(), hide = true)]
#[arg(long, value_parser = clap::builder::BoolishValueParser::new(), hide = true)]
pub show_resolution: bool,
/// Number of times that `uv run` will allow recursive invocations.
@@ -3813,17 +3810,17 @@ pub struct SyncArgs {
#[arg(long, overrides_with("all_extras"), hide = true)]
pub no_all_extras: bool,
/// Include the development dependency group.
/// Include the development dependency group [env: UV_DEV=]
///
/// This option is an alias for `--group dev`.
#[arg(long, overrides_with("no_dev"), hide = true, env = EnvVars::UV_DEV, value_parser = clap::builder::BoolishValueParser::new())]
#[arg(long, overrides_with("no_dev"), hide = true, value_parser = clap::builder::BoolishValueParser::new())]
pub dev: bool,
/// Disable the development dependency group.
/// Disable the development dependency group [env: UV_NO_DEV=]
///
/// This option is an alias of `--no-group dev`.
/// See `--no-default-groups` to disable all default groups instead.
#[arg(long, overrides_with("dev"), env = EnvVars::UV_NO_DEV, value_parser = clap::builder::BoolishValueParser::new())]
#[arg(long, overrides_with("dev"), value_parser = clap::builder::BoolishValueParser::new())]
pub no_dev: bool,
/// Only include the development dependency group.
@@ -3879,8 +3876,8 @@ pub struct SyncArgs {
pub editable: bool,
/// Install any editable dependencies, including the project and any workspace members, as
/// non-editable.
#[arg(long, overrides_with = "editable", value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_NO_EDITABLE)]
/// non-editable [env: UV_NO_EDITABLE=]
#[arg(long, overrides_with = "editable", value_parser = clap::builder::BoolishValueParser::new())]
pub no_editable: bool,
/// Do not remove extraneous packages present in the environment.
@@ -3972,20 +3969,20 @@ pub struct SyncArgs {
#[arg(long, conflicts_with = "no_install_package", hide = true, value_hint = ValueHint::Other)]
pub only_install_package: Vec<PackageName>,
/// Assert that the `uv.lock` will remain unchanged.
/// Assert that the `uv.lock` will remain unchanged [env: UV_LOCKED=]
///
/// Requires that the lockfile is up-to-date. If the lockfile is missing or needs to be updated,
/// uv will exit with an error.
#[arg(long, env = EnvVars::UV_LOCKED, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["frozen", "upgrade"])]
#[arg(long, conflicts_with_all = ["frozen", "upgrade"])]
pub locked: bool,
/// Sync without updating the `uv.lock` file.
/// Sync without updating the `uv.lock` file [env: UV_FROZEN=]
///
/// Instead of checking if the lockfile is up-to-date, uses the versions in the lockfile as the
/// source of truth. If the lockfile is missing, uv will exit with an error. If the
/// `pyproject.toml` includes changes to dependencies that have not been included in the
/// lockfile yet, they will not be present in the environment.
#[arg(long, env = EnvVars::UV_FROZEN, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["locked", "upgrade", "no_sources"])]
#[arg(long, conflicts_with_all = ["locked", "upgrade", "no_sources"])]
pub frozen: bool,
/// Perform a dry run, without writing the lockfile or modifying the project environment.
@@ -4114,19 +4111,19 @@ pub struct LockArgs {
#[arg(long, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["check_exists", "upgrade"], overrides_with = "check")]
pub check: bool,
/// Check if the lockfile is up-to-date.
/// Check if the lockfile is up-to-date [env: UV_LOCKED=]
///
/// Asserts that the `uv.lock` would remain unchanged after a resolution. If the lockfile is
/// missing or needs to be updated, uv will exit with an error.
///
/// Equivalent to `--check`.
#[arg(long, env = EnvVars::UV_LOCKED, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["check_exists", "upgrade"], hide = true)]
#[arg(long, conflicts_with_all = ["check_exists", "upgrade"], hide = true)]
pub locked: bool,
/// Assert that a `uv.lock` exists without checking if it is up-to-date.
/// Assert that a `uv.lock` exists without checking if it is up-to-date [env: UV_FROZEN=]
///
/// Equivalent to `--frozen`.
#[arg(long, alias = "frozen", env = EnvVars::UV_FROZEN, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["check", "locked"])]
#[arg(long, alias = "frozen", conflicts_with_all = ["check", "locked"])]
pub check_exists: bool,
/// Perform a dry run, without writing the lockfile.
@@ -4221,7 +4218,7 @@ pub struct AddArgs {
#[arg(long, short, value_parser = MarkerTree::from_str, value_hint = ValueHint::Other)]
pub marker: Option<MarkerTree>,
/// Add the requirements to the development dependency group.
/// Add the requirements to the development dependency group [env: UV_DEV=]
///
/// This option is an alias for `--group dev`.
#[arg(
@@ -4229,7 +4226,6 @@ pub struct AddArgs {
conflicts_with("optional"),
conflicts_with("group"),
conflicts_with("script"),
env = EnvVars::UV_DEV,
value_parser = clap::builder::BoolishValueParser::new()
)]
pub dev: bool,
@@ -4258,7 +4254,8 @@ pub struct AddArgs {
#[arg(long, overrides_with = "no_editable")]
pub editable: bool,
#[arg(long, overrides_with = "editable", hide = true, value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_NO_EDITABLE)]
/// Don't add the requirements as editable [env: UV_NO_EDITABLE=]
#[arg(long, overrides_with = "editable", hide = true, value_parser = clap::builder::BoolishValueParser::new())]
pub no_editable: bool,
/// Add a dependency as provided.
@@ -4317,21 +4314,21 @@ pub struct AddArgs {
#[arg(long, value_hint = ValueHint::Other)]
pub extra: Option<Vec<ExtraName>>,
/// Avoid syncing the virtual environment.
#[arg(long, env = EnvVars::UV_NO_SYNC, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with = "frozen")]
/// Avoid syncing the virtual environment [env: UV_NO_SYNC=]
#[arg(long)]
pub no_sync: bool,
/// Assert that the `uv.lock` will remain unchanged.
/// Assert that the `uv.lock` will remain unchanged [env: UV_LOCKED=]
///
/// Requires that the lockfile is up-to-date. If the lockfile is missing or needs to be updated,
/// uv will exit with an error.
#[arg(long, env = EnvVars::UV_LOCKED, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["frozen", "upgrade"])]
#[arg(long, conflicts_with_all = ["frozen", "upgrade"])]
pub locked: bool,
/// Add dependencies without re-locking the project.
/// Add dependencies without re-locking the project [env: UV_FROZEN=]
///
/// The project environment will not be synced.
#[arg(long, env = EnvVars::UV_FROZEN, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["locked", "upgrade", "no_sources"])]
#[arg(long, conflicts_with_all = ["locked", "upgrade", "no_sources"])]
pub frozen: bool,
/// Prefer the active virtual environment over the project's virtual environment.
@@ -4523,10 +4520,10 @@ pub struct RemoveArgs {
#[arg(required = true, value_hint = ValueHint::Other)]
pub packages: Vec<Requirement<VerbatimParsedUrl>>,
/// Remove the packages from the development dependency group.
/// Remove the packages from the development dependency group [env: UV_DEV=]
///
/// This option is an alias for `--group dev`.
#[arg(long, conflicts_with("optional"), conflicts_with("group"), env = EnvVars::UV_DEV, value_parser = clap::builder::BoolishValueParser::new())]
#[arg(long, conflicts_with("optional"), conflicts_with("group"), value_parser = clap::builder::BoolishValueParser::new())]
pub dev: bool,
/// Remove the packages from the project's optional dependencies for the specified extra.
@@ -4549,8 +4546,8 @@ pub struct RemoveArgs {
)]
pub group: Option<GroupName>,
/// Avoid syncing the virtual environment after re-locking the project.
#[arg(long, env = EnvVars::UV_NO_SYNC, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with = "frozen")]
/// Avoid syncing the virtual environment after re-locking the project [env: UV_NO_SYNC=]
#[arg(long)]
pub no_sync: bool,
/// Prefer the active virtual environment over the project's virtual environment.
@@ -4566,17 +4563,17 @@ pub struct RemoveArgs {
#[arg(long, overrides_with = "active", hide = true)]
pub no_active: bool,
/// Assert that the `uv.lock` will remain unchanged.
/// Assert that the `uv.lock` will remain unchanged [env: UV_LOCKED=]
///
/// Requires that the lockfile is up-to-date. If the lockfile is missing or needs to be updated,
/// uv will exit with an error.
#[arg(long, env = EnvVars::UV_LOCKED, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["frozen", "upgrade"])]
#[arg(long, conflicts_with_all = ["frozen", "upgrade"])]
pub locked: bool,
/// Remove dependencies without re-locking the project.
/// Remove dependencies without re-locking the project [env: UV_FROZEN=]
///
/// The project environment will not be synced.
#[arg(long, env = EnvVars::UV_FROZEN, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["locked", "upgrade", "no_sources"])]
#[arg(long, conflicts_with_all = ["locked", "upgrade", "no_sources"])]
pub frozen: bool,
#[command(flatten)]
@@ -4628,13 +4625,13 @@ pub struct TreeArgs {
#[command(flatten)]
pub tree: DisplayTreeArgs,
/// Include the development dependency group.
/// Include the development dependency group [env: UV_DEV=]
///
/// Development dependencies are defined via `dependency-groups.dev` or
/// `tool.uv.dev-dependencies` in a `pyproject.toml`.
///
/// This option is an alias for `--group dev`.
#[arg(long, overrides_with("no_dev"), hide = true, env = EnvVars::UV_DEV, value_parser = clap::builder::BoolishValueParser::new())]
#[arg(long, overrides_with("no_dev"), hide = true, value_parser = clap::builder::BoolishValueParser::new())]
pub dev: bool,
/// Only include the development dependency group.
@@ -4645,11 +4642,11 @@ pub struct TreeArgs {
#[arg(long, conflicts_with_all = ["group", "all_groups", "no_dev"])]
pub only_dev: bool,
/// Disable the development dependency group.
/// Disable the development dependency group [env: UV_NO_DEV=]
///
/// This option is an alias of `--no-group dev`.
/// See `--no-default-groups` to disable all default groups instead.
#[arg(long, overrides_with("dev"), env = EnvVars::UV_NO_DEV, value_parser = clap::builder::BoolishValueParser::new())]
#[arg(long, overrides_with("dev"), value_parser = clap::builder::BoolishValueParser::new())]
pub no_dev: bool,
/// Include dependencies from the specified dependency group.
@@ -4688,17 +4685,17 @@ pub struct TreeArgs {
#[arg(long, conflicts_with_all = ["only_group", "only_dev"])]
pub all_groups: bool,
/// Assert that the `uv.lock` will remain unchanged.
/// Assert that the `uv.lock` will remain unchanged [env: UV_LOCKED=]
///
/// Requires that the lockfile is up-to-date. If the lockfile is missing or needs to be updated,
/// uv will exit with an error.
#[arg(long, env = EnvVars::UV_LOCKED, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["frozen", "upgrade"])]
#[arg(long, conflicts_with_all = ["frozen", "upgrade"])]
pub locked: bool,
/// Display the requirements without locking the project.
/// Display the requirements without locking the project [env: UV_FROZEN=]
///
/// If the lockfile is missing, uv will exit with an error.
#[arg(long, env = EnvVars::UV_FROZEN, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["locked", "upgrade", "no_sources"])]
#[arg(long, conflicts_with_all = ["locked", "upgrade", "no_sources"])]
pub frozen: bool,
#[command(flatten)]
@@ -4808,17 +4805,17 @@ pub struct ExportArgs {
#[arg(long, overrides_with("all_extras"), hide = true)]
pub no_all_extras: bool,
/// Include the development dependency group.
/// Include the development dependency group [env: UV_DEV=]
///
/// This option is an alias for `--group dev`.
#[arg(long, overrides_with("no_dev"), hide = true, env = EnvVars::UV_DEV, value_parser = clap::builder::BoolishValueParser::new())]
#[arg(long, overrides_with("no_dev"), hide = true, value_parser = clap::builder::BoolishValueParser::new())]
pub dev: bool,
/// Disable the development dependency group.
/// Disable the development dependency group [env: UV_NO_DEV=]
///
/// This option is an alias of `--no-group dev`.
/// See `--no-default-groups` to disable all default groups instead.
#[arg(long, overrides_with("dev"), env = EnvVars::UV_NO_DEV, value_parser = clap::builder::BoolishValueParser::new())]
#[arg(long, overrides_with("dev"), value_parser = clap::builder::BoolishValueParser::new())]
pub no_dev: bool,
/// Only include the development dependency group.
@@ -4885,8 +4882,8 @@ pub struct ExportArgs {
pub editable: bool,
/// Export any editable dependencies, including the project and any workspace members, as
/// non-editable.
#[arg(long, overrides_with = "editable", value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_NO_EDITABLE)]
/// non-editable [env: UV_NO_EDITABLE=]
#[arg(long, overrides_with = "editable", value_parser = clap::builder::BoolishValueParser::new())]
pub no_editable: bool,
/// Include hashes for all dependencies.
@@ -4994,17 +4991,17 @@ pub struct ExportArgs {
)]
pub only_emit_package: Vec<PackageName>,
/// Assert that the `uv.lock` will remain unchanged.
/// Assert that the `uv.lock` will remain unchanged [env: UV_LOCKED=]
///
/// Requires that the lockfile is up-to-date. If the lockfile is missing or needs to be updated,
/// uv will exit with an error.
#[arg(long, env = EnvVars::UV_LOCKED, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["frozen", "upgrade"])]
#[arg(long, conflicts_with_all = ["frozen", "upgrade"])]
pub locked: bool,
/// Do not update the `uv.lock` before exporting.
/// Do not update the `uv.lock` before exporting [env: UV_FROZEN=]
///
/// If a `uv.lock` does not exist, uv will exit with an error.
#[arg(long, env = EnvVars::UV_FROZEN, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["locked", "upgrade", "no_sources"])]
#[arg(long, conflicts_with_all = ["locked", "upgrade", "no_sources"])]
pub frozen: bool,
#[command(flatten)]
@@ -5302,8 +5299,9 @@ pub struct ToolRunArgs {
)]
pub overrides: Vec<Maybe<PathBuf>>,
/// Run the tool in an isolated virtual environment, ignoring any already-installed tools.
#[arg(long, env = EnvVars::UV_ISOLATED, value_parser = clap::builder::BoolishValueParser::new())]
/// Run the tool in an isolated virtual environment, ignoring any already-installed tools [env:
/// UV_ISOLATED=]
#[arg(long, value_parser = clap::builder::BoolishValueParser::new())]
pub isolated: bool,
/// Load environment variables from a `.env` file.
@@ -5313,8 +5311,8 @@ pub struct ToolRunArgs {
#[arg(long, value_delimiter = ' ', env = EnvVars::UV_ENV_FILE, value_hint = ValueHint::FilePath)]
pub env_file: Vec<PathBuf>,
/// Avoid reading environment variables from a `.env` file.
#[arg(long, value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_NO_ENV_FILE)]
/// Avoid reading environment variables from a `.env` file [env: UV_NO_ENV_FILE=]
#[arg(long, value_parser = clap::builder::BoolishValueParser::new())]
pub no_env_file: bool,
#[command(flatten)]
@@ -5344,10 +5342,11 @@ pub struct ToolRunArgs {
)]
pub python: Option<Maybe<String>>,
/// Whether to show resolver and installer output from any environment modifications.
/// Whether to show resolver and installer output from any environment modifications [env:
/// UV_SHOW_RESOLUTION=]
///
/// By default, environment modifications are omitted, but enabled under `--verbose`.
#[arg(long, env = EnvVars::UV_SHOW_RESOLUTION, value_parser = clap::builder::BoolishValueParser::new(), hide = true)]
#[arg(long, value_parser = clap::builder::BoolishValueParser::new(), hide = true)]
pub show_resolution: bool,
/// The platform for which requirements should be installed.
@@ -6649,17 +6648,11 @@ pub struct IndexArgs {
#[derive(Args)]
pub struct RefreshArgs {
/// Refresh all cached data.
#[arg(
long,
conflicts_with("offline"),
overrides_with("no_refresh"),
help_heading = "Cache options"
)]
#[arg(long, overrides_with("no_refresh"), help_heading = "Cache options")]
pub refresh: bool,
#[arg(
long,
conflicts_with("offline"),
overrides_with("refresh"),
hide = true,
help_heading = "Cache options"

View File

@@ -1,10 +1,12 @@
use std::fmt;
use anstream::eprintln;
use uv_cache::Refresh;
use uv_configuration::{BuildIsolation, Reinstall, Upgrade};
use uv_distribution_types::{ConfigSettings, PackageConfigSettings, Requirement};
use uv_resolver::{ExcludeNewer, ExcludeNewerPackage, PrereleaseMode};
use uv_settings::{Combine, PipOptions, ResolverInstallerOptions, ResolverOptions};
use uv_settings::{Combine, EnvFlag, PipOptions, ResolverInstallerOptions, ResolverOptions};
use uv_warnings::owo_colors::OwoColorize;
use crate::{
@@ -37,6 +39,150 @@ pub fn flag(yes: bool, no: bool, name: &str) -> Option<bool> {
}
}
/// The source of a boolean flag value.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FlagSource {
/// The flag was set via command-line argument.
Cli,
/// The flag was set via environment variable.
Env(&'static str),
/// The flag was set via workspace/project configuration.
Config,
}
impl fmt::Display for FlagSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Cli => write!(f, "command-line argument"),
Self::Env(name) => write!(f, "environment variable `{name}`"),
Self::Config => write!(f, "workspace configuration"),
}
}
}
/// A boolean flag value with its source.
#[derive(Debug, Clone, Copy)]
pub enum Flag {
/// The flag is not set.
Disabled,
/// The flag is enabled with a known source.
Enabled {
source: FlagSource,
/// The CLI flag name (e.g., "locked" for `--locked`).
name: &'static str,
},
}
impl Flag {
/// Create a flag that is explicitly disabled.
pub const fn disabled() -> Self {
Self::Disabled
}
/// Create an enabled flag from a CLI argument.
pub const fn from_cli(name: &'static str) -> Self {
Self::Enabled {
source: FlagSource::Cli,
name,
}
}
/// Create an enabled flag from workspace/project configuration.
pub const fn from_config(name: &'static str) -> Self {
Self::Enabled {
source: FlagSource::Config,
name,
}
}
/// Returns `true` if the flag is set.
pub fn is_enabled(self) -> bool {
matches!(self, Self::Enabled { .. })
}
/// Returns the source of the flag, if it is set.
pub fn source(self) -> Option<FlagSource> {
match self {
Self::Disabled => None,
Self::Enabled { source, .. } => Some(source),
}
}
/// Returns the CLI flag name, if the flag is enabled.
pub fn name(self) -> Option<&'static str> {
match self {
Self::Disabled => None,
Self::Enabled { name, .. } => Some(name),
}
}
}
impl From<Flag> for bool {
fn from(flag: Flag) -> Self {
flag.is_enabled()
}
}
/// Resolve a boolean flag from CLI arguments and an environment variable.
///
/// The CLI argument takes precedence over the environment variable. Returns a [`Flag`] with the
/// resolved value and source.
pub fn resolve_flag(cli_flag: bool, name: &'static str, env_flag: EnvFlag) -> Flag {
if cli_flag {
Flag::Enabled {
source: FlagSource::Cli,
name,
}
} else if env_flag.value == Some(true) {
Flag::Enabled {
source: FlagSource::Env(env_flag.env_var),
name,
}
} else {
Flag::Disabled
}
}
/// Check if two flags conflict and exit with an error if they do.
///
/// This function checks if both flags are enabled (truthy) and reports an error if so, including
/// the source of each flag (CLI or environment variable) in the error message.
pub fn check_conflicts(flag_a: Flag, flag_b: Flag) {
if let (
Flag::Enabled {
source: source_a,
name: name_a,
},
Flag::Enabled {
source: source_b,
name: name_b,
},
) = (flag_a, flag_b)
{
let display_a = match source_a {
FlagSource::Cli => format!("`--{name_a}`"),
FlagSource::Env(env) => format!("`{env}` (environment variable)"),
FlagSource::Config => format!("`{name_a}` (workspace configuration)"),
};
let display_b = match source_b {
FlagSource::Cli => format!("`--{name_b}`"),
FlagSource::Env(env) => format!("`{env}` (environment variable)"),
FlagSource::Config => format!("`{name_b}` (workspace configuration)"),
};
eprintln!(
"{}{} the argument {} cannot be used with {}",
"error".bold().red(),
":".bold(),
display_a.green(),
display_b.green(),
);
#[allow(clippy::exit)]
{
std::process::exit(2);
}
}
}
impl From<RefreshArgs> for Refresh {
fn from(value: RefreshArgs) -> Self {
let RefreshArgs {

View File

@@ -594,6 +594,25 @@ pub struct Concurrency {
pub installs: Option<NonZeroUsize>,
}
/// A boolean flag parsed from an environment variable.
///
/// Stores both the value and the environment variable name for use in error messages.
#[derive(Debug, Clone, Copy)]
pub struct EnvFlag {
pub value: Option<bool>,
pub env_var: &'static str,
}
impl EnvFlag {
/// Create a new [`EnvFlag`] by parsing the given environment variable.
pub fn new(env_var: &'static str) -> Result<Self, Error> {
Ok(Self {
value: parse_boolish_environment_variable(env_var)?,
env_var,
})
}
}
/// Options loaded from environment variables.
///
/// This is currently a subset of all respected environment variables, most are parsed via Clap at
@@ -613,6 +632,24 @@ pub struct EnvironmentOptions {
pub concurrency: Concurrency,
#[cfg(feature = "tracing-durations-export")]
pub tracing_durations_file: Option<PathBuf>,
pub frozen: EnvFlag,
pub locked: EnvFlag,
pub offline: EnvFlag,
pub no_sync: EnvFlag,
pub managed_python: EnvFlag,
pub no_managed_python: EnvFlag,
pub native_tls: EnvFlag,
pub preview: EnvFlag,
pub isolated: EnvFlag,
pub no_progress: EnvFlag,
pub no_installer_metadata: EnvFlag,
pub dev: EnvFlag,
pub no_dev: EnvFlag,
pub show_resolution: EnvFlag,
pub no_editable: EnvFlag,
pub no_env_file: EnvFlag,
pub venv_seed: EnvFlag,
pub venv_clear: EnvFlag,
}
impl EnvironmentOptions {
@@ -667,6 +704,24 @@ impl EnvironmentOptions {
tracing_durations_file: parse_path_environment_variable(
EnvVars::TRACING_DURATIONS_FILE,
),
frozen: EnvFlag::new(EnvVars::UV_FROZEN)?,
locked: EnvFlag::new(EnvVars::UV_LOCKED)?,
offline: EnvFlag::new(EnvVars::UV_OFFLINE)?,
no_sync: EnvFlag::new(EnvVars::UV_NO_SYNC)?,
managed_python: EnvFlag::new(EnvVars::UV_MANAGED_PYTHON)?,
no_managed_python: EnvFlag::new(EnvVars::UV_NO_MANAGED_PYTHON)?,
native_tls: EnvFlag::new(EnvVars::UV_NATIVE_TLS)?,
preview: EnvFlag::new(EnvVars::UV_PREVIEW)?,
isolated: EnvFlag::new(EnvVars::UV_ISOLATED)?,
no_progress: EnvFlag::new(EnvVars::UV_NO_PROGRESS)?,
no_installer_metadata: EnvFlag::new(EnvVars::UV_NO_INSTALLER_METADATA)?,
dev: EnvFlag::new(EnvVars::UV_DEV)?,
no_dev: EnvFlag::new(EnvVars::UV_NO_DEV)?,
show_resolution: EnvFlag::new(EnvVars::UV_SHOW_RESOLUTION)?,
no_editable: EnvFlag::new(EnvVars::UV_NO_EDITABLE)?,
no_env_file: EnvFlag::new(EnvVars::UV_NO_ENV_FILE)?,
venv_seed: EnvFlag::new(EnvVars::UV_VENV_SEED)?,
venv_clear: EnvFlag::new(EnvVars::UV_VENV_CLEAR)?,
})
}
}

View File

@@ -70,6 +70,18 @@ pub(crate) fn help(query: &[String], printer: Printer, no_pager: bool) -> Result
.render_long_help()
};
// Reformat inline [env: VAR=] annotations to their own line.
let help_plain = if is_root {
help.to_string()
} else {
reformat_env_annotations(&help.to_string())
};
let help_ansi = if is_root {
help.ansi().to_string()
} else {
reformat_env_annotations(&help.ansi().to_string())
};
let want_color = match anstream::Stdout::choice(&std::io::stdout()) {
ColorChoice::Always | ColorChoice::AlwaysAnsi => true,
ColorChoice::Never => false,
@@ -83,21 +95,156 @@ pub(crate) fn help(query: &[String], printer: Printer, no_pager: bool) -> Result
if should_page && let Some(pager) = Pager::try_from_env() {
let query = query.join(" ");
if want_color && pager.supports_colors() {
pager.spawn(format!("{}: {query}", "uv help".bold()), help.ansi())?;
pager.spawn(format!("{}: {query}", "uv help".bold()), &help_ansi)?;
} else {
pager.spawn(format!("uv help: {query}"), help)?;
pager.spawn(format!("uv help: {query}"), &help_plain)?;
}
} else {
if want_color {
writeln!(printer.stdout(), "{}", help.ansi())?;
writeln!(printer.stdout(), "{help_ansi}")?;
} else {
writeln!(printer.stdout(), "{help}")?;
writeln!(printer.stdout(), "{help_plain}")?;
}
}
Ok(ExitStatus::Success)
}
/// Get the first non-ANSI character starting at a given byte position.
///
/// Returns `None` if the rest of the string is empty or only contains ANSI sequences.
fn first_non_ansi_char(s: &str, start: usize) -> Option<char> {
let mut chars = s[start..].chars().peekable();
while let Some(c) = chars.next() {
if c == '\x1b' {
// Skip ANSI escape sequences.
if chars.peek() == Some(&'[') {
chars.next();
for c in chars.by_ref() {
if c.is_ascii_alphabetic() {
break;
}
}
}
} else {
return Some(c);
}
}
None
}
/// Reformat `[env: VAR=]` annotations in long help output.
///
/// Moves inline `[env: VAR=]` annotations to their own line at the end of each
/// argument's description, matching clap's native formatting for environment vars.
fn reformat_env_annotations(help: &str) -> String {
let mut result = String::new();
let mut pending_env: Option<String> = None;
let lines: Vec<&str> = help.lines().collect();
let mut i = 0;
while i < lines.len() {
let line = lines[i];
// Classify the line type based on clap's help formatting:
// - Argument lines: 6 spaces + `-` or `<` (e.g., " --offline", " <PACKAGE>")
// - Description lines: 10 spaces + text (e.g., " Disable network access")
// - Section headers: no leading spaces, ends with `:` (e.g., "Options:")
//
// Leading spaces never contain ANSI codes, but argument names may be colored,
// so we skip ANSI sequences when checking the first content character.
let indent = line.len() - line.trim_start().len();
let first_char = first_non_ansi_char(line, indent);
let is_arg_line = indent == 6 && matches!(first_char, Some('-' | '<'));
let is_section_header = indent == 0 && line.ends_with(':');
let is_description_line = indent == 10;
// Flush pending env before starting a new argument or section.
if is_arg_line || is_section_header {
if let Some(env) = pending_env.take() {
// Remove trailing blank lines; add exactly one blank line before the environment variable.
while result.ends_with("\n\n") {
result.pop();
}
if !result.ends_with('\n') {
result.push('\n');
}
result.push('\n');
let _ = write!(result, " {env}\n\n");
}
}
// Check for inline environment annotations on description lines.
if is_description_line {
if let Some((env_annotation, new_line)) = extract_env_annotation(line) {
pending_env = Some(env_annotation);
if !new_line.trim().is_empty() {
result.push_str(&new_line);
// Add a period, if the line doesn't end with punctuation.
if !new_line.ends_with('.') && !new_line.ends_with(':') {
result.push('.');
}
result.push('\n');
}
i += 1;
continue;
}
}
result.push_str(line);
result.push('\n');
i += 1;
}
// Flush any remaining pending environment variables at the end of the help.
if let Some(env) = pending_env {
while result.ends_with("\n\n") {
result.pop();
}
if !result.ends_with('\n') {
result.push('\n');
}
result.push('\n');
let _ = writeln!(result, " {env}");
}
if result.ends_with('\n') {
result.pop();
}
result
}
/// Extract an inline `[env: VAR=]` annotation from a line.
///
/// Returns the annotation and the line with the annotation removed, or `None` if no
/// annotation is found.
fn extract_env_annotation(line: &str) -> Option<(String, String)> {
// Look for the pattern: " [env: SOMETHING=]"
let start = line.find(" [env: ")?;
let rest = &line[start + " [env: ".len()..];
let end_offset = rest.find("=]")?;
// Validate that the environment variable name contains only uppercase letters and underscores.
let env_name = &rest[..end_offset];
if !env_name.chars().all(|c| c.is_ascii_uppercase() || c == '_') {
return None;
}
let annotation_end = start + " [env: ".len() + end_offset + "=]".len();
let annotation = line[start + " ".len()..annotation_end].to_string();
let new_line = format!("{}{}", &line[..start], &line[annotation_end..]);
// Only extract if there's actual text remaining (not just whitespace).
// If the line is just the annotation (clap-generated), leave it alone.
if new_line.trim().is_empty() {
return None;
}
Some((annotation, new_line))
}
/// Find the command corresponding to a set of arguments, e.g., `["uv", "pip", "install"]`.
///
/// If the command cannot be found, the nearest command is returned.

View File

@@ -57,14 +57,14 @@ use crate::commands::project::{
use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter};
use crate::commands::{ExitStatus, ScriptPath, diagnostics, project};
use crate::printer::Printer;
use crate::settings::{LockCheck, ResolverInstallerSettings};
use crate::settings::{FrozenSource, LockCheck, ResolverInstallerSettings};
/// Add one or more packages to the project requirements.
#[allow(clippy::fn_params_excessive_bools)]
pub(crate) async fn add(
project_dir: &Path,
lock_check: LockCheck,
frozen: bool,
frozen: Option<FrozenSource>,
active: Option<bool>,
no_sync: bool,
no_install_project: bool,
@@ -187,7 +187,7 @@ pub(crate) async fn add(
"`{lock_check}` is a no-op for Python scripts with inline metadata, which always run in isolation"
);
}
if frozen {
if frozen.is_some() {
warn_user_once!(
"`--frozen` is a no-op for Python scripts with inline metadata, which always run in isolation"
);
@@ -291,7 +291,7 @@ pub(crate) async fn add(
defaulted_groups =
groups.with_defaults(default_dependency_groups(project.pyproject_toml())?);
if frozen || no_sync {
if frozen.is_some() || no_sync {
// Discover the interpreter.
let interpreter = ProjectInterpreter::discover(
project.workspace(),
@@ -706,7 +706,7 @@ pub(crate) async fn add(
// If `--frozen`, exit early. There's no reason to lock and sync, since we don't need a `uv.lock`
// to exist at all.
if frozen {
if frozen.is_some() {
return Ok(ExitStatus::Success);
}

View File

@@ -27,12 +27,12 @@ use crate::commands::project::install_target::InstallTarget;
use crate::commands::project::lock::{LockMode, LockOperation};
use crate::commands::project::lock_target::LockTarget;
use crate::commands::project::{
MissingLockfileSource, ProjectError, ProjectInterpreter, ScriptInterpreter, UniversalState,
default_dependency_groups, detect_conflicts,
ProjectError, ProjectInterpreter, ScriptInterpreter, UniversalState, default_dependency_groups,
detect_conflicts,
};
use crate::commands::{ExitStatus, OutputWriter, diagnostics};
use crate::printer::Printer;
use crate::settings::{LockCheck, ResolverSettings};
use crate::settings::{FrozenSource, LockCheck, ResolverSettings};
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
@@ -68,7 +68,7 @@ pub(crate) async fn export(
groups: DependencyGroups,
editable: Option<EditableMode>,
lock_check: LockCheck,
frozen: bool,
frozen: Option<FrozenSource>,
include_annotations: bool,
include_header: bool,
script: Option<Pep723Script>,
@@ -90,7 +90,7 @@ pub(crate) async fn export(
let target = if let Some(script) = script {
ExportTarget::Script(script)
} else {
let project = if frozen {
let project = if frozen.is_some() {
VirtualProject::discover(
project_dir,
&DiscoveryOptions {
@@ -142,7 +142,7 @@ pub(crate) async fn export(
let extras = extras.with_defaults(default_extras);
// Find an interpreter for the project, unless `--frozen` is set.
let interpreter = if frozen {
let interpreter = if frozen.is_some() {
None
} else {
Some(match &target {
@@ -184,8 +184,8 @@ pub(crate) async fn export(
};
// Determine the lock mode.
let mode = if frozen {
LockMode::Frozen(MissingLockfileSource::frozen())
let mode = if let Some(frozen_source) = frozen {
LockMode::Frozen(frozen_source.into())
} else if let LockCheck::Enabled(lock_check) = lock_check {
LockMode::Locked(interpreter.as_ref().unwrap(), lock_check)
} else if matches!(target, ExportTarget::Script(_))

View File

@@ -49,7 +49,7 @@ use crate::commands::project::{
use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter};
use crate::commands::{ExitStatus, ScriptPath, diagnostics, pip};
use crate::printer::Printer;
use crate::settings::{LockCheck, LockCheckSource, ResolverSettings};
use crate::settings::{FrozenSource, LockCheck, LockCheckSource, ResolverSettings};
/// The result of running a lock operation.
#[derive(Debug, Clone)]
@@ -82,7 +82,7 @@ impl LockResult {
pub(crate) async fn lock(
project_dir: &Path,
lock_check: LockCheck,
frozen: bool,
frozen: Option<FrozenSource>,
dry_run: DryRun,
refresh: Refresh,
python: Option<String>,
@@ -136,8 +136,8 @@ pub(crate) async fn lock(
// Determine the lock mode.
let interpreter;
let mode = if frozen {
LockMode::Frozen(MissingLockfileSource::frozen())
let mode = if let Some(frozen_source) = frozen {
LockMode::Frozen(frozen_source.into())
} else {
interpreter = match target {
LockTarget::Workspace(workspace) => ProjectInterpreter::discover(

View File

@@ -41,7 +41,7 @@ use uv_resolver::{
ResolverEnvironment, ResolverOutput,
};
use uv_scripts::Pep723ItemRef;
use uv_settings::{PythonInstallMirrors, parse_boolish_environment_variable};
use uv_settings::PythonInstallMirrors;
use uv_static::EnvVars;
use uv_torch::{TorchSource, TorchStrategy};
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy};
@@ -58,7 +58,8 @@ use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter};
use crate::commands::{capitalize, conjunction, pip};
use crate::printer::Printer;
use crate::settings::{
InstallerSettingsRef, LockCheckSource, ResolverInstallerSettings, ResolverSettings,
FrozenSource, InstallerSettingsRef, LockCheckSource, ResolverInstallerSettings,
ResolverSettings,
};
pub(crate) mod add;
@@ -82,10 +83,14 @@ pub(crate) enum MissingLockfileSource {
Frozen,
/// The `UV_FROZEN` environment variable was set.
FrozenEnv,
/// The `frozen` option was set via workspace configuration.
FrozenConfiguration,
/// The `--locked` flag was provided.
Locked,
/// The `UV_LOCKED` environment variable was set.
LockedEnv,
/// The `locked` option was set via workspace configuration.
LockedConfiguration,
/// The `--check` flag was provided.
Check,
}
@@ -95,8 +100,10 @@ impl std::fmt::Display for MissingLockfileSource {
match self {
Self::Frozen => write!(f, "`--frozen`"),
Self::FrozenEnv => write!(f, "`UV_FROZEN=1`"),
Self::FrozenConfiguration => write!(f, "`frozen` (workspace configuration)"),
Self::Locked => write!(f, "`--locked`"),
Self::LockedEnv => write!(f, "`UV_LOCKED=1`"),
Self::LockedConfiguration => write!(f, "`locked` (workspace configuration)"),
Self::Check => write!(f, "`--check`"),
}
}
@@ -105,38 +112,20 @@ impl std::fmt::Display for MissingLockfileSource {
impl From<LockCheckSource> for MissingLockfileSource {
fn from(source: LockCheckSource) -> Self {
match source {
LockCheckSource::Locked => {
// TODO(charlie): Track the source (flag vs. environment variable) when resolving
// settings, rather than checking after-the-fact.
if matches!(
parse_boolish_environment_variable(EnvVars::UV_LOCKED),
Ok(Some(true))
) {
Self::LockedEnv
} else {
Self::Locked
}
}
LockCheckSource::LockedCli => Self::Locked,
LockCheckSource::LockedEnv => Self::LockedEnv,
LockCheckSource::LockedConfiguration => Self::LockedConfiguration,
LockCheckSource::Check => Self::Check,
}
}
}
impl MissingLockfileSource {
/// Determine the source of the frozen flag.
///
/// If `UV_FROZEN` is set to a truthy value in the environment, the source is the environment
/// variable. Otherwise, the source is the `--frozen` flag.
pub(crate) fn frozen() -> Self {
// TODO(charlie): Track the source (flag vs. environment variable) when resolving
// settings, rather than checking after-the-fact.
if matches!(
parse_boolish_environment_variable(EnvVars::UV_FROZEN),
Ok(Some(true))
) {
Self::FrozenEnv
} else {
Self::Frozen
impl From<FrozenSource> for MissingLockfileSource {
fn from(source: FrozenSource) -> Self {
match source {
FrozenSource::Cli => Self::Frozen,
FrozenSource::Env => Self::FrozenEnv,
FrozenSource::Configuration => Self::FrozenConfiguration,
}
}
}

View File

@@ -36,14 +36,14 @@ use crate::commands::project::{
};
use crate::commands::{ExitStatus, diagnostics, project};
use crate::printer::Printer;
use crate::settings::{LockCheck, ResolverInstallerSettings};
use crate::settings::{FrozenSource, LockCheck, ResolverInstallerSettings};
/// Remove one or more packages from the project requirements.
#[allow(clippy::fn_params_excessive_bools)]
pub(crate) async fn remove(
project_dir: &Path,
lock_check: LockCheck,
frozen: bool,
frozen: Option<FrozenSource>,
active: Option<bool>,
no_sync: bool,
packages: Vec<PackageName>,
@@ -75,7 +75,7 @@ pub(crate) async fn remove(
"`{lock_check}` is a no-op for Python scripts with inline metadata, which always run in isolation",
);
}
if frozen {
if frozen.is_some() {
warn_user_once!(
"`--frozen` is a no-op for Python scripts with inline metadata, which always run in isolation"
);
@@ -184,7 +184,7 @@ pub(crate) async fn remove(
// If `--frozen`, exit early. There's no reason to lock and sync, since we don't need a `uv.lock`
// to exist at all.
if frozen {
if frozen.is_some() {
return Ok(ExitStatus::Success);
}

View File

@@ -65,15 +65,15 @@ use crate::commands::project::install_target::InstallTarget;
use crate::commands::project::lock::LockMode;
use crate::commands::project::lock_target::LockTarget;
use crate::commands::project::{
EnvironmentSpecification, MissingLockfileSource, PreferenceLocation, ProjectEnvironment,
ProjectError, ScriptEnvironment, ScriptInterpreter, UniversalState, WorkspacePython,
EnvironmentSpecification, PreferenceLocation, ProjectEnvironment, ProjectError,
ScriptEnvironment, ScriptInterpreter, UniversalState, WorkspacePython,
default_dependency_groups, script_extra_build_requires, script_specification,
update_environment, validate_project_requires_python,
};
use crate::commands::reporters::PythonDownloadReporter;
use crate::commands::{ExitStatus, diagnostics, project};
use crate::printer::Printer;
use crate::settings::{LockCheck, ResolverInstallerSettings, ResolverSettings};
use crate::settings::{FrozenSource, LockCheck, ResolverInstallerSettings, ResolverSettings};
/// Run a command.
#[allow(clippy::fn_params_excessive_bools)]
@@ -84,7 +84,7 @@ pub(crate) async fn run(
requirements: Vec<RequirementsSource>,
show_resolution: bool,
lock_check: LockCheck,
frozen: bool,
frozen: Option<FrozenSource>,
active: Option<bool>,
no_sync: bool,
isolated: bool,
@@ -262,8 +262,8 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
.ok();
// Determine the lock mode.
let mode = if frozen {
LockMode::Frozen(MissingLockfileSource::frozen())
let mode = if let Some(frozen_source) = frozen {
LockMode::Frozen(frozen_source.into())
} else if let LockCheck::Enabled(lock_check) = lock_check {
LockMode::Locked(environment.interpreter(), lock_check)
} else {
@@ -364,7 +364,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
"uv lock --script".green(),
);
}
if frozen {
if frozen.is_some() {
warn_user!(
"No lockfile found for Python script (ignoring `--frozen`); run `{}` to generate a lockfile",
"uv lock --script".green(),
@@ -601,7 +601,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
if let LockCheck::Enabled(lock_check) = lock_check {
warn_user!("`{lock_check}` has no effect when used alongside `--no-project`");
}
if frozen {
if frozen.is_some() {
warn_user!("`--frozen` has no effect when used alongside `--no-project`");
}
if no_sync {
@@ -748,8 +748,8 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
.ok();
// Determine the lock mode.
let mode = if frozen {
LockMode::Frozen(MissingLockfileSource::frozen())
let mode = if let Some(frozen_source) = frozen {
LockMode::Frozen(frozen_source.into())
} else if let LockCheck::Enabled(lock_check) = lock_check {
LockMode::Locked(venv.interpreter(), lock_check)
} else if isolated {

View File

@@ -44,14 +44,15 @@ use crate::commands::project::install_target::InstallTarget;
use crate::commands::project::lock::{LockMode, LockOperation, LockResult};
use crate::commands::project::lock_target::LockTarget;
use crate::commands::project::{
EnvironmentUpdate, MissingLockfileSource, PlatformState, ProjectEnvironment, ProjectError,
ScriptEnvironment, UniversalState, default_dependency_groups, detect_conflicts,
script_extra_build_requires, script_specification, update_environment,
EnvironmentUpdate, PlatformState, ProjectEnvironment, ProjectError, ScriptEnvironment,
UniversalState, default_dependency_groups, detect_conflicts, script_extra_build_requires,
script_specification, update_environment,
};
use crate::commands::{ExitStatus, diagnostics};
use crate::printer::Printer;
use crate::settings::{
InstallerSettingsRef, LockCheck, LockCheckSource, ResolverInstallerSettings, ResolverSettings,
FrozenSource, InstallerSettingsRef, LockCheck, LockCheckSource, ResolverInstallerSettings,
ResolverSettings,
};
/// Sync the project environment.
@@ -59,7 +60,7 @@ use crate::settings::{
pub(crate) async fn sync(
project_dir: &Path,
lock_check: LockCheck,
frozen: bool,
frozen: Option<FrozenSource>,
dry_run: DryRun,
active: Option<bool>,
all_packages: bool,
@@ -99,7 +100,7 @@ pub(crate) async fn sync(
SyncTarget::Script(script)
} else {
// Identify the project.
let project = if frozen {
let project = if frozen.is_some() {
VirtualProject::discover(
project_dir,
&DiscoveryOptions {
@@ -225,7 +226,7 @@ pub(crate) async fn sync(
if let SyncTarget::Script(script) = &target {
let lockfile = LockTarget::from(script).lock_path();
if !lockfile.is_file() {
if frozen {
if frozen.is_some() {
return Err(anyhow::anyhow!(
"`uv sync --frozen` requires a script lockfile; run `{}` to lock the script",
format!("uv lock --script {}", script.path.user_display()).green(),
@@ -329,8 +330,8 @@ pub(crate) async fn sync(
let state = UniversalState::default();
// Determine the lock mode.
let mode = if frozen {
LockMode::Frozen(MissingLockfileSource::frozen())
let mode = if let Some(frozen_source) = frozen {
LockMode::Frozen(frozen_source.into())
} else if let LockCheck::Enabled(lock_check) = lock_check {
LockMode::Locked(environment.interpreter(), lock_check)
} else if dry_run.enabled() {

View File

@@ -24,12 +24,12 @@ use crate::commands::pip::resolution_markers;
use crate::commands::project::lock::{LockMode, LockOperation};
use crate::commands::project::lock_target::LockTarget;
use crate::commands::project::{
MissingLockfileSource, ProjectError, ProjectInterpreter, ScriptInterpreter, UniversalState,
default_dependency_groups,
ProjectError, ProjectInterpreter, ScriptInterpreter, UniversalState, default_dependency_groups,
};
use crate::commands::reporters::LatestVersionReporter;
use crate::commands::{ExitStatus, diagnostics};
use crate::printer::Printer;
use crate::settings::FrozenSource;
use crate::settings::LockCheck;
use crate::settings::ResolverSettings;
@@ -39,7 +39,7 @@ pub(crate) async fn tree(
project_dir: &Path,
groups: DependencyGroups,
lock_check: LockCheck,
frozen: bool,
frozen: Option<FrozenSource>,
universal: bool,
depth: u8,
prune: Vec<PackageName>,
@@ -83,7 +83,7 @@ pub(crate) async fn tree(
let groups = groups.with_defaults(default_groups);
// Find an interpreter for the project, unless `--frozen` and `--universal` are both set.
let interpreter = if frozen && universal {
let interpreter = if frozen.is_some() && universal {
None
} else {
Some(match target {
@@ -125,8 +125,8 @@ pub(crate) async fn tree(
};
// Determine the lock mode.
let mode = if frozen {
LockMode::Frozen(MissingLockfileSource::frozen())
let mode = if let Some(frozen_source) = frozen {
LockMode::Frozen(frozen_source.into())
} else if let LockCheck::Enabled(lock_check) = lock_check {
LockMode::Locked(interpreter.as_ref().unwrap(), lock_check)
} else if matches!(target, LockTarget::Script(_)) && !target.lock_path().is_file() {

View File

@@ -34,12 +34,11 @@ use crate::commands::project::add::{AddTarget, PythonTarget};
use crate::commands::project::install_target::InstallTarget;
use crate::commands::project::lock::LockMode;
use crate::commands::project::{
MissingLockfileSource, ProjectEnvironment, ProjectError, ProjectInterpreter, UniversalState,
default_dependency_groups,
ProjectEnvironment, ProjectError, ProjectInterpreter, UniversalState, default_dependency_groups,
};
use crate::commands::{ExitStatus, diagnostics, project};
use crate::printer::Printer;
use crate::settings::{LockCheck, ResolverInstallerSettings};
use crate::settings::{FrozenSource, LockCheck, ResolverInstallerSettings};
/// Display version information for uv itself (`uv self version`)
pub(crate) fn self_version(
@@ -65,7 +64,7 @@ pub(crate) async fn project_version(
explicit_project: bool,
dry_run: bool,
lock_check: LockCheck,
frozen: bool,
frozen: Option<FrozenSource>,
active: Option<bool>,
no_sync: bool,
python: Option<String>,
@@ -94,27 +93,30 @@ pub(crate) async fn project_version(
// Short-circuit early for a frozen read
let is_read_only = value.is_none() && bump.is_empty();
if frozen && is_read_only {
return Box::pin(print_frozen_version(
project,
&name,
project_dir,
active,
python,
install_mirrors,
&settings,
client_builder,
python_preference,
python_downloads,
concurrency,
no_config,
cache,
short,
output_format,
printer,
preview,
))
.await;
if let Some(frozen_source) = frozen {
if is_read_only {
return Box::pin(print_frozen_version(
project,
&name,
project_dir,
frozen_source,
active,
python,
install_mirrors,
&settings,
client_builder,
python_preference,
python_downloads,
concurrency,
no_config,
cache,
short,
output_format,
printer,
preview,
))
.await;
}
}
let mut toml = PyProjectTomlMut::from_toml(
@@ -422,6 +424,7 @@ async fn print_frozen_version(
project: VirtualProject,
name: &PackageName,
project_dir: &Path,
frozen_source: FrozenSource,
active: Option<bool>,
python: Option<String>,
install_mirrors: PythonInstallMirrors,
@@ -465,7 +468,7 @@ async fn print_frozen_version(
// Lock and sync the environment, if necessary.
let lock = match Box::pin(
project::lock::LockOperation::new(
LockMode::Frozen(MissingLockfileSource::frozen()),
LockMode::Frozen(frozen_source.into()),
&settings.resolver,
&client_builder,
&state,
@@ -518,7 +521,7 @@ async fn lock_and_sync(
project: VirtualProject,
project_dir: &Path,
lock_check: LockCheck,
frozen: bool,
frozen: Option<FrozenSource>,
active: Option<bool>,
no_sync: bool,
python: Option<String>,
@@ -535,7 +538,7 @@ async fn lock_and_sync(
preview: Preview,
) -> Result<ExitStatus> {
// If frozen, don't touch the lock or sync at all
if frozen {
if frozen.is_some() {
return Ok(ExitStatus::Success);
}

View File

@@ -567,6 +567,11 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
let args = PipCompileSettings::resolve(args, filesystem, environment);
show_settings!(args);
// Check for conflicts between offline and refresh.
globals
.network_settings
.check_refresh_conflict(&args.refresh);
// Initialize the cache.
let cache = cache.init().await?.with_refresh(
args.refresh
@@ -677,6 +682,11 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
let args = PipSyncSettings::resolve(args, filesystem, environment);
show_settings!(args);
// Check for conflicts between offline and refresh.
globals
.network_settings
.check_refresh_conflict(&args.refresh);
// Initialize the cache.
let cache = cache.init().await?.with_refresh(
args.refresh
@@ -840,6 +850,11 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
}
}
// Check for conflicts between offline and refresh.
globals
.network_settings
.check_refresh_conflict(&args.refresh);
// Initialize the cache.
let cache = cache.init().await?.with_refresh(
args.refresh
@@ -1101,6 +1116,11 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
let args = settings::BuildSettings::resolve(args, filesystem, environment);
show_settings!(args);
// Check for conflicts between offline and refresh.
globals
.network_settings
.check_refresh_conflict(&args.refresh);
// Initialize the cache.
let cache = cache.init().await?.with_refresh(
args.refresh
@@ -1162,6 +1182,11 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
let args = settings::VenvSettings::resolve(args, filesystem, environment);
show_settings!(args);
// Check for conflicts between offline and refresh.
globals
.network_settings
.check_refresh_conflict(&args.refresh);
// Initialize the cache.
let cache = cache.init().await?.with_refresh(
args.refresh
@@ -1325,6 +1350,11 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
);
show_settings!(args);
// Check for conflicts between offline and refresh.
globals
.network_settings
.check_refresh_conflict(&args.refresh);
// Initialize the cache.
let cache = cache.init().await?.with_refresh(
args.refresh
@@ -1410,6 +1440,11 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
let args = settings::ToolInstallSettings::resolve(args, filesystem, environment);
show_settings!(args);
// Check for conflicts between offline and refresh.
globals
.network_settings
.check_refresh_conflict(&args.refresh);
// Initialize the cache.
let cache = cache.init().await?.with_refresh(
args.refresh
@@ -1951,6 +1986,11 @@ async fn run_project(
let args = settings::RunSettings::resolve(args, filesystem, environment);
show_settings!(args);
// Check for conflicts between offline and refresh.
globals
.network_settings
.check_refresh_conflict(&args.refresh);
// Initialize the cache.
let cache = cache.init().await?.with_refresh(
args.refresh
@@ -2015,6 +2055,11 @@ async fn run_project(
let args = settings::SyncSettings::resolve(args, filesystem, environment);
show_settings!(args);
// Check for conflicts between offline and refresh.
globals
.network_settings
.check_refresh_conflict(&args.refresh);
// Initialize the cache.
let cache = cache.init().await?.with_refresh(
args.refresh
@@ -2065,6 +2110,11 @@ async fn run_project(
let args = settings::LockSettings::resolve(args, filesystem, environment);
show_settings!(args);
// Check for conflicts between offline and refresh.
globals
.network_settings
.check_refresh_conflict(&args.refresh);
// Initialize the cache.
let cache = cache.init().await?.with_refresh(
args.refresh
@@ -2177,6 +2227,11 @@ async fn run_project(
}
}
// Check for conflicts between offline and refresh.
globals
.network_settings
.check_refresh_conflict(&args.refresh);
// Initialize the cache.
let cache = cache.init().await?.with_refresh(
args.refresh
@@ -2240,6 +2295,11 @@ async fn run_project(
let args = settings::RemoveSettings::resolve(args, filesystem, environment);
show_settings!(args);
// Check for conflicts between offline and refresh.
globals
.network_settings
.check_refresh_conflict(&args.refresh);
// Initialize the cache.
let cache = cache.init().await?.with_refresh(
args.refresh
@@ -2284,6 +2344,11 @@ async fn run_project(
let args = settings::VersionSettings::resolve(args, filesystem, environment);
show_settings!(args);
// Check for conflicts between offline and refresh.
globals
.network_settings
.check_refresh_conflict(&args.refresh);
// Initialize the cache.
let cache = cache.init().await?.with_refresh(
args.refresh

View File

@@ -21,7 +21,10 @@ use uv_cli::{
use uv_cli::{
AuthorFrom, BuildArgs, ExportArgs, FormatArgs, PublishArgs, PythonDirArgs,
ResolverInstallerArgs, ToolUpgradeArgs,
options::{flag, resolver_installer_options, resolver_options},
options::{
Flag, FlagSource, check_conflicts, flag, resolve_flag, resolver_installer_options,
resolver_options,
},
};
use uv_client::Connectivity;
use uv_configuration::{
@@ -87,7 +90,7 @@ impl GlobalSettings {
environment: &EnvironmentOptions,
) -> Self {
let network_settings = NetworkSettings::resolve(args, workspace, environment);
let python_preference = resolve_python_preference(args, workspace);
let python_preference = resolve_python_preference(args, workspace, environment);
Self {
required_version: workspace
.and_then(|workspace| workspace.globals.required_version.clone()),
@@ -140,9 +143,7 @@ impl GlobalSettings {
},
show_settings: args.show_settings,
preview: Preview::from_args(
flag(args.preview, args.no_preview, "preview")
.combine(workspace.and_then(|workspace| workspace.globals.preview))
.unwrap_or(false),
resolve_preview(args, workspace, environment),
args.no_preview,
&args.preview_features,
),
@@ -158,8 +159,15 @@ impl GlobalSettings {
.unwrap_or_default(),
// Disable the progress bar with `RUST_LOG` to avoid progress fragments interleaving
// with log messages.
no_progress: args.no_progress || std::env::var_os(EnvVars::RUST_LOG).is_some(),
installer_metadata: !args.no_installer_metadata,
no_progress: resolve_flag(args.no_progress, "no-progress", environment.no_progress)
.is_enabled()
|| std::env::var_os(EnvVars::RUST_LOG).is_some(),
installer_metadata: !resolve_flag(
args.no_installer_metadata,
"no-installer-metadata",
environment.no_installer_metadata,
)
.is_enabled(),
}
}
}
@@ -167,10 +175,33 @@ impl GlobalSettings {
fn resolve_python_preference(
args: &GlobalArgs,
workspace: Option<&FilesystemOptions>,
environment: &EnvironmentOptions,
) -> PythonPreference {
if args.managed_python {
// Resolve flags from CLI and environment variables.
let managed_python = resolve_flag(
args.managed_python,
"managed-python",
environment.managed_python,
);
let no_managed_python = resolve_flag(
args.no_managed_python,
"no-managed-python",
environment.no_managed_python,
);
// Check for conflicts between managed_python and python_preference.
if managed_python.is_enabled() && args.python_preference.is_some() {
check_conflicts(managed_python, Flag::from_cli("python-preference"));
}
// Check for conflicts between no_managed_python and python_preference.
if no_managed_python.is_enabled() && args.python_preference.is_some() {
check_conflicts(no_managed_python, Flag::from_cli("python-preference"));
}
if managed_python.is_enabled() {
PythonPreference::OnlyManaged
} else if args.no_managed_python {
} else if no_managed_python.is_enabled() {
PythonPreference::OnlySystem
} else {
args.python_preference
@@ -179,10 +210,34 @@ fn resolve_python_preference(
}
}
/// Resolve the preview setting from CLI, environment, and workspace config.
fn resolve_preview(
args: &GlobalArgs,
workspace: Option<&FilesystemOptions>,
environment: &EnvironmentOptions,
) -> bool {
// CLI takes precedence
match flag(args.preview, args.no_preview, "preview") {
Some(value) => value,
None => {
// Check environment variable
if environment.preview.value == Some(true) {
true
} else {
// Fall back to workspace config
workspace
.and_then(|workspace| workspace.globals.preview)
.unwrap_or(false)
}
}
}
}
/// The resolved network settings to use for any invocation of the CLI.
#[derive(Debug, Clone)]
pub(crate) struct NetworkSettings {
pub(crate) connectivity: Connectivity,
pub(crate) offline: Flag,
pub(crate) native_tls: bool,
pub(crate) http_proxy: Option<ProxyUrl>,
pub(crate) https_proxy: Option<ProxyUrl>,
@@ -198,17 +253,45 @@ impl NetworkSettings {
workspace: Option<&FilesystemOptions>,
environment: &EnvironmentOptions,
) -> Self {
let connectivity = if flag(args.offline, args.no_offline, "offline")
.combine(workspace.and_then(|workspace| workspace.globals.offline))
.unwrap_or(false)
{
// Resolve offline flag from CLI, environment variable, and workspace config.
// Precedence: CLI > Env var > Workspace config > default (false).
let offline = match flag(args.offline, args.no_offline, "offline") {
Some(true) => Flag::from_cli("offline"),
Some(false) => Flag::disabled(),
None => {
// CLI didn't provide a value, check environment variable.
let env_flag = resolve_flag(false, "offline", environment.offline);
if env_flag.is_enabled() {
env_flag
} else if workspace
.and_then(|workspace| workspace.globals.offline)
.unwrap_or(false)
{
// Workspace config enabled offline mode.
Flag::from_config("offline")
} else {
Flag::disabled()
}
}
};
let connectivity = if offline.is_enabled() {
Connectivity::Offline
} else {
Connectivity::Online
};
let native_tls = flag(args.native_tls, args.no_native_tls, "native-tls")
.combine(workspace.and_then(|workspace| workspace.globals.native_tls))
.unwrap_or(false);
let native_tls = match flag(args.native_tls, args.no_native_tls, "native-tls") {
Some(value) => value,
None => {
if environment.native_tls.value == Some(true) {
true
} else {
workspace
.and_then(|workspace| workspace.globals.native_tls)
.unwrap_or(false)
}
}
};
let allow_insecure_host = args
.allow_insecure_host
.as_ref()
@@ -232,6 +315,7 @@ impl NetworkSettings {
Self {
connectivity,
offline,
native_tls,
http_proxy,
https_proxy,
@@ -241,6 +325,19 @@ impl NetworkSettings {
retries: environment.http_retries,
}
}
/// Check if offline mode conflicts with a refresh request.
///
/// This should be called when a command uses refresh functionality to ensure
/// offline mode and refresh are not both enabled.
pub(crate) fn check_refresh_conflict(&self, refresh: &Refresh) {
if !matches!(refresh, Refresh::None(_)) {
// TODO(charlie): `Refresh` isn't a `Flag`, so we create a synthetic one here
// (which matches Clap's representation). Consider a dedicated helper for
// conflicts with CLI-only arguments.
check_conflicts(self.offline, Flag::from_cli("refresh"));
}
}
}
/// The resolved cache settings to use for any invocation of the CLI.
@@ -363,7 +460,11 @@ impl InitSettings {
#[derive(Debug, Clone, Copy)]
pub(crate) enum LockCheckSource {
/// The user invoked `uv <command> --locked`
Locked,
LockedCli,
/// The `UV_LOCKED` environment variable was set.
LockedEnv,
/// The `locked` option was set via workspace configuration.
LockedConfiguration,
/// The user invoked `uv <command> --check`
Check,
}
@@ -371,7 +472,9 @@ pub(crate) enum LockCheckSource {
impl std::fmt::Display for LockCheckSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Locked => write!(f, "--locked"),
Self::LockedCli => write!(f, "--locked"),
Self::LockedEnv => write!(f, "UV_LOCKED=1"),
Self::LockedConfiguration => write!(f, "locked (workspace configuration)"),
Self::Check => write!(f, "--check"),
}
}
@@ -386,11 +489,48 @@ pub(crate) enum LockCheck {
Disabled,
}
/// The source of the frozen flag.
#[derive(Debug, Clone, Copy)]
pub(crate) enum FrozenSource {
/// The `--frozen` flag was provided on CLI.
Cli,
/// The `UV_FROZEN` environment variable was set.
Env,
/// The `frozen` option was set via workspace configuration.
Configuration,
}
/// Convert a resolved flag to an optional frozen source.
fn resolve_frozen(flag: Flag) -> Option<FrozenSource> {
if flag.is_enabled() {
Some(match flag.source() {
Some(FlagSource::Cli) | None => FrozenSource::Cli,
Some(FlagSource::Env(_)) => FrozenSource::Env,
Some(FlagSource::Config) => FrozenSource::Configuration,
})
} else {
None
}
}
/// Convert a resolved flag to a lock check.
fn resolve_lock_check(flag: Flag) -> LockCheck {
if flag.is_enabled() {
LockCheck::Enabled(match flag.source() {
Some(FlagSource::Cli) | None => LockCheckSource::LockedCli,
Some(FlagSource::Env(_)) => LockCheckSource::LockedEnv,
Some(FlagSource::Config) => LockCheckSource::LockedConfiguration,
})
} else {
LockCheck::Disabled
}
}
/// The resolved settings to use for a `run` invocation.
#[derive(Debug, Clone)]
pub(crate) struct RunSettings {
pub(crate) lock_check: LockCheck,
pub(crate) frozen: bool,
pub(crate) frozen: Option<FrozenSource>,
pub(crate) extras: ExtrasSpecification,
pub(crate) groups: DependencyGroups,
pub(crate) editable: Option<EditableMode>,
@@ -477,13 +617,24 @@ impl RunSettings {
.map(|fs| fs.install_mirrors.clone())
.unwrap_or_default();
// Resolve flags from CLI and environment variables.
let locked = resolve_flag(locked, "locked", environment.locked);
let frozen = resolve_flag(frozen, "frozen", environment.frozen);
// Check for conflicts between locked and frozen.
check_conflicts(locked, frozen);
let dev = dev || environment.dev.value == Some(true);
let no_dev = no_dev || environment.no_dev.value == Some(true);
let no_editable = no_editable || environment.no_editable.value == Some(true);
let isolated = isolated || environment.isolated.value == Some(true);
let show_resolution = show_resolution || environment.show_resolution.value == Some(true);
let no_env_file = no_env_file || environment.no_env_file.value == Some(true);
Self {
lock_check: if locked {
LockCheck::Enabled(LockCheckSource::Locked)
} else {
LockCheck::Disabled
},
frozen,
lock_check: resolve_lock_check(locked),
frozen: resolve_frozen(frozen),
extras: ExtrasSpecification::from_args(
extra.unwrap_or_default(),
no_extra,
@@ -646,6 +797,11 @@ impl ToolRunSettings {
}
let lfs = GitLfsSetting::new(lfs.then_some(true), environment.lfs);
// Resolve flags from CLI and environment variables.
let isolated = isolated || environment.isolated.value == Some(true);
let show_resolution = show_resolution || environment.show_resolution.value == Some(true);
let no_env_file = no_env_file || environment.no_env_file.value == Some(true);
Self {
command,
from,
@@ -1387,7 +1543,7 @@ impl PythonPinSettings {
#[derive(Debug, Clone)]
pub(crate) struct SyncSettings {
pub(crate) lock_check: LockCheck,
pub(crate) frozen: bool,
pub(crate) frozen: Option<FrozenSource>,
pub(crate) dry_run: DryRun,
pub(crate) script: Option<PathBuf>,
pub(crate) active: Option<bool>,
@@ -1472,16 +1628,22 @@ impl SyncSettings {
} else {
DryRun::from_args(dry_run)
};
let lock_check = if locked {
LockCheck::Enabled(LockCheckSource::Locked)
} else {
LockCheck::Disabled
};
// Resolve flags from CLI and environment variables.
let locked = resolve_flag(locked, "locked", environment.locked);
let frozen = resolve_flag(frozen, "frozen", environment.frozen);
// Check for conflicts between locked and frozen.
check_conflicts(locked, frozen);
let dev = dev || environment.dev.value == Some(true);
let no_dev = no_dev || environment.no_dev.value == Some(true);
let no_editable = no_editable || environment.no_editable.value == Some(true);
Self {
output_format,
lock_check,
frozen,
lock_check: resolve_lock_check(locked),
frozen: resolve_frozen(frozen),
dry_run,
script,
active: flag(active, no_active, "active"),
@@ -1538,7 +1700,7 @@ impl SyncSettings {
#[derive(Debug, Clone)]
pub(crate) struct LockSettings {
pub(crate) lock_check: LockCheck,
pub(crate) frozen: bool,
pub(crate) frozen: Option<FrozenSource>,
pub(crate) dry_run: DryRun,
pub(crate) script: Option<PathBuf>,
pub(crate) python: Option<String>,
@@ -1572,17 +1734,22 @@ impl LockSettings {
.map(|fs| fs.install_mirrors.clone())
.unwrap_or_default();
// Resolve flags from CLI and environment variables.
let locked = resolve_flag(locked, "locked", environment.locked);
let frozen = resolve_flag(check_exists, "frozen", environment.frozen);
// Check for conflicts between locked and frozen.
check_conflicts(locked, frozen);
let lock_check = if check {
LockCheck::Enabled(LockCheckSource::Check)
} else if locked {
LockCheck::Enabled(LockCheckSource::Locked)
} else {
LockCheck::Disabled
resolve_lock_check(locked)
};
Self {
lock_check,
frozen: check_exists,
frozen: resolve_frozen(frozen),
dry_run: DryRun::from_args(dry_run),
script,
python: python.and_then(Maybe::into_option),
@@ -1600,7 +1767,7 @@ impl LockSettings {
#[derive(Debug, Clone)]
pub(crate) struct AddSettings {
pub(crate) lock_check: LockCheck,
pub(crate) frozen: bool,
pub(crate) frozen: Option<FrozenSource>,
pub(crate) active: Option<bool>,
pub(crate) no_sync: bool,
pub(crate) packages: Vec<String>,
@@ -1682,6 +1849,10 @@ impl AddSettings {
only_install_package,
} = args;
// Resolve flags from CLI and environment variables.
let dev = dev || environment.dev.value == Some(true);
let no_editable = no_editable || environment.no_editable.value == Some(true);
let dependency_type = if let Some(extra) = optional {
DependencyType::Optional(extra)
} else if let Some(group) = group {
@@ -1760,15 +1931,22 @@ impl AddSettings {
let bounds = bounds.or(filesystem.as_ref().and_then(|fs| fs.add.add_bounds));
let lfs = GitLfsSetting::new(lfs.then_some(true), environment.lfs);
// Resolve flags from CLI and environment variables.
let locked = resolve_flag(locked, "locked", environment.locked);
let frozen = resolve_flag(frozen, "frozen", environment.frozen);
let no_sync = resolve_flag(no_sync, "no-sync", environment.no_sync);
// Check for conflicts between locked and frozen.
check_conflicts(locked, frozen);
// Check for conflicts between no_sync and frozen.
check_conflicts(no_sync, frozen);
Self {
lock_check: if locked {
LockCheck::Enabled(LockCheckSource::Locked)
} else {
LockCheck::Disabled
},
frozen,
lock_check: resolve_lock_check(locked),
frozen: resolve_frozen(frozen),
active: flag(active, no_active, "active"),
no_sync,
no_sync: no_sync.is_enabled(),
packages,
requirements,
constraints: constraints
@@ -1815,7 +1993,7 @@ impl AddSettings {
#[derive(Debug, Clone)]
pub(crate) struct RemoveSettings {
pub(crate) lock_check: LockCheck,
pub(crate) frozen: bool,
pub(crate) frozen: Option<FrozenSource>,
pub(crate) active: Option<bool>,
pub(crate) no_sync: bool,
pub(crate) packages: Vec<PackageName>,
@@ -1854,6 +2032,9 @@ impl RemoveSettings {
python,
} = args;
// Resolve flags from CLI and environment variables.
let dev = dev || environment.dev.value == Some(true);
let dependency_type = if let Some(extra) = optional {
DependencyType::Optional(extra)
} else if let Some(group) = group {
@@ -1874,15 +2055,22 @@ impl RemoveSettings {
.map(|requirement| requirement.name)
.collect();
// Resolve flags from CLI and environment variables.
let locked = resolve_flag(locked, "locked", environment.locked);
let frozen = resolve_flag(frozen, "frozen", environment.frozen);
let no_sync = resolve_flag(no_sync, "no-sync", environment.no_sync);
// Check for conflicts between locked and frozen.
check_conflicts(locked, frozen);
// Check for conflicts between no_sync and frozen.
check_conflicts(no_sync, frozen);
Self {
lock_check: if locked {
LockCheck::Enabled(LockCheckSource::Locked)
} else {
LockCheck::Disabled
},
frozen,
lock_check: resolve_lock_check(locked),
frozen: resolve_frozen(frozen),
active: flag(active, no_active, "active"),
no_sync,
no_sync: no_sync.is_enabled(),
packages,
dependency_type,
package,
@@ -1910,7 +2098,7 @@ pub(crate) struct VersionSettings {
pub(crate) output_format: VersionFormat,
pub(crate) dry_run: bool,
pub(crate) lock_check: LockCheck,
pub(crate) frozen: bool,
pub(crate) frozen: Option<FrozenSource>,
pub(crate) active: Option<bool>,
pub(crate) no_sync: bool,
pub(crate) package: Option<PackageName>,
@@ -1951,20 +2139,27 @@ impl VersionSettings {
.map(|fs| fs.install_mirrors.clone())
.unwrap_or_default();
// Resolve flags from CLI and environment variables.
let locked = resolve_flag(locked, "locked", environment.locked);
let frozen = resolve_flag(frozen, "frozen", environment.frozen);
let no_sync = resolve_flag(no_sync, "no-sync", environment.no_sync);
// Check for conflicts between locked and frozen.
check_conflicts(locked, frozen);
// Check for conflicts between no_sync and frozen.
check_conflicts(no_sync, frozen);
Self {
value,
bump,
short,
output_format,
dry_run,
lock_check: if locked {
LockCheck::Enabled(LockCheckSource::Locked)
} else {
LockCheck::Disabled
},
frozen,
lock_check: resolve_lock_check(locked),
frozen: resolve_frozen(frozen),
active: flag(active, no_active, "active"),
no_sync,
no_sync: no_sync.is_enabled(),
package,
python: python.and_then(Maybe::into_option),
refresh: Refresh::from(refresh),
@@ -1984,7 +2179,7 @@ impl VersionSettings {
pub(crate) struct TreeSettings {
pub(crate) groups: DependencyGroups,
pub(crate) lock_check: LockCheck,
pub(crate) frozen: bool,
pub(crate) frozen: Option<FrozenSource>,
pub(crate) universal: bool,
pub(crate) depth: u8,
pub(crate) prune: Vec<PackageName>,
@@ -2035,6 +2230,16 @@ impl TreeSettings {
.map(|fs| fs.install_mirrors.clone())
.unwrap_or_default();
// Resolve flags from CLI and environment variables.
let locked = resolve_flag(locked, "locked", environment.locked);
let frozen = resolve_flag(frozen, "frozen", environment.frozen);
// Check for conflicts between locked and frozen.
check_conflicts(locked, frozen);
let dev = dev || environment.dev.value == Some(true);
let no_dev = no_dev || environment.no_dev.value == Some(true);
Self {
groups: DependencyGroups::from_args(
dev,
@@ -2046,12 +2251,8 @@ impl TreeSettings {
only_group,
all_groups,
),
lock_check: if locked {
LockCheck::Enabled(LockCheckSource::Locked)
} else {
LockCheck::Disabled
},
frozen,
lock_check: resolve_lock_check(locked),
frozen: resolve_frozen(frozen),
universal,
depth: tree.depth,
prune: tree.prune,
@@ -2087,7 +2288,7 @@ pub(crate) struct ExportSettings {
pub(crate) install_options: InstallOptions,
pub(crate) output_file: Option<PathBuf>,
pub(crate) lock_check: LockCheck,
pub(crate) frozen: bool,
pub(crate) frozen: Option<FrozenSource>,
pub(crate) include_annotations: bool,
pub(crate) include_header: bool,
pub(crate) script: Option<PathBuf>,
@@ -2140,7 +2341,7 @@ impl ExportSettings {
no_emit_package,
only_emit_package,
locked,
frozen,
frozen: frozen_cli,
resolver,
build,
refresh,
@@ -2152,6 +2353,17 @@ impl ExportSettings {
.map(|fs| fs.install_mirrors.clone())
.unwrap_or_default();
// Resolve flags from CLI and environment variables.
let locked = resolve_flag(locked, "locked", environment.locked);
let frozen = resolve_flag(frozen_cli, "frozen", environment.frozen);
// Check for conflicts between locked and frozen.
check_conflicts(locked, frozen);
let dev = dev || environment.dev.value == Some(true);
let no_dev = no_dev || environment.no_dev.value == Some(true);
let no_editable = no_editable || environment.no_editable.value == Some(true);
Self {
format,
all_packages,
@@ -2189,12 +2401,8 @@ impl ExportSettings {
only_emit_package,
),
output_file,
lock_check: if locked {
LockCheck::Enabled(LockCheckSource::Locked)
} else {
LockCheck::Disabled
},
frozen,
lock_check: resolve_lock_check(locked),
frozen: resolve_frozen(frozen),
include_annotations: flag(annotate, no_annotate, "annotate").unwrap_or(true),
include_header: flag(header, no_header, "header").unwrap_or(true),
script,
@@ -2250,7 +2458,7 @@ pub(crate) struct PipCompileSettings {
pub(crate) build_constraints: Vec<PathBuf>,
pub(crate) constraints_from_workspace: Vec<Requirement>,
pub(crate) overrides_from_workspace: Vec<Requirement>,
pub(crate) excludes_from_workspace: Vec<uv_normalize::PackageName>,
pub(crate) excludes_from_workspace: Vec<PackageName>,
pub(crate) build_constraints_from_workspace: Vec<Requirement>,
pub(crate) environments: SupportedEnvironments,
pub(crate) refresh: Refresh,
@@ -2566,7 +2774,7 @@ pub(crate) struct PipInstallSettings {
pub(crate) dry_run: DryRun,
pub(crate) constraints_from_workspace: Vec<Requirement>,
pub(crate) overrides_from_workspace: Vec<Requirement>,
pub(crate) excludes_from_workspace: Vec<uv_normalize::PackageName>,
pub(crate) excludes_from_workspace: Vec<PackageName>,
pub(crate) build_constraints_from_workspace: Vec<Requirement>,
pub(crate) modifications: Modifications,
pub(crate) refresh: Refresh,
@@ -3169,6 +3377,10 @@ impl VenvSettings {
exclude_newer_package,
} = args;
// Resolve flags from CLI and environment variables.
let seed = seed || environment.venv_seed.value == Some(true);
let clear = clear || environment.venv_clear.value == Some(true);
Self {
seed,
allow_existing,

View File

@@ -57,8 +57,7 @@ fn help() {
--color <COLOR_CHOICE>
Control the use of color in output [possible values: auto, always, never]
--native-tls
Whether to load TLS certificates from the platform's native certificate store [env:
UV_NATIVE_TLS=]
Whether to load TLS certificates from the platform's native store [env: UV_NATIVE_TLS=]
--offline
Disable network access [env: UV_OFFLINE=]
--allow-insecure-host <ALLOW_INSECURE_HOST>
@@ -138,8 +137,7 @@ fn help_flag() {
--color <COLOR_CHOICE>
Control the use of color in output [possible values: auto, always, never]
--native-tls
Whether to load TLS certificates from the platform's native certificate store [env:
UV_NATIVE_TLS=]
Whether to load TLS certificates from the platform's native store [env: UV_NATIVE_TLS=]
--offline
Disable network access [env: UV_OFFLINE=]
--allow-insecure-host <ALLOW_INSECURE_HOST>
@@ -218,8 +216,7 @@ fn help_short_flag() {
--color <COLOR_CHOICE>
Control the use of color in output [possible values: auto, always, never]
--native-tls
Whether to load TLS certificates from the platform's native certificate store [env:
UV_NATIVE_TLS=]
Whether to load TLS certificates from the platform's native store [env: UV_NATIVE_TLS=]
--offline
Disable network access [env: UV_OFFLINE=]
--allow-insecure-host <ALLOW_INSECURE_HOST>
@@ -331,14 +328,14 @@ fn help_subcommand() {
By default, uv prefers using Python versions it manages. However, it will use system
Python versions if a uv-managed Python is not installed. This option disables use of
system Python versions.
[env: UV_MANAGED_PYTHON=]
--no-managed-python
Disable use of uv-managed Python versions.
Instead, uv will search for a suitable Python version on the system.
[env: UV_NO_MANAGED_PYTHON=]
--no-python-downloads
@@ -369,7 +366,7 @@ fn help_subcommand() {
- never: Disables colored output
--native-tls
Whether to load TLS certificates from the platform's native certificate store.
Whether to load TLS certificates from the platform's native store.
By default, uv loads certificates from the bundled `webpki-roots` crate. The
`webpki-roots` are a reliable set of trust roots from Mozilla, and including them in uv
@@ -378,14 +375,14 @@ fn help_subcommand() {
However, in some cases, you may want to use the platform's native certificate store,
especially if you're relying on a corporate trust root (e.g., for a mandatory proxy)
that's included in your system's certificate store.
[env: UV_NATIVE_TLS=]
--offline
Disable network access.
When disabled, uv will only use locally cached data and locally available files.
[env: UV_OFFLINE=]
--allow-insecure-host <ALLOW_INSECURE_HOST>
@@ -406,7 +403,7 @@ fn help_subcommand() {
Hide all progress outputs.
For example, spinners or progress bars.
[env: UV_NO_PROGRESS=]
--directory <DIRECTORY>
@@ -455,7 +452,6 @@ fn help_subcommand() {
Use `uv help python <command>` for more information on a specific command.
----- stderr -----
"#);
}
@@ -604,14 +600,14 @@ fn help_subsubcommand() {
By default, uv prefers using Python versions it manages. However, it will use system
Python versions if a uv-managed Python is not installed. This option disables use of
system Python versions.
[env: UV_MANAGED_PYTHON=]
--no-managed-python
Disable use of uv-managed Python versions.
Instead, uv will search for a suitable Python version on the system.
[env: UV_NO_MANAGED_PYTHON=]
--no-python-downloads
@@ -642,7 +638,7 @@ fn help_subsubcommand() {
- never: Disables colored output
--native-tls
Whether to load TLS certificates from the platform's native certificate store.
Whether to load TLS certificates from the platform's native store.
By default, uv loads certificates from the bundled `webpki-roots` crate. The
`webpki-roots` are a reliable set of trust roots from Mozilla, and including them in uv
@@ -651,14 +647,14 @@ fn help_subsubcommand() {
However, in some cases, you may want to use the platform's native certificate store,
especially if you're relying on a corporate trust root (e.g., for a mandatory proxy)
that's included in your system's certificate store.
[env: UV_NATIVE_TLS=]
--offline
Disable network access.
When disabled, uv will only use locally cached data and locally available files.
[env: UV_OFFLINE=]
--allow-insecure-host <ALLOW_INSECURE_HOST>
@@ -679,7 +675,7 @@ fn help_subsubcommand() {
Hide all progress outputs.
For example, spinners or progress bars.
[env: UV_NO_PROGRESS=]
--directory <DIRECTORY>
@@ -726,7 +722,6 @@ fn help_subsubcommand() {
-h, --help
Display the concise help for this command
----- stderr -----
"#);
}
@@ -772,8 +767,7 @@ fn help_flag_subcommand() {
--color <COLOR_CHOICE>
Control the use of color in output [possible values: auto, always, never]
--native-tls
Whether to load TLS certificates from the platform's native certificate store [env:
UV_NATIVE_TLS=]
Whether to load TLS certificates from the platform's native store [env: UV_NATIVE_TLS=]
--offline
Disable network access [env: UV_OFFLINE=]
--allow-insecure-host <ALLOW_INSECURE_HOST>
@@ -853,8 +847,7 @@ fn help_flag_subsubcommand() {
--color <COLOR_CHOICE>
Control the use of color in output [possible values: auto, always, never]
--native-tls
Whether to load TLS certificates from the platform's native certificate store [env:
UV_NATIVE_TLS=]
Whether to load TLS certificates from the platform's native store [env: UV_NATIVE_TLS=]
--offline
Disable network access [env: UV_OFFLINE=]
--allow-insecure-host <ALLOW_INSECURE_HOST>
@@ -1015,8 +1008,7 @@ fn help_with_global_option() {
--color <COLOR_CHOICE>
Control the use of color in output [possible values: auto, always, never]
--native-tls
Whether to load TLS certificates from the platform's native certificate store [env:
UV_NATIVE_TLS=]
Whether to load TLS certificates from the platform's native store [env: UV_NATIVE_TLS=]
--offline
Disable network access [env: UV_OFFLINE=]
--allow-insecure-host <ALLOW_INSECURE_HOST>
@@ -1138,8 +1130,7 @@ fn help_with_no_pager() {
--color <COLOR_CHOICE>
Control the use of color in output [possible values: auto, always, never]
--native-tls
Whether to load TLS certificates from the platform's native certificate store [env:
UV_NATIVE_TLS=]
Whether to load TLS certificates from the platform's native store [env: UV_NATIVE_TLS=]
--offline
Disable network access [env: UV_OFFLINE=]
--allow-insecure-host <ALLOW_INSECURE_HOST>

View File

@@ -12273,6 +12273,25 @@ fn conflicting_flags_clap_bug() {
);
}
/// Test that `--offline` and `--refresh` conflict.
#[test]
fn offline_refresh_conflict() {
let context = TestContext::new("3.12");
uv_snapshot!(context.filters(), context.pip_install()
.arg("tqdm")
.arg("--offline")
.arg("--refresh"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: the argument `--offline` cannot be used with `--refresh`
"
);
}
/// Test that shebang arguments are stripped when installing scripts
#[test]
#[cfg(unix)]

View File

@@ -64,6 +64,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -270,6 +271,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -477,6 +479,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -716,6 +719,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -924,6 +928,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -1108,6 +1113,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -1341,6 +1347,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -1582,6 +1589,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -1881,6 +1889,7 @@ fn resolve_find_links() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -2111,6 +2120,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -2300,6 +2310,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -2539,6 +2550,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -2801,6 +2813,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -2980,6 +2993,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -3159,6 +3173,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -3340,6 +3355,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -3540,6 +3556,7 @@ fn resolve_tool() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -3734,6 +3751,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -3947,6 +3965,7 @@ fn resolve_both() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -4199,6 +4218,7 @@ fn resolve_both_special_fields() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -4530,6 +4550,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -4836,6 +4857,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -5018,6 +5040,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -5208,6 +5231,7 @@ fn allow_insecure_host() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -5412,6 +5436,7 @@ fn index_priority() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -5653,6 +5678,7 @@ fn index_priority() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -5900,6 +5926,7 @@ fn index_priority() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -6142,6 +6169,7 @@ fn index_priority() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -6391,6 +6419,7 @@ fn index_priority() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -6633,6 +6662,7 @@ fn index_priority() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -6888,6 +6918,7 @@ fn verify_hashes() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -7060,6 +7091,7 @@ fn verify_hashes() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -7230,6 +7262,7 @@ fn verify_hashes() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -7402,6 +7435,7 @@ fn verify_hashes() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -7572,6 +7606,7 @@ fn verify_hashes() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -7743,6 +7778,7 @@ fn verify_hashes() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -7929,6 +7965,7 @@ fn preview_features() {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -7966,7 +8003,7 @@ fn preview_features() {
output_format: Text,
dry_run: false,
lock_check: Disabled,
frozen: false,
frozen: None,
active: None,
no_sync: false,
package: None,
@@ -8047,6 +8084,7 @@ fn preview_features() {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -8084,7 +8122,7 @@ fn preview_features() {
output_format: Text,
dry_run: false,
lock_check: Disabled,
frozen: false,
frozen: None,
active: None,
no_sync: false,
package: None,
@@ -8165,6 +8203,7 @@ fn preview_features() {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -8202,7 +8241,7 @@ fn preview_features() {
output_format: Text,
dry_run: false,
lock_check: Disabled,
frozen: false,
frozen: None,
active: None,
no_sync: false,
package: None,
@@ -8283,6 +8322,7 @@ fn preview_features() {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -8320,7 +8360,7 @@ fn preview_features() {
output_format: Text,
dry_run: false,
lock_check: Disabled,
frozen: false,
frozen: None,
active: None,
no_sync: false,
package: None,
@@ -8401,6 +8441,7 @@ fn preview_features() {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -8438,7 +8479,7 @@ fn preview_features() {
output_format: Text,
dry_run: false,
lock_check: Disabled,
frozen: false,
frozen: None,
active: None,
no_sync: false,
package: None,
@@ -8521,6 +8562,7 @@ fn preview_features() {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -8558,7 +8600,7 @@ fn preview_features() {
output_format: Text,
dry_run: false,
lock_check: Disabled,
frozen: false,
frozen: None,
active: None,
no_sync: false,
package: None,
@@ -8660,6 +8702,7 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -8840,6 +8883,7 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -9043,6 +9087,7 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -9221,6 +9266,7 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -9393,6 +9439,7 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -9566,6 +9613,7 @@ fn upgrade_pip_cli_config_interaction() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -9804,6 +9852,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -9836,7 +9885,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
}
LockSettings {
lock_check: Disabled,
frozen: false,
frozen: None,
dry_run: Disabled,
script: None,
python: None,
@@ -9927,6 +9976,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -9959,7 +10009,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
}
LockSettings {
lock_check: Disabled,
frozen: false,
frozen: None,
dry_run: Disabled,
script: None,
python: None,
@@ -10073,6 +10123,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -10105,7 +10156,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
}
LockSettings {
lock_check: Disabled,
frozen: false,
frozen: None,
dry_run: Disabled,
script: None,
python: None,
@@ -10194,6 +10245,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -10226,7 +10278,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
}
LockSettings {
lock_check: Disabled,
frozen: false,
frozen: None,
dry_run: Disabled,
script: None,
python: None,
@@ -10305,6 +10357,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -10337,7 +10390,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
}
LockSettings {
lock_check: Disabled,
frozen: false,
frozen: None,
dry_run: Disabled,
script: None,
python: None,
@@ -10417,6 +10470,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -10449,7 +10503,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
}
LockSettings {
lock_check: Disabled,
frozen: false,
frozen: None,
dry_run: Disabled,
script: None,
python: None,
@@ -10593,6 +10647,7 @@ fn build_isolation_override() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,
@@ -10768,6 +10823,7 @@ fn build_isolation_override() -> anyhow::Result<()> {
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
offline: Disabled,
native_tls: false,
http_proxy: None,
https_proxy: None,