From b6ffddd018883e5a7ea5f1aa07250ebeffb96338 Mon Sep 17 00:00:00 2001 From: Ofek Lev Date: Sun, 12 May 2024 00:43:32 -0400 Subject: [PATCH] Add `cache` management command (#122) --- Cargo.toml | 1 + build.rs | 48 +++++++++++++++++++---------- docs/changelog.md | 9 ++++++ docs/runtime.md | 8 +++++ scripts/validate_options.py | 9 +++++- src/commands/self_cmd/cache/cli.rs | 27 ++++++++++++++++ src/commands/self_cmd/cache/dist.rs | 35 +++++++++++++++++++++ src/commands/self_cmd/cache/mod.rs | 4 +++ src/commands/self_cmd/cache/pip.rs | 39 +++++++++++++++++++++++ src/commands/self_cmd/cache/uv.rs | 39 +++++++++++++++++++++++ src/commands/self_cmd/cli.rs | 4 ++- src/commands/self_cmd/mod.rs | 1 + src/commands/self_cmd/pip.rs | 1 + src/distribution.rs | 2 +- 14 files changed, 208 insertions(+), 19 deletions(-) create mode 100644 src/commands/self_cmd/cache/cli.rs create mode 100644 src/commands/self_cmd/cache/dist.rs create mode 100644 src/commands/self_cmd/cache/mod.rs create mode 100644 src/commands/self_cmd/cache/pip.rs create mode 100644 src/commands/self_cmd/cache/uv.rs diff --git a/Cargo.toml b/Cargo.toml index 77f20c2..e914397 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ passthrough = [ "PYAPP_EXEC_NOTEBOOK", "PYAPP_EXEC_SCRIPT", "PYAPP_EXEC_SPEC", + "PYAPP_EXPOSE_CACHE", "PYAPP_EXPOSE_METADATA", "PYAPP_EXPOSE_PIP", "PYAPP_EXPOSE_PYTHON", diff --git a/build.rs b/build.rs index 881228c..95eed49 100644 --- a/build.rs +++ b/build.rs @@ -3,7 +3,7 @@ use std::fmt::Display; use std::fs::{self, File}; use std::hash::{Hash, Hasher}; use std::io::Read; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine as _}; use highway::PortableHash; @@ -926,6 +926,23 @@ fn set_self_command() { } } +fn set_exposed_command(path: &Path, command_name: &str, indicator: &Regex) { + if !path.is_file() { + return; + } + + let command_path = path.to_str().unwrap(); + let command_source = fs::read_to_string(command_path).unwrap(); + if indicator.is_match(&command_source) { + let variable = format!("PYAPP_EXPOSE_{}", command_name.to_uppercase()); + if is_enabled(&variable) { + set_runtime_variable(&variable, "1"); + } else { + set_runtime_variable(&variable, "0"); + } + } +} + fn set_exposed_commands() { let indicator = Regex::new(r"(?m)^#\[command\(hide = env!").unwrap(); let commands_dir: PathBuf = [ @@ -937,24 +954,23 @@ fn set_exposed_commands() { .iter() .collect(); - for entry in fs::read_dir(commands_dir).unwrap() { + for entry in fs::read_dir(&commands_dir).unwrap() { let entry = entry.unwrap(); let path = entry.path(); - if !path.is_file() { - continue; - } + set_exposed_command( + &path, + path.file_stem().unwrap().to_str().unwrap(), + &indicator, + ); + } - let command_name = path.file_stem().unwrap().to_str().unwrap(); - let command_path = path.to_str().unwrap(); - let command_source = fs::read_to_string(command_path).unwrap(); - if indicator.is_match(&command_source) { - let variable = format!("PYAPP_EXPOSE_{}", command_name.to_uppercase()); - if is_enabled(&variable) { - set_runtime_variable(&variable, "1"); - } else { - set_runtime_variable(&variable, "0"); - } - } + let command_groups = ["cache"]; + for command_group in command_groups { + set_exposed_command( + &commands_dir.join(command_group).join("cli.rs"), + command_group, + &indicator, + ); } } diff --git a/docs/changelog.md b/docs/changelog.md index 14611e4..8b6fded 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -8,6 +8,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## Unreleased +***Added:*** + +- Add `cache` management command + +***Fixed:*** + +- The `pip` management command is now resilient to cache removal +- Management commands now properly support the `-h`/`--help` flag + ## 0.19.0 - 2024-04-24 ***Added:*** diff --git a/docs/runtime.md b/docs/runtime.md index 9d5cdc2..2bfc011 100644 --- a/docs/runtime.md +++ b/docs/runtime.md @@ -116,6 +116,14 @@ This will update the project to the latest available version in the currently us These commands are hidden by default and each can be individually exposed by setting its corresponding `PYAPP_EXPOSE_` option (e.g. `PYAPP_EXPOSE_METADATA`) to `true` or `1`. +#### Cache + +``` + self cache [dist|pip|uv] +``` + +This is the command group for managing the cache. Each subcommand has a `-r`/`--remove` flag to remove the cached asset. Not passing that flag will display the location instead. + #### Metadata ``` diff --git a/scripts/validate_options.py b/scripts/validate_options.py index 0b1d4c6..b8d4a8a 100644 --- a/scripts/validate_options.py +++ b/scripts/validate_options.py @@ -14,11 +14,18 @@ def main(): available_options = set(re.findall(r'"(PYAPP_[^_].+?)"', Path('build.rs').read_text('utf-8'))) available_options -= IGNORED + root_commands = Path('src/commands/self_cmd') expose_option = re.compile(r'^#\[command\(hide = env!\("(PYAPP_EXPOSE_.+?)"\)', re.MULTILINE) - for entry in Path('src/commands/self_cmd').iterdir(): + for entry in root_commands.iterdir(): if entry.is_file() and (match := expose_option.search(entry.read_text('utf-8'))): available_options.add(match.group(1)) + command_groups = ['cache'] + for command_group in command_groups: + path = root_commands / command_group / 'cli.rs' + if match := expose_option.search(path.read_text('utf-8')): + available_options.add(match.group(1)) + expected_options = sorted(available_options) if defined_options != expected_options: left_padding = max(len(option) for option in expected_options) diff --git a/src/commands/self_cmd/cache/cli.rs b/src/commands/self_cmd/cache/cli.rs new file mode 100644 index 0000000..31cf5fe --- /dev/null +++ b/src/commands/self_cmd/cache/cli.rs @@ -0,0 +1,27 @@ +use anyhow::Result; +use clap::{Args, Subcommand}; + +/// Manage the cache +#[derive(Args, Debug)] +#[command(hide = env!("PYAPP_EXPOSE_CACHE") == "0")] +pub struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand, Debug)] +enum Commands { + Dist(super::dist::Cli), + Pip(super::pip::Cli), + Uv(super::uv::Cli), +} + +impl Cli { + pub fn exec(self) -> Result<()> { + match self.command { + Commands::Dist(cli) => cli.exec(), + Commands::Pip(cli) => cli.exec(), + Commands::Uv(cli) => cli.exec(), + } + } +} diff --git a/src/commands/self_cmd/cache/dist.rs b/src/commands/self_cmd/cache/dist.rs new file mode 100644 index 0000000..a66a636 --- /dev/null +++ b/src/commands/self_cmd/cache/dist.rs @@ -0,0 +1,35 @@ +use std::fs; + +use anyhow::Result; +use clap::Args; + +use crate::app; + +/// Manage the distribution cache +#[derive(Args, Debug)] +#[command()] +pub struct Cli { + #[arg(short, long)] + remove: bool, +} + +impl Cli { + pub fn exec(self) -> Result<()> { + let distributions_dir = app::distributions_cache(); + let distribution_file = distributions_dir.join(app::distribution_id()); + if !distribution_file.exists() { + if self.remove { + println!("Does not exist"); + } + return Ok(()); + } + + if self.remove { + fs::remove_file(distribution_file)?; + } else { + println!("{}", distribution_file.display()); + } + + Ok(()) + } +} diff --git a/src/commands/self_cmd/cache/mod.rs b/src/commands/self_cmd/cache/mod.rs new file mode 100644 index 0000000..57313b9 --- /dev/null +++ b/src/commands/self_cmd/cache/mod.rs @@ -0,0 +1,4 @@ +pub mod cli; +pub mod dist; +pub mod pip; +pub mod uv; diff --git a/src/commands/self_cmd/cache/pip.rs b/src/commands/self_cmd/cache/pip.rs new file mode 100644 index 0000000..1335d0a --- /dev/null +++ b/src/commands/self_cmd/cache/pip.rs @@ -0,0 +1,39 @@ +use std::fs; + +use anyhow::Result; +use clap::Args; + +use crate::app; + +/// Manage the external pip cache +#[derive(Args, Debug)] +#[command()] +pub struct Cli { + #[arg(short, long)] + remove: bool, +} + +impl Cli { + pub fn exec(self) -> Result<()> { + if !app::pip_external() { + println!("External pip not enabled"); + return Ok(()); + } + + let external_pip = app::external_pip_zipapp(); + if !external_pip.exists() { + if self.remove { + println!("Does not exist"); + } + return Ok(()); + } + + if self.remove { + fs::remove_file(external_pip)?; + } else { + println!("{}", external_pip.display()); + } + + Ok(()) + } +} diff --git a/src/commands/self_cmd/cache/uv.rs b/src/commands/self_cmd/cache/uv.rs new file mode 100644 index 0000000..cecfac1 --- /dev/null +++ b/src/commands/self_cmd/cache/uv.rs @@ -0,0 +1,39 @@ +use std::fs; + +use anyhow::Result; +use clap::Args; + +use crate::app; + +/// Manage the UV cache +#[derive(Args, Debug)] +#[command()] +pub struct Cli { + #[arg(short, long)] + remove: bool, +} + +impl Cli { + pub fn exec(self) -> Result<()> { + if !app::uv_enabled() { + println!("UV is not enabled"); + return Ok(()); + } + + let managed_uv = app::managed_uv(); + if !managed_uv.exists() { + if self.remove { + println!("Does not exist"); + } + return Ok(()); + } + + if self.remove { + fs::remove_file(managed_uv)?; + } else { + println!("{}", managed_uv.display()); + } + + Ok(()) + } +} diff --git a/src/commands/self_cmd/cli.rs b/src/commands/self_cmd/cli.rs index e62d071..6eb5491 100644 --- a/src/commands/self_cmd/cli.rs +++ b/src/commands/self_cmd/cli.rs @@ -3,7 +3,7 @@ use clap::{Args, Subcommand}; /// Manage this application #[derive(Args, Debug)] -#[command(disable_help_flag = true)] +#[command()] pub struct Cli { #[command(subcommand)] command: Commands, @@ -11,6 +11,7 @@ pub struct Cli { #[derive(Subcommand, Debug)] enum Commands { + Cache(super::cache::cli::Cli), Metadata(super::metadata::Cli), Pip(super::pip::Cli), Python(super::python::Cli), @@ -23,6 +24,7 @@ enum Commands { impl Cli { pub fn exec(self) -> Result<()> { match self.command { + Commands::Cache(cli) => cli.exec(), Commands::Metadata(cli) => cli.exec(), Commands::Pip(cli) => cli.exec(), Commands::Python(cli) => cli.exec(), diff --git a/src/commands/self_cmd/mod.rs b/src/commands/self_cmd/mod.rs index e861a31..6e0313e 100644 --- a/src/commands/self_cmd/mod.rs +++ b/src/commands/self_cmd/mod.rs @@ -1,3 +1,4 @@ +pub mod cache; pub mod cli; pub mod metadata; pub mod pip; diff --git a/src/commands/self_cmd/pip.rs b/src/commands/self_cmd/pip.rs index 7ee55a1..fb456f4 100644 --- a/src/commands/self_cmd/pip.rs +++ b/src/commands/self_cmd/pip.rs @@ -16,6 +16,7 @@ pub struct Cli { impl Cli { pub fn exec(self) -> Result<()> { distribution::ensure_ready()?; + distribution::ensure_installer_available()?; let mut command = distribution::pip_base_command(); command.args(self.args); diff --git a/src/distribution.rs b/src/distribution.rs index e4d6fe4..9af5335 100644 --- a/src/distribution.rs +++ b/src/distribution.rs @@ -410,7 +410,7 @@ fn ensure_base_pip(distribution_directory: &Path) -> Result<()> { Ok(()) } -fn ensure_installer_available() -> Result<()> { +pub fn ensure_installer_available() -> Result<()> { if app::uv_as_installer() { ensure_uv_available()?; } else if app::pip_external() {