diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index f31a8e8499d9..0bf5415478e9 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -1263,9 +1263,12 @@ pub struct PipSyncArgs { /// Require a matching hash for each requirement. /// - /// Hash-checking mode is all or nothing. If enabled, _all_ requirements must be provided - /// with a corresponding hash or set of hashes. Additionally, if enabled, _all_ requirements - /// must either be pinned to exact versions (e.g., `==1.0.0`), or be specified via direct URL. + /// By default, uv will verify any available hashes in the requirements file, but will not + /// require that all requirements have an associated hash. + /// + /// When `--require-hashes` is enabled, _all_ requirements must include a hash or set of hashes, + /// and _all_ requirements must either be pinned to exact versions (e.g., `==1.0.0`), or be + /// specified via direct URL. /// /// Hash-checking mode introduces a number of additional constraints: /// @@ -1284,20 +1287,20 @@ pub struct PipSyncArgs { #[arg(long, overrides_with("require_hashes"), hide = true)] pub no_require_hashes: bool, - /// Validate any hashes provided in the requirements file. + #[arg(long, overrides_with("no_verify_hashes"), hide = true)] + pub verify_hashes: bool, + + /// Disable validation of hashes in the requirements file. /// - /// Unlike `--require-hashes`, `--verify-hashes` does not require that all requirements have - /// hashes; instead, it will limit itself to verifying the hashes of those requirements that do - /// include them. + /// By default, uv will verify any available hashes in the requirements file, but will not + /// require that all requirements have an associated hash. To enforce hash validation, use + /// `--require-hashes`. #[arg( long, - env = EnvVars::UV_VERIFY_HASHES, + env = EnvVars::UV_NO_VERIFY_HASHES, value_parser = clap::builder::BoolishValueParser::new(), - overrides_with("no_verify_hashes"), + overrides_with("verify_hashes"), )] - pub verify_hashes: bool, - - #[arg(long, overrides_with("verify_hashes"), hide = true)] pub no_verify_hashes: bool, /// The Python interpreter into which packages should be installed. @@ -1546,9 +1549,12 @@ pub struct PipInstallArgs { /// Require a matching hash for each requirement. /// - /// Hash-checking mode is all or nothing. If enabled, _all_ requirements must be provided - /// with a corresponding hash or set of hashes. Additionally, if enabled, _all_ requirements - /// must either be pinned to exact versions (e.g., `==1.0.0`), or be specified via direct URL. + /// By default, uv will verify any available hashes in the requirements file, but will not + /// require that all requirements have an associated hash. + /// + /// When `--require-hashes` is enabled, _all_ requirements must include a hash or set of hashes, + /// and _all_ requirements must either be pinned to exact versions (e.g., `==1.0.0`), or be + /// specified via direct URL. /// /// Hash-checking mode introduces a number of additional constraints: /// @@ -1567,20 +1573,20 @@ pub struct PipInstallArgs { #[arg(long, overrides_with("require_hashes"), hide = true)] pub no_require_hashes: bool, - /// Validate any hashes provided in the requirements file. + #[arg(long, overrides_with("no_verify_hashes"), hide = true)] + pub verify_hashes: bool, + + /// Disable validation of hashes in the requirements file. /// - /// Unlike `--require-hashes`, `--verify-hashes` does not require that all requirements have - /// hashes; instead, it will limit itself to verifying the hashes of those requirements that do - /// include them. + /// By default, uv will verify any available hashes in the requirements file, but will not + /// require that all requirements have an associated hash. To enforce hash validation, use + /// `--require-hashes`. #[arg( long, - env = EnvVars::UV_VERIFY_HASHES, + env = EnvVars::UV_NO_VERIFY_HASHES, value_parser = clap::builder::BoolishValueParser::new(), - overrides_with("no_verify_hashes"), + overrides_with("verify_hashes"), )] - pub verify_hashes: bool, - - #[arg(long, overrides_with("verify_hashes"), hide = true)] pub no_verify_hashes: bool, /// The Python interpreter into which packages should be installed. @@ -2177,12 +2183,14 @@ pub struct BuildArgs { #[arg(long, short, env = EnvVars::UV_BUILD_CONSTRAINT, value_delimiter = ' ', value_parser = parse_maybe_file_path)] pub build_constraint: Vec>, - /// Require a matching hash for each build requirement. + /// Require a matching hash for each requirement. + /// + /// By default, uv will verify any available hashes in the requirements file, but will not + /// require that all requirements have an associated hash. /// - /// Hash-checking mode is all or nothing. If enabled, _all_ build requirements must be provided - /// with a corresponding hash or set of hashes via the `--build-constraint` argument. - /// Additionally, if enabled, _all_ requirements must either be pinned to exact versions - /// (e.g., `==1.0.0`), or be specified via direct URL. + /// When `--require-hashes` is enabled, _all_ requirements must include a hash or set of hashes, + /// and _all_ requirements must either be pinned to exact versions (e.g., `==1.0.0`), or be + /// specified via direct URL. /// /// Hash-checking mode introduces a number of additional constraints: /// @@ -2201,20 +2209,20 @@ pub struct BuildArgs { #[arg(long, overrides_with("require_hashes"), hide = true)] pub no_require_hashes: bool, - /// Validate any hashes provided in the build constraints file. + #[arg(long, overrides_with("no_verify_hashes"), hide = true)] + pub verify_hashes: bool, + + /// Disable validation of hashes in the requirements file. /// - /// Unlike `--require-hashes`, `--verify-hashes` does not require that all requirements have - /// hashes; instead, it will limit itself to verifying the hashes of those requirements that do - /// include them. + /// By default, uv will verify any available hashes in the requirements file, but will not + /// require that all requirements have an associated hash. To enforce hash validation, use + /// `--require-hashes`. #[arg( long, - env = EnvVars::UV_VERIFY_HASHES, + env = EnvVars::UV_NO_VERIFY_HASHES, value_parser = clap::builder::BoolishValueParser::new(), - overrides_with("no_verify_hashes"), + overrides_with("verify_hashes"), )] - pub verify_hashes: bool, - - #[arg(long, overrides_with("verify_hashes"), hide = true)] pub no_verify_hashes: bool, /// The Python interpreter to use for the build environment. diff --git a/crates/uv-configuration/src/hash.rs b/crates/uv-configuration/src/hash.rs index 52f6d0a01149..cadebbd1510a 100644 --- a/crates/uv-configuration/src/hash.rs +++ b/crates/uv-configuration/src/hash.rs @@ -9,13 +9,26 @@ pub enum HashCheckingMode { impl HashCheckingMode { /// Return the [`HashCheckingMode`] from the command-line arguments, if any. - pub fn from_args(require_hashes: bool, verify_hashes: bool) -> Option { - if require_hashes { + /// + /// By default, the hash checking mode is [`HashCheckingMode::Verify`]. If `--require-hashes` is + /// passed, the hash checking mode is [`HashCheckingMode::Require`]. If `--no-verify-hashes` is + /// passed, then no hash checking is performed. + pub fn from_args(require_hashes: Option, verify_hashes: Option) -> Option { + if require_hashes == Some(true) { + // Given `--require-hashes`, always require hashes, regardless of any other flags. Some(Self::Require) - } else if verify_hashes { + } else if verify_hashes == Some(true) { + // Given `--verify-hashes`, always verify hashes, regardless of any other flags. Some(Self::Verify) - } else { + } else if verify_hashes == Some(false) { + // Given `--no-verify-hashes` (without `--require-hashes`), do not verify hashes. + None + } else if require_hashes == Some(false) { + // Given `--no-require-hashes` (without `--verify-hashes`), do not require hashes. None + } else { + // By default, verify hashes. + Some(Self::Verify) } } diff --git a/crates/uv-settings/src/settings.rs b/crates/uv-settings/src/settings.rs index 9b2d261da50e..200e3940df67 100644 --- a/crates/uv-settings/src/settings.rs +++ b/crates/uv-settings/src/settings.rs @@ -1348,7 +1348,7 @@ pub struct PipOptions { /// hashes; instead, it will limit itself to verifying the hashes of those requirements that do /// include them. #[option( - default = "false", + default = "true", value_type = "bool", example = r#" verify-hashes = true diff --git a/crates/uv-static/src/env_vars.rs b/crates/uv-static/src/env_vars.rs index 63dc454a7117..33c2f2db8f10 100644 --- a/crates/uv-static/src/env_vars.rs +++ b/crates/uv-static/src/env_vars.rs @@ -167,8 +167,9 @@ impl EnvVars { /// Equivalent to the `--token` argument for self update. A GitHub token for authentication. pub const UV_GITHUB_TOKEN: &'static str = "UV_GITHUB_TOKEN"; - /// Equivalent to the `--verify-hashes` argument. Verifies included hashes. - pub const UV_VERIFY_HASHES: &'static str = "UV_VERIFY_HASHES"; + /// Equivalent to the `--no-verify-hashes` argument. Disables hash verification for + /// `requirements.txt` files. + pub const UV_NO_VERIFY_HASHES: &'static str = "UV_VERIFY_HASHES"; /// Equivalent to the `--allow-insecure-host` argument. pub const UV_INSECURE_HOST: &'static str = "UV_INSECURE_HOST"; diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 8b6b44669809..375b00d97d65 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -2003,8 +2003,8 @@ impl BuildSettings { .filter_map(Maybe::into_option) .collect(), hash_checking: HashCheckingMode::from_args( - flag(require_hashes, no_require_hashes).unwrap_or_default(), - flag(verify_hashes, no_verify_hashes).unwrap_or_default(), + flag(require_hashes, no_require_hashes), + flag(verify_hashes, no_verify_hashes), ), python: python.and_then(Maybe::into_option), refresh: Refresh::from(refresh), @@ -2641,12 +2641,8 @@ impl PipSettings { .unwrap_or_default(), link_mode: args.link_mode.combine(link_mode).unwrap_or_default(), hash_checking: HashCheckingMode::from_args( - args.require_hashes - .combine(require_hashes) - .unwrap_or_default(), - args.verify_hashes - .combine(verify_hashes) - .unwrap_or_default(), + args.require_hashes.combine(require_hashes), + args.verify_hashes.combine(verify_hashes), ), python: args.python.combine(python), system: args.system.combine(system).unwrap_or_default(), diff --git a/crates/uv/tests/it/build.rs b/crates/uv/tests/it/build.rs index d5404191c942..68e361940833 100644 --- a/crates/uv/tests/it/build.rs +++ b/crates/uv/tests/it/build.rs @@ -1447,103 +1447,40 @@ fn sha() -> Result<()> { project.child("src").child("__init__.py").touch()?; project.child("README").touch()?; - // Ignore an incorrect hash, if `--require-hashes` is not provided. + // Reject an incorrect hash. let constraints = project.child("constraints.txt"); constraints.write_str("setuptools==68.2.2 --hash=sha256:a248cb506794bececcddeddb1678bc722f9cfcacf02f98f7c0af6b9ed893caf2")?; uv_snapshot!(&filters, context.build().arg("--build-constraint").arg("constraints.txt").current_dir(&project), @r###" - success: true - exit_code: 0 + success: false + exit_code: 2 ----- stdout ----- ----- stderr ----- Building source distribution... - running egg_info - creating src/project.egg-info - writing src/project.egg-info/PKG-INFO - writing dependency_links to src/project.egg-info/dependency_links.txt - writing requirements to src/project.egg-info/requires.txt - writing top-level names to src/project.egg-info/top_level.txt - writing manifest file 'src/project.egg-info/SOURCES.txt' - reading manifest file 'src/project.egg-info/SOURCES.txt' - writing manifest file 'src/project.egg-info/SOURCES.txt' - running sdist - running egg_info - writing src/project.egg-info/PKG-INFO - writing dependency_links to src/project.egg-info/dependency_links.txt - writing requirements to src/project.egg-info/requires.txt - writing top-level names to src/project.egg-info/top_level.txt - reading manifest file 'src/project.egg-info/SOURCES.txt' - writing manifest file 'src/project.egg-info/SOURCES.txt' - running check - creating project-0.1.0 - creating project-0.1.0/src - creating project-0.1.0/src/project.egg-info - copying files to project-0.1.0... - copying README -> project-0.1.0 - copying pyproject.toml -> project-0.1.0 - copying src/__init__.py -> project-0.1.0/src - copying src/project.egg-info/PKG-INFO -> project-0.1.0/src/project.egg-info - copying src/project.egg-info/SOURCES.txt -> project-0.1.0/src/project.egg-info - copying src/project.egg-info/dependency_links.txt -> project-0.1.0/src/project.egg-info - copying src/project.egg-info/requires.txt -> project-0.1.0/src/project.egg-info - copying src/project.egg-info/top_level.txt -> project-0.1.0/src/project.egg-info - Writing project-0.1.0/setup.cfg - Creating tar archive - removing 'project-0.1.0' (and everything under it) - Building wheel from source distribution... - running egg_info - writing src/project.egg-info/PKG-INFO - writing dependency_links to src/project.egg-info/dependency_links.txt - writing requirements to src/project.egg-info/requires.txt - writing top-level names to src/project.egg-info/top_level.txt - reading manifest file 'src/project.egg-info/SOURCES.txt' - writing manifest file 'src/project.egg-info/SOURCES.txt' - running bdist_wheel - running build - running build_py - creating build - creating build/lib - copying src/__init__.py -> build/lib - running egg_info - writing src/project.egg-info/PKG-INFO - writing dependency_links to src/project.egg-info/dependency_links.txt - writing requirements to src/project.egg-info/requires.txt - writing top-level names to src/project.egg-info/top_level.txt - reading manifest file 'src/project.egg-info/SOURCES.txt' - writing manifest file 'src/project.egg-info/SOURCES.txt' - installing to build/bdist.linux-x86_64/wheel - running install - running install_lib - creating build/bdist.linux-x86_64 - creating build/bdist.linux-x86_64/wheel - copying build/lib/__init__.py -> build/bdist.linux-x86_64/wheel - running install_egg_info - Copying src/project.egg-info to build/bdist.linux-x86_64/wheel/project-0.1.0-py3.8.egg-info - running install_scripts - creating build/bdist.linux-x86_64/wheel/project-0.1.0.dist-info/WHEEL - creating '[TEMP_DIR]/project/dist/[TMP]/wheel' to it - adding '__init__.py' - adding 'project-0.1.0.dist-info/METADATA' - adding 'project-0.1.0.dist-info/WHEEL' - adding 'project-0.1.0.dist-info/top_level.txt' - adding 'project-0.1.0.dist-info/RECORD' - removing build/bdist.linux-x86_64/wheel - Successfully built dist/project-0.1.0.tar.gz and dist/project-0.1.0-py3-none-any.whl + error: Failed to install requirements from `build-system.requires` + Caused by: Failed to download `setuptools==68.2.2` + Caused by: Hash mismatch for `setuptools==68.2.2` + + Expected: + sha256:a248cb506794bececcddeddb1678bc722f9cfcacf02f98f7c0af6b9ed893caf2 + + Computed: + sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a "###); project .child("dist") .child("project-0.1.0.tar.gz") - .assert(predicate::path::is_file()); + .assert(predicate::path::missing()); project .child("dist") .child("project-0.1.0-py3-none-any.whl") - .assert(predicate::path::is_file()); + .assert(predicate::path::missing()); fs_err::remove_dir_all(project.child("dist"))?; - // Reject an incorrect hash. + // Reject an incorrect hash with --requires-hashes. uv_snapshot!(&filters, context.build().arg("--build-constraint").arg("constraints.txt").arg("--require-hashes").current_dir(&project), @r###" success: false exit_code: 2 @@ -1598,6 +1535,8 @@ fn sha() -> Result<()> { .child("project-0.1.0-py3-none-any.whl") .assert(predicate::path::missing()); + fs_err::remove_dir_all(project.child("dist"))?; + // Accept a correct hash. let constraints = project.child("constraints.txt"); constraints.write_str("setuptools==68.2.2 --hash=sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a")?; @@ -1610,10 +1549,12 @@ fn sha() -> Result<()> { ----- stderr ----- Building source distribution... running egg_info + creating src/project.egg-info writing src/project.egg-info/PKG-INFO writing dependency_links to src/project.egg-info/dependency_links.txt writing requirements to src/project.egg-info/requires.txt writing top-level names to src/project.egg-info/top_level.txt + writing manifest file 'src/project.egg-info/SOURCES.txt' reading manifest file 'src/project.egg-info/SOURCES.txt' writing manifest file 'src/project.egg-info/SOURCES.txt' running sdist diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 1ce62f494b1a..57b30771057c 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -6241,6 +6241,44 @@ fn verify_hashes_mismatch() -> Result<()> { "### ); + uv_snapshot!(context.pip_install() + .arg("-r") + .arg("requirements.txt"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + × Failed to download `anyio==4.0.0` + ╰─▶ Hash mismatch for `anyio==4.0.0` + + Expected: + sha256:afdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + sha256:a7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a + + Computed: + sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f + "### + ); + + uv_snapshot!(context.pip_install() + .arg("-r") + .arg("requirements.txt") + .arg("--no-verify-hashes"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.0.0 + + idna==3.6 + + sniffio==1.3.1 + "### + ); + Ok(()) } diff --git a/crates/uv/tests/it/show_settings.rs b/crates/uv/tests/it/show_settings.rs index 8083e3639038..2f964484b3c8 100644 --- a/crates/uv/tests/it/show_settings.rs +++ b/crates/uv/tests/it/show_settings.rs @@ -176,7 +176,9 @@ fn resolve_uv_toml() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -327,7 +329,9 @@ fn resolve_uv_toml() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -479,7 +483,9 @@ fn resolve_uv_toml() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -663,7 +669,9 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -787,7 +795,9 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -949,7 +959,9 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -1153,7 +1165,9 @@ fn resolve_index_url() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -1364,7 +1378,9 @@ fn resolve_index_url() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -1540,7 +1556,9 @@ fn resolve_find_links() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -1686,7 +1704,9 @@ fn resolve_top_level() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -1882,7 +1902,9 @@ fn resolve_top_level() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -2061,7 +2083,9 @@ fn resolve_top_level() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -2207,7 +2231,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -2336,7 +2362,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -2465,7 +2493,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -2596,7 +2626,9 @@ fn resolve_user_configuration() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -2907,7 +2939,9 @@ fn resolve_poetry_toml() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -3093,7 +3127,9 @@ fn resolve_both() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -3367,7 +3403,9 @@ fn resolve_config_file() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -3590,7 +3628,9 @@ fn resolve_skip_empty() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -3722,7 +3762,9 @@ fn resolve_skip_empty() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -3873,7 +3915,9 @@ fn allow_insecure_host() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -4075,7 +4119,9 @@ fn index_priority() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -4256,7 +4302,9 @@ fn index_priority() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -4443,7 +4491,9 @@ fn index_priority() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -4625,7 +4675,9 @@ fn index_priority() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -4814,7 +4866,9 @@ fn index_priority() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, @@ -4996,7 +5050,770 @@ fn index_priority() -> anyhow::Result<()> { link_mode: Clone, compile_bytecode: false, sources: Enabled, - hash_checking: None, + hash_checking: Some( + Verify, + ), + upgrade: None, + reinstall: None, + }, + } + + ----- stderr ----- + "### + ); + + Ok(()) +} + +/// Verify hashes by default. +#[test] +#[cfg_attr( + windows, + ignore = "Configuration tests are not yet supported on Windows" +)] +fn verify_hashes() -> anyhow::Result<()> { + let context = TestContext::new("3.12"); + + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("anyio>3.0.0")?; + + uv_snapshot!(context.filters(), add_shared_args(context.pip_install()) + .arg("-r") + .arg("requirements.in") + .arg("--show-settings"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + GlobalSettings { + quiet: false, + verbose: 0, + color: Auto, + native_tls: false, + concurrency: Concurrency { + downloads: 50, + builds: 16, + installs: 8, + }, + connectivity: Online, + allow_insecure_host: [], + show_settings: true, + preview: Disabled, + python_preference: Managed, + python_downloads: Automatic, + no_progress: false, + } + CacheSettings { + no_cache: false, + cache_dir: Some( + "[CACHE_DIR]/", + ), + } + PipInstallSettings { + package: [], + requirement: [ + "requirements.in", + ], + editable: [], + constraint: [], + override: [], + build_constraint: [], + dry_run: false, + constraints_from_workspace: [], + overrides_from_workspace: [], + modifications: Sufficient, + refresh: None( + Timestamp( + SystemTime { + tv_sec: [TIME], + tv_nsec: [TIME], + }, + ), + ), + settings: PipSettings { + index_locations: IndexLocations { + indexes: [], + flat_index: [], + no_index: false, + }, + python: None, + install_mirrors: PythonInstallMirrors { + python_install_mirror: None, + pypy_install_mirror: None, + }, + system: false, + extras: None, + break_system_packages: false, + target: None, + prefix: None, + index_strategy: FirstIndex, + keyring_provider: Disabled, + no_build_isolation: false, + no_build_isolation_package: [], + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, + allow_empty_requirements: false, + strict: false, + dependency_mode: Transitive, + resolution: Highest, + prerelease: IfNecessaryOrExplicit, + dependency_metadata: DependencyMetadata( + {}, + ), + output_file: None, + no_strip_extras: false, + no_strip_markers: false, + no_annotate: false, + no_header: false, + custom_compile_command: None, + generate_hashes: false, + config_setting: ConfigSettings( + {}, + ), + python_version: None, + python_platform: None, + universal: false, + exclude_newer: Some( + ExcludeNewer( + 2024-03-25T00:00:00Z, + ), + ), + no_emit_package: [], + emit_index_url: false, + emit_find_links: false, + emit_build_options: false, + emit_marker_expression: false, + emit_index_annotation: false, + annotation_style: Split, + link_mode: Clone, + compile_bytecode: false, + sources: Enabled, + hash_checking: Some( + Verify, + ), + upgrade: None, + reinstall: None, + }, + } + + ----- stderr ----- + "### + ); + + uv_snapshot!(context.filters(), add_shared_args(context.pip_install()) + .arg("-r") + .arg("requirements.in") + .arg("--no-verify-hashes") + .arg("--show-settings"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + GlobalSettings { + quiet: false, + verbose: 0, + color: Auto, + native_tls: false, + concurrency: Concurrency { + downloads: 50, + builds: 16, + installs: 8, + }, + connectivity: Online, + allow_insecure_host: [], + show_settings: true, + preview: Disabled, + python_preference: Managed, + python_downloads: Automatic, + no_progress: false, + } + CacheSettings { + no_cache: false, + cache_dir: Some( + "[CACHE_DIR]/", + ), + } + PipInstallSettings { + package: [], + requirement: [ + "requirements.in", + ], + editable: [], + constraint: [], + override: [], + build_constraint: [], + dry_run: false, + constraints_from_workspace: [], + overrides_from_workspace: [], + modifications: Sufficient, + refresh: None( + Timestamp( + SystemTime { + tv_sec: [TIME], + tv_nsec: [TIME], + }, + ), + ), + settings: PipSettings { + index_locations: IndexLocations { + indexes: [], + flat_index: [], + no_index: false, + }, + python: None, + install_mirrors: PythonInstallMirrors { + python_install_mirror: None, + pypy_install_mirror: None, + }, + system: false, + extras: None, + break_system_packages: false, + target: None, + prefix: None, + index_strategy: FirstIndex, + keyring_provider: Disabled, + no_build_isolation: false, + no_build_isolation_package: [], + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, + allow_empty_requirements: false, + strict: false, + dependency_mode: Transitive, + resolution: Highest, + prerelease: IfNecessaryOrExplicit, + dependency_metadata: DependencyMetadata( + {}, + ), + output_file: None, + no_strip_extras: false, + no_strip_markers: false, + no_annotate: false, + no_header: false, + custom_compile_command: None, + generate_hashes: false, + config_setting: ConfigSettings( + {}, + ), + python_version: None, + python_platform: None, + universal: false, + exclude_newer: Some( + ExcludeNewer( + 2024-03-25T00:00:00Z, + ), + ), + no_emit_package: [], + emit_index_url: false, + emit_find_links: false, + emit_build_options: false, + emit_marker_expression: false, + emit_index_annotation: false, + annotation_style: Split, + link_mode: Clone, + compile_bytecode: false, + sources: Enabled, + hash_checking: None, + upgrade: None, + reinstall: None, + }, + } + + ----- stderr ----- + "### + ); + + uv_snapshot!(context.filters(), add_shared_args(context.pip_install()) + .arg("-r") + .arg("requirements.in") + .arg("--require-hashes") + .arg("--show-settings"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + GlobalSettings { + quiet: false, + verbose: 0, + color: Auto, + native_tls: false, + concurrency: Concurrency { + downloads: 50, + builds: 16, + installs: 8, + }, + connectivity: Online, + allow_insecure_host: [], + show_settings: true, + preview: Disabled, + python_preference: Managed, + python_downloads: Automatic, + no_progress: false, + } + CacheSettings { + no_cache: false, + cache_dir: Some( + "[CACHE_DIR]/", + ), + } + PipInstallSettings { + package: [], + requirement: [ + "requirements.in", + ], + editable: [], + constraint: [], + override: [], + build_constraint: [], + dry_run: false, + constraints_from_workspace: [], + overrides_from_workspace: [], + modifications: Sufficient, + refresh: None( + Timestamp( + SystemTime { + tv_sec: [TIME], + tv_nsec: [TIME], + }, + ), + ), + settings: PipSettings { + index_locations: IndexLocations { + indexes: [], + flat_index: [], + no_index: false, + }, + python: None, + install_mirrors: PythonInstallMirrors { + python_install_mirror: None, + pypy_install_mirror: None, + }, + system: false, + extras: None, + break_system_packages: false, + target: None, + prefix: None, + index_strategy: FirstIndex, + keyring_provider: Disabled, + no_build_isolation: false, + no_build_isolation_package: [], + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, + allow_empty_requirements: false, + strict: false, + dependency_mode: Transitive, + resolution: Highest, + prerelease: IfNecessaryOrExplicit, + dependency_metadata: DependencyMetadata( + {}, + ), + output_file: None, + no_strip_extras: false, + no_strip_markers: false, + no_annotate: false, + no_header: false, + custom_compile_command: None, + generate_hashes: false, + config_setting: ConfigSettings( + {}, + ), + python_version: None, + python_platform: None, + universal: false, + exclude_newer: Some( + ExcludeNewer( + 2024-03-25T00:00:00Z, + ), + ), + no_emit_package: [], + emit_index_url: false, + emit_find_links: false, + emit_build_options: false, + emit_marker_expression: false, + emit_index_annotation: false, + annotation_style: Split, + link_mode: Clone, + compile_bytecode: false, + sources: Enabled, + hash_checking: Some( + Require, + ), + upgrade: None, + reinstall: None, + }, + } + + ----- stderr ----- + "### + ); + + uv_snapshot!(context.filters(), add_shared_args(context.pip_install()) + .arg("-r") + .arg("requirements.in") + .arg("--no-require-hashes") + .arg("--show-settings"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + GlobalSettings { + quiet: false, + verbose: 0, + color: Auto, + native_tls: false, + concurrency: Concurrency { + downloads: 50, + builds: 16, + installs: 8, + }, + connectivity: Online, + allow_insecure_host: [], + show_settings: true, + preview: Disabled, + python_preference: Managed, + python_downloads: Automatic, + no_progress: false, + } + CacheSettings { + no_cache: false, + cache_dir: Some( + "[CACHE_DIR]/", + ), + } + PipInstallSettings { + package: [], + requirement: [ + "requirements.in", + ], + editable: [], + constraint: [], + override: [], + build_constraint: [], + dry_run: false, + constraints_from_workspace: [], + overrides_from_workspace: [], + modifications: Sufficient, + refresh: None( + Timestamp( + SystemTime { + tv_sec: [TIME], + tv_nsec: [TIME], + }, + ), + ), + settings: PipSettings { + index_locations: IndexLocations { + indexes: [], + flat_index: [], + no_index: false, + }, + python: None, + install_mirrors: PythonInstallMirrors { + python_install_mirror: None, + pypy_install_mirror: None, + }, + system: false, + extras: None, + break_system_packages: false, + target: None, + prefix: None, + index_strategy: FirstIndex, + keyring_provider: Disabled, + no_build_isolation: false, + no_build_isolation_package: [], + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, + allow_empty_requirements: false, + strict: false, + dependency_mode: Transitive, + resolution: Highest, + prerelease: IfNecessaryOrExplicit, + dependency_metadata: DependencyMetadata( + {}, + ), + output_file: None, + no_strip_extras: false, + no_strip_markers: false, + no_annotate: false, + no_header: false, + custom_compile_command: None, + generate_hashes: false, + config_setting: ConfigSettings( + {}, + ), + python_version: None, + python_platform: None, + universal: false, + exclude_newer: Some( + ExcludeNewer( + 2024-03-25T00:00:00Z, + ), + ), + no_emit_package: [], + emit_index_url: false, + emit_find_links: false, + emit_build_options: false, + emit_marker_expression: false, + emit_index_annotation: false, + annotation_style: Split, + link_mode: Clone, + compile_bytecode: false, + sources: Enabled, + hash_checking: None, + upgrade: None, + reinstall: None, + }, + } + + ----- stderr ----- + "### + ); + + uv_snapshot!(context.filters(), add_shared_args(context.pip_install()) + .arg("-r") + .arg("requirements.in") + .env("UV_NO_VERIFY_HASHES", "1") + .arg("--show-settings"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + GlobalSettings { + quiet: false, + verbose: 0, + color: Auto, + native_tls: false, + concurrency: Concurrency { + downloads: 50, + builds: 16, + installs: 8, + }, + connectivity: Online, + allow_insecure_host: [], + show_settings: true, + preview: Disabled, + python_preference: Managed, + python_downloads: Automatic, + no_progress: false, + } + CacheSettings { + no_cache: false, + cache_dir: Some( + "[CACHE_DIR]/", + ), + } + PipInstallSettings { + package: [], + requirement: [ + "requirements.in", + ], + editable: [], + constraint: [], + override: [], + build_constraint: [], + dry_run: false, + constraints_from_workspace: [], + overrides_from_workspace: [], + modifications: Sufficient, + refresh: None( + Timestamp( + SystemTime { + tv_sec: [TIME], + tv_nsec: [TIME], + }, + ), + ), + settings: PipSettings { + index_locations: IndexLocations { + indexes: [], + flat_index: [], + no_index: false, + }, + python: None, + install_mirrors: PythonInstallMirrors { + python_install_mirror: None, + pypy_install_mirror: None, + }, + system: false, + extras: None, + break_system_packages: false, + target: None, + prefix: None, + index_strategy: FirstIndex, + keyring_provider: Disabled, + no_build_isolation: false, + no_build_isolation_package: [], + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, + allow_empty_requirements: false, + strict: false, + dependency_mode: Transitive, + resolution: Highest, + prerelease: IfNecessaryOrExplicit, + dependency_metadata: DependencyMetadata( + {}, + ), + output_file: None, + no_strip_extras: false, + no_strip_markers: false, + no_annotate: false, + no_header: false, + custom_compile_command: None, + generate_hashes: false, + config_setting: ConfigSettings( + {}, + ), + python_version: None, + python_platform: None, + universal: false, + exclude_newer: Some( + ExcludeNewer( + 2024-03-25T00:00:00Z, + ), + ), + no_emit_package: [], + emit_index_url: false, + emit_find_links: false, + emit_build_options: false, + emit_marker_expression: false, + emit_index_annotation: false, + annotation_style: Split, + link_mode: Clone, + compile_bytecode: false, + sources: Enabled, + hash_checking: Some( + Verify, + ), + upgrade: None, + reinstall: None, + }, + } + + ----- stderr ----- + "### + ); + + uv_snapshot!(context.filters(), add_shared_args(context.pip_install()) + .arg("-r") + .arg("requirements.in") + .arg("--verify-hashes") + .arg("--no-require-hashes") + .arg("--show-settings"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + GlobalSettings { + quiet: false, + verbose: 0, + color: Auto, + native_tls: false, + concurrency: Concurrency { + downloads: 50, + builds: 16, + installs: 8, + }, + connectivity: Online, + allow_insecure_host: [], + show_settings: true, + preview: Disabled, + python_preference: Managed, + python_downloads: Automatic, + no_progress: false, + } + CacheSettings { + no_cache: false, + cache_dir: Some( + "[CACHE_DIR]/", + ), + } + PipInstallSettings { + package: [], + requirement: [ + "requirements.in", + ], + editable: [], + constraint: [], + override: [], + build_constraint: [], + dry_run: false, + constraints_from_workspace: [], + overrides_from_workspace: [], + modifications: Sufficient, + refresh: None( + Timestamp( + SystemTime { + tv_sec: [TIME], + tv_nsec: [TIME], + }, + ), + ), + settings: PipSettings { + index_locations: IndexLocations { + indexes: [], + flat_index: [], + no_index: false, + }, + python: None, + install_mirrors: PythonInstallMirrors { + python_install_mirror: None, + pypy_install_mirror: None, + }, + system: false, + extras: None, + break_system_packages: false, + target: None, + prefix: None, + index_strategy: FirstIndex, + keyring_provider: Disabled, + no_build_isolation: false, + no_build_isolation_package: [], + build_options: BuildOptions { + no_binary: None, + no_build: None, + }, + allow_empty_requirements: false, + strict: false, + dependency_mode: Transitive, + resolution: Highest, + prerelease: IfNecessaryOrExplicit, + dependency_metadata: DependencyMetadata( + {}, + ), + output_file: None, + no_strip_extras: false, + no_strip_markers: false, + no_annotate: false, + no_header: false, + custom_compile_command: None, + generate_hashes: false, + config_setting: ConfigSettings( + {}, + ), + python_version: None, + python_platform: None, + universal: false, + exclude_newer: Some( + ExcludeNewer( + 2024-03-25T00:00:00Z, + ), + ), + no_emit_package: [], + emit_index_url: false, + emit_find_links: false, + emit_build_options: false, + emit_marker_expression: false, + emit_index_annotation: false, + annotation_style: Split, + link_mode: Clone, + compile_bytecode: false, + sources: Enabled, + hash_checking: Some( + Verify, + ), upgrade: None, reinstall: None, }, diff --git a/docs/configuration/environment.md b/docs/configuration/environment.md index b30ac7520dea..3d3f96d5f26c 100644 --- a/docs/configuration/environment.md +++ b/docs/configuration/environment.md @@ -188,6 +188,11 @@ Disables all progress output. For example, spinners and progress bars. Equivalent to the `--no-sync` command-line argument. If set, uv will skip updating the environment. +### `UV_NO_VERIFY_HASHES` + +Equivalent to the `--no-verify-hashes` argument. Disables hash verification for +`requirements.txt` files. + ### `UV_NO_WRAP` Use to disable line wrapping for diagnostics. @@ -317,10 +322,6 @@ Specifies the directory where uv stores managed tools. Used ephemeral environments like CI to install uv to a specific path while preventing the installer from modifying shell profiles or environment variables. -### `UV_VERIFY_HASHES` - -Equivalent to the `--verify-hashes` argument. Verifies included hashes. - ## Externally defined variables diff --git a/docs/reference/cli.md b/docs/reference/cli.md index d60596633472..16edb5f3e62b 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -5753,6 +5753,11 @@ uv pip sync [OPTIONS] ...
--no-sources

Ignore the tool.uv.sources table when resolving dependencies. Used to lock against the standards-compliant, publishable package metadata, as opposed to using any local or Git sources

+
--no-verify-hashes

Disable validation of hashes in the requirements file.

+ +

By default, uv will verify any available hashes in the requirements file, but will not require that all requirements have an associated hash. To enforce hash validation, use --require-hashes.

+ +

May also be set with the UV_VERIFY_HASHES environment variable.

--offline

Disable network access.

When disabled, uv will only use locally cached data and locally available files.

@@ -5859,7 +5864,9 @@ uv pip sync [OPTIONS] ...
--require-hashes

Require a matching hash for each requirement.

-

Hash-checking mode is all or nothing. If enabled, all requirements must be provided with a corresponding hash or set of hashes. Additionally, if enabled, all requirements must either be pinned to exact versions (e.g., ==1.0.0), or be specified via direct URL.

+

By default, uv will verify any available hashes in the requirements file, but will not require that all requirements have an associated hash.

+ +

When --require-hashes is enabled, all requirements must include a hash or set of hashes, and all requirements must either be pinned to exact versions (e.g., ==1.0.0), or be specified via direct URL.

Hash-checking mode introduces a number of additional constraints:

@@ -5883,11 +5890,6 @@ uv pip sync [OPTIONS] ...

You can configure fine-grained logging using the RUST_LOG environment variable. (<https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives>)

-
--verify-hashes

Validate any hashes provided in the requirements file.

- -

Unlike --require-hashes, --verify-hashes does not require that all requirements have hashes; instead, it will limit itself to verifying the hashes of those requirements that do include them.

- -

May also be set with the UV_VERIFY_HASHES environment variable.

--version, -V

Display the uv version

@@ -6128,6 +6130,11 @@ uv pip install [OPTIONS] |--editable
--no-sources

Ignore the tool.uv.sources table when resolving dependencies. Used to lock against the standards-compliant, publishable package metadata, as opposed to using any local or Git sources

+
--no-verify-hashes

Disable validation of hashes in the requirements file.

+ +

By default, uv will verify any available hashes in the requirements file, but will not require that all requirements have an associated hash. To enforce hash validation, use --require-hashes.

+ +

May also be set with the UV_VERIFY_HASHES environment variable.

--offline

Disable network access.

When disabled, uv will only use locally cached data and locally available files.

@@ -6259,7 +6266,9 @@ uv pip install [OPTIONS] |--editable
--require-hashes

Require a matching hash for each requirement.

-

Hash-checking mode is all or nothing. If enabled, all requirements must be provided with a corresponding hash or set of hashes. Additionally, if enabled, all requirements must either be pinned to exact versions (e.g., ==1.0.0), or be specified via direct URL.

+

By default, uv will verify any available hashes in the requirements file, but will not require that all requirements have an associated hash.

+ +

When --require-hashes is enabled, all requirements must include a hash or set of hashes, and all requirements must either be pinned to exact versions (e.g., ==1.0.0), or be specified via direct URL.

Hash-checking mode introduces a number of additional constraints:

@@ -6307,11 +6316,6 @@ uv pip install [OPTIONS] |--editable You can configure fine-grained logging using the RUST_LOG environment variable. (<https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives>)

-
--verify-hashes

Validate any hashes provided in the requirements file.

- -

Unlike --require-hashes, --verify-hashes does not require that all requirements have hashes; instead, it will limit itself to verifying the hashes of those requirements that do include them.

- -

May also be set with the UV_VERIFY_HASHES environment variable.

--version, -V

Display the uv version

@@ -7721,6 +7725,11 @@ uv build [OPTIONS] [SRC]
--no-sources

Ignore the tool.uv.sources table when resolving dependencies. Used to lock against the standards-compliant, publishable package metadata, as opposed to using any local or Git sources

+
--no-verify-hashes

Disable validation of hashes in the requirements file.

+ +

By default, uv will verify any available hashes in the requirements file, but will not require that all requirements have an associated hash. To enforce hash validation, use --require-hashes.

+ +

May also be set with the UV_VERIFY_HASHES environment variable.

--offline

Disable network access.

When disabled, uv will only use locally cached data and locally available files.

@@ -7792,9 +7801,11 @@ uv build [OPTIONS] [SRC]
--refresh-package refresh-package

Refresh cached data for a specific package

-
--require-hashes

Require a matching hash for each build requirement.

+
--require-hashes

Require a matching hash for each requirement.

+ +

By default, uv will verify any available hashes in the requirements file, but will not require that all requirements have an associated hash.

-

Hash-checking mode is all or nothing. If enabled, all build requirements must be provided with a corresponding hash or set of hashes via the --build-constraint argument. Additionally, if enabled, all requirements must either be pinned to exact versions (e.g., ==1.0.0), or be specified via direct URL.

+

When --require-hashes is enabled, all requirements must include a hash or set of hashes, and all requirements must either be pinned to exact versions (e.g., ==1.0.0), or be specified via direct URL.

Hash-checking mode introduces a number of additional constraints:

@@ -7827,11 +7838,6 @@ uv build [OPTIONS] [SRC]

You can configure fine-grained logging using the RUST_LOG environment variable. (<https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives>)

-
--verify-hashes

Validate any hashes provided in the build constraints file.

- -

Unlike --require-hashes, --verify-hashes does not require that all requirements have hashes; instead, it will limit itself to verifying the hashes of those requirements that do include them.

- -

May also be set with the UV_VERIFY_HASHES environment variable.

--version, -V

Display the uv version

--wheel

Build a binary distribution ("wheel") from the given directory

diff --git a/docs/reference/settings.md b/docs/reference/settings.md index df8c9a9fd555..02d3aecbcfbc 100644 --- a/docs/reference/settings.md +++ b/docs/reference/settings.md @@ -3104,7 +3104,7 @@ Unlike `--require-hashes`, `--verify-hashes` does not require that all requireme hashes; instead, it will limit itself to verifying the hashes of those requirements that do include them. -**Default value**: `false` +**Default value**: `true` **Type**: `bool`