From a9d554fa90a7c0e9fc7ef17319ce79f2fc0cfe2f Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 10 Apr 2024 14:07:03 -0400 Subject: [PATCH] Add a `--require-hashes` command-line setting (#2824) ## Summary I'll likely only merge this once the PR chain is further along, but this PR wires up the setting fro the CLI. --- crates/uv-resolver/src/options.rs | 10 +++++++++ crates/uv/src/commands/pip_install.rs | 7 +++++++ crates/uv/src/commands/pip_sync.rs | 6 ++++++ crates/uv/src/main.rs | 30 +++++++++++++++++++++++++++ 4 files changed, 53 insertions(+) diff --git a/crates/uv-resolver/src/options.rs b/crates/uv-resolver/src/options.rs index 55e6e5f07..764728391 100644 --- a/crates/uv-resolver/src/options.rs +++ b/crates/uv-resolver/src/options.rs @@ -9,6 +9,7 @@ pub struct Options { pub prerelease_mode: PreReleaseMode, pub dependency_mode: DependencyMode, pub exclude_newer: Option>, + pub require_hashes: bool, } /// Builder for [`Options`]. @@ -18,6 +19,7 @@ pub struct OptionsBuilder { prerelease_mode: PreReleaseMode, dependency_mode: DependencyMode, exclude_newer: Option>, + require_hashes: bool, } impl OptionsBuilder { @@ -54,6 +56,13 @@ impl OptionsBuilder { self } + /// Sets the `--requires-hash` flag. + #[must_use] + pub fn require_hashes(mut self, require_hashes: bool) -> Self { + self.require_hashes = require_hashes; + self + } + /// Builds the options. pub fn build(self) -> Options { Options { @@ -61,6 +70,7 @@ impl OptionsBuilder { prerelease_mode: self.prerelease_mode, dependency_mode: self.dependency_mode, exclude_newer: self.exclude_newer, + require_hashes: self.require_hashes, } } } diff --git a/crates/uv/src/commands/pip_install.rs b/crates/uv/src/commands/pip_install.rs index bc002de4a..280c0c0b7 100644 --- a/crates/uv/src/commands/pip_install.rs +++ b/crates/uv/src/commands/pip_install.rs @@ -67,6 +67,7 @@ pub(crate) async fn pip_install( reinstall: Reinstall, link_mode: LinkMode, compile: bool, + require_hashes: bool, setup_py: SetupPyStrategy, connectivity: Connectivity, config_settings: &ConfigSettings, @@ -84,6 +85,11 @@ pub(crate) async fn pip_install( printer: Printer, ) -> Result { let start = std::time::Instant::now(); + + if require_hashes { + warn_user!("Hash-checking mode (via `--require-hashes`) is not yet supported."); + } + let client_builder = BaseClientBuilder::new() .connectivity(connectivity) .native_tls(native_tls) @@ -292,6 +298,7 @@ pub(crate) async fn pip_install( .prerelease_mode(prerelease_mode) .dependency_mode(dependency_mode) .exclude_newer(exclude_newer) + .require_hashes(require_hashes) .build(); // Resolve the requirements. diff --git a/crates/uv/src/commands/pip_sync.rs b/crates/uv/src/commands/pip_sync.rs index 5229613b7..b5d991ad1 100644 --- a/crates/uv/src/commands/pip_sync.rs +++ b/crates/uv/src/commands/pip_sync.rs @@ -45,6 +45,7 @@ pub(crate) async fn pip_sync( reinstall: &Reinstall, link_mode: LinkMode, compile: bool, + require_hashes: bool, index_locations: IndexLocations, index_strategy: IndexStrategy, keyring_provider: KeyringProvider, @@ -64,6 +65,10 @@ pub(crate) async fn pip_sync( ) -> Result { let start = std::time::Instant::now(); + if require_hashes { + warn_user!("Hash-checking mode (via `--require-hashes`) is not yet supported."); + } + let client_builder = BaseClientBuilder::new() .connectivity(connectivity) .native_tls(native_tls) @@ -289,6 +294,7 @@ pub(crate) async fn pip_sync( // Resolve with `--no-deps`. let options = OptionsBuilder::new() .dependency_mode(DependencyMode::Direct) + .require_hashes(require_hashes) .build(); // Create a bound on the progress bar, since we know the number of packages upfront. diff --git a/crates/uv/src/main.rs b/crates/uv/src/main.rs index 0e162184f..d6b525fad 100644 --- a/crates/uv/src/main.rs +++ b/crates/uv/src/main.rs @@ -593,6 +593,20 @@ struct PipSyncArgs { #[clap(long, default_value_t, value_enum, env = "UV_INDEX_STRATEGY")] index_strategy: IndexStrategy, + /// 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. + /// + /// Hash-checking mode introduces a number of additional constraints: + /// - Git dependencies are not supported. + /// - Editable installs are not supported. + /// - Local dependencies are not supported, unless they point to a specific wheel (`.whl`) or + /// source archive (`.zip`, `.tar.gz`), as opposed to a directory. + #[clap(long, hide = true)] + require_hashes: bool, + /// Attempt to use `keyring` for authentication for index urls /// /// Function's similar to `pip`'s `--keyring-provider subprocess` argument, @@ -867,6 +881,20 @@ struct PipInstallArgs { #[clap(long, default_value_t, value_enum, env = "UV_INDEX_STRATEGY")] index_strategy: IndexStrategy, + /// 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. + /// + /// Hash-checking mode introduces a number of additional constraints: + /// - Git dependencies are not supported. + /// - Editable installs are not supported. + /// - Local dependencies are not supported, unless they point to a specific wheel (`.whl`) or + /// source archive (`.zip`, `.tar.gz`), as opposed to a directory. + #[clap(long, hide = true)] + require_hashes: bool, + /// Attempt to use `keyring` for authentication for index urls /// /// Due to not having Python imports, only `--keyring-provider subprocess` argument is currently @@ -1650,6 +1678,7 @@ async fn run() -> Result { &reinstall, args.link_mode, args.compile, + args.require_hashes, index_urls, args.index_strategy, args.keyring_provider, @@ -1750,6 +1779,7 @@ async fn run() -> Result { reinstall, args.link_mode, args.compile, + args.require_hashes, setup_py, if args.offline { Connectivity::Offline