From b386590b3c22a69b25b2936e4350e4903693a5d4 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 11 Feb 2024 22:19:55 -0500 Subject: [PATCH] Add some compatibility arguments to `puffin venv` (#1282) See: https://github.com/astral-sh/puffin/issues/1276. --- crates/puffin/src/compat/mod.rs | 73 +++++++++++++++++++++++++++++++-- crates/puffin/src/main.rs | 6 +++ crates/puffin/tests/venv.rs | 44 ++++++++++++++++++++ 3 files changed, 119 insertions(+), 4 deletions(-) diff --git a/crates/puffin/src/compat/mod.rs b/crates/puffin/src/compat/mod.rs index ea5101918..60ace94dc 100644 --- a/crates/puffin/src/compat/mod.rs +++ b/crates/puffin/src/compat/mod.rs @@ -3,6 +3,10 @@ use clap::{Args, ValueEnum}; use puffin_warnings::warn_user; +pub(crate) trait CompatArgs { + fn validate(&self) -> Result<()>; +} + /// Arguments for `pip-compile` compatibility. /// /// These represent a subset of the `pip-compile` interface that Puffin supports by default. @@ -84,13 +88,13 @@ pub(crate) struct PipCompileCompatArgs { pip_args: Option, } -impl PipCompileCompatArgs { +impl CompatArgs for PipCompileCompatArgs { /// Validate the arguments passed for `pip-compile` compatibility. /// /// This method will warn when an argument is passed that has no effect but matches Puffin's /// behavior. If an argument is passed that does _not_ match Puffin's behavior (e.g., /// `--no-build-isolation`), this method will return an error. - pub(crate) fn validate(&self) -> Result<()> { + fn validate(&self) -> Result<()> { if self.allow_unsafe { warn_user!( "pip-compile's `--allow-unsafe` has no effect (Puffin can safely pin `pip` and other packages)." @@ -281,13 +285,13 @@ pub(crate) struct PipSyncCompatArgs { pip_args: Option, } -impl PipSyncCompatArgs { +impl CompatArgs for PipSyncCompatArgs { /// Validate the arguments passed for `pip-sync` compatibility. /// /// This method will warn when an argument is passed that has no effect but matches Puffin's /// behavior. If an argument is passed that does _not_ match Puffin's behavior, this method will /// return an error. - pub(crate) fn validate(&self) -> Result<()> { + fn validate(&self) -> Result<()> { if self.ask { return Err(anyhow!( "pip-sync's `--ask` is unsupported (Puffin never asks for confirmation)." @@ -349,3 +353,64 @@ enum AnnotationStyle { Line, Split, } + +/// Arguments for `venv` compatibility. +/// +/// These represent a subset of the `virtualenv` interface that Puffin supports by default. +#[derive(Args)] +#[allow(clippy::struct_excessive_bools)] +pub(crate) struct VenvCompatArgs { + #[clap(long, hide = true)] + clear: bool, + + #[clap(long, hide = true)] + no_seed: bool, + + #[clap(long, hide = true)] + no_pip: bool, + + #[clap(long, hide = true)] + no_setuptools: bool, + + #[clap(long, hide = true)] + no_wheel: bool, +} + +impl CompatArgs for VenvCompatArgs { + /// Validate the arguments passed for `venv` compatibility. + /// + /// This method will warn when an argument is passed that has no effect but matches Puffin's + /// behavior. If an argument is passed that does _not_ match Puffin's behavior, this method will + /// return an error. + fn validate(&self) -> Result<()> { + if self.clear { + warn_user!( + "virtualenv's `--clear` has no effect (Puffin always clears the virtual environment)." + ); + } + + if self.no_seed { + warn_user!( + "virtualenv's `--no-seed` has no effect (Puffin omits seed packages by default)." + ); + } + + if self.no_pip { + warn_user!("virtualenv's `--no-pip` has no effect (Puffin omits `pip` by default)."); + } + + if self.no_setuptools { + warn_user!( + "virtualenv's `--no-setuptools` has no effect (Puffin omits `setuptools` by default)." + ); + } + + if self.no_wheel { + warn_user!( + "virtualenv's `--no-wheel` has no effect (Puffin omits `wheel` by default)." + ); + } + + Ok(()) + } +} diff --git a/crates/puffin/src/main.rs b/crates/puffin/src/main.rs index 040e92d5d..4d1bff46c 100644 --- a/crates/puffin/src/main.rs +++ b/crates/puffin/src/main.rs @@ -21,6 +21,7 @@ use puffin_traits::{NoBuild, PackageNameSpecifier, SetupPyStrategy}; use requirements::ExtrasSpecification; use crate::commands::{extra_name_with_clap_error, ExitStatus, Upgrade}; +use crate::compat::CompatArgs; use crate::requirements::RequirementsSource; #[cfg(target_os = "windows")] @@ -625,6 +626,9 @@ struct VenvArgs { /// format (e.g., `2006-12-02`). #[arg(long, value_parser = date_or_datetime, hide = true)] exclude_newer: Option>, + + #[command(flatten)] + compat_args: compat::VenvCompatArgs, } #[derive(Args)] @@ -928,6 +932,8 @@ async fn run() -> Result { }) => commands::freeze(&cache, args.strict, printer), Commands::Clean(args) => commands::clean(&cache, &args.package, printer), Commands::Venv(args) => { + args.compat_args.validate()?; + let index_locations = IndexLocations::from_args( args.index_url, args.extra_index_url, diff --git a/crates/puffin/tests/venv.rs b/crates/puffin/tests/venv.rs index 1d38f3440..77861adcc 100644 --- a/crates/puffin/tests/venv.rs +++ b/crates/puffin/tests/venv.rs @@ -443,3 +443,47 @@ fn non_empty_dir_exists() -> Result<()> { Ok(()) } + +#[test] +fn virtualenv_compatibility() -> Result<()> { + let temp_dir = assert_fs::TempDir::new()?; + let cache_dir = assert_fs::TempDir::new()?; + let bin = create_bin_with_executables(&temp_dir, &["3.12"]).expect("Failed to create bin dir"); + let venv = temp_dir.child(".venv"); + + // Create a virtual environment at `.venv`, passing the redundant `--clear` flag. + let filter_venv = regex::escape(&venv.normalized_display().to_string()); + let filters = &[ + ( + r"Using Python 3\.\d+\.\d+ interpreter at .+", + "Using Python [VERSION] interpreter at [PATH]", + ), + (&filter_venv, "/home/ferris/project/.venv"), + ]; + puffin_snapshot!(filters, Command::new(get_bin()) + .arg("venv") + .arg(venv.as_os_str()) + .arg("--clear") + .arg("--python") + .arg("3.12") + .arg("--cache-dir") + .arg(cache_dir.path()) + .arg("--exclude-newer") + .arg(EXCLUDE_NEWER) + .env("PUFFIN_TEST_PYTHON_PATH", bin.clone()) + .current_dir(&temp_dir), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: virtualenv's `--clear` has no effect (Puffin always clears the virtual environment). + Using Python [VERSION] interpreter at [PATH] + Creating virtualenv at: /home/ferris/project/.venv + "### + ); + + venv.assert(predicates::path::is_dir()); + + Ok(()) +}