diff --git a/docs/dev-tools/backends/pipx.md b/docs/dev-tools/backends/pipx.md index a06d767043..e33cbc2df8 100644 --- a/docs/dev-tools/backends/pipx.md +++ b/docs/dev-tools/backends/pipx.md @@ -27,6 +27,11 @@ This relies on having `uv` (recommended) or `pipx` installed. If you have `uv` installed, mise will use `uv tool install` under the hood and you don't need to install `pipx` to run the commands containing "pipx:". +When [`install_before`](/configuration/settings.html#install_before) is set and mise installs a +`pipx:` tool through uv, mise passes uv's `--exclude-newer` flag so transitive Python dependencies +are resolved only from releases uploaded before that timestamp. This only applies to the uv install +path; the `pipx` fallback does not receive a transitive release-age cutoff. + In case you need `pipx` for other reasons, you can install it with or without mise. Here is how to install `pipx` with mise: diff --git a/docs/dev-tools/mise-lock.md b/docs/dev-tools/mise-lock.md index 5d272c2ec5..47cd2316a9 100644 --- a/docs/dev-tools/mise-lock.md +++ b/docs/dev-tools/mise-lock.md @@ -360,6 +360,10 @@ install_before = "7d" # only resolve to versions released more than 7 days ago This pairs well with lockfiles — use `install_before` to avoid picking up brand-new releases, and lockfiles to pin the exact versions you've vetted. +Some package-manager backends also forward this cutoff into transitive dependency resolution during +install. This includes `npm:` tools and `pipx:` tools installed through uv. The `pipx` fallback path +does not receive a transitive release-age cutoff. + ## See Also - [Configuration Settings](/configuration/settings) - All available settings diff --git a/docs/tips-and-tricks.md b/docs/tips-and-tricks.md index 6635e3f22c..ebe491f263 100644 --- a/docs/tips-and-tricks.md +++ b/docs/tips-and-tricks.md @@ -180,9 +180,10 @@ install_before = "7d" # only install versions released more than 7 days ago Supports relative durations (`7d`, `6m`, `1y`) and absolute dates (`2024-06-01`). For most backends, this only affects fuzzy version resolution (e.g., `node@20` or `latest`) — explicitly pinned versions like `node@22.5.0` bypass the filter. -For `npm:` tools, the same cutoff is also forwarded to transitive dependency resolution during -install. Refer to the [npm backend docs](/dev-tools/backends/npm.html) for package-manager support -details. +For `npm:` tools and `pipx:` tools installed through uv, the same cutoff is also forwarded to +transitive dependency resolution during install. Refer to the +[npm backend docs](/dev-tools/backends/npm.html) and [pipx backend docs](/dev-tools/backends/pipx.html) +for package-manager support details. You can also set `install_before` per-tool to override the global setting: diff --git a/settings.toml b/settings.toml index 4b27381a50..67f8228821 100644 --- a/settings.toml +++ b/settings.toml @@ -1037,6 +1037,10 @@ like `node@20` or `latest`. Explicitly pinned versions like `node@22.5.0` are no allowing you to selectively use newer versions for specific tools while keeping others behind the cutoff date. +Some package-manager backends also forward the cutoff to transitive dependency resolution during +install. This includes `npm:` tools and `pipx:` tools installed through uv. The `pipx` fallback path +does not receive a transitive release-age cutoff. + Can be overridden with the `--before` CLI flag or per-tool with the `install_before` tool option. ```toml diff --git a/src/backend/pipx.rs b/src/backend/pipx.rs index d8b0766b8d..b5e5191003 100644 --- a/src/backend/pipx.rs +++ b/src/backend/pipx.rs @@ -18,8 +18,10 @@ use async_trait::async_trait; use eyre::{Result, eyre}; use indexmap::IndexMap; use itertools::Itertools; +use jiff::Timestamp; use regex::Regex; use std::collections::BTreeMap; +use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{fmt::Debug, sync::Arc}; @@ -229,6 +231,7 @@ impl Backend for PIPXBackend { ctx.pr.as_ref(), ) .await?; + cmd = cmd.args(Self::uv_exclude_newer_args(ctx.before_date)); if let Some(args) = tv.request.options().get("uvx_args") { cmd = cmd.args(shell_words::split(args)?); } @@ -288,6 +291,13 @@ pub fn install_time_option_keys() -> Vec { } impl PIPXBackend { + fn uv_exclude_newer_args(before_date: Option) -> Vec { + match before_date { + Some(before_date) => vec!["--exclude-newer".into(), before_date.to_string().into()], + None => vec![], + } + } + pub fn from_arg(ba: BackendArg) -> Self { Self { latest_version_cache: CacheManagerBuilder::new( @@ -640,3 +650,32 @@ fn fix_venv_python_symlink(install_path: &Path, pkg_name: &str) -> Result<()> { fn fix_venv_python_symlink(_install_path: &Path, _pkg_name: &str) -> Result<()> { Ok(()) } + +#[cfg(test)] +mod tests { + use super::PIPXBackend; + use pretty_assertions::assert_eq; + use std::ffi::OsString; + + #[test] + fn test_uv_exclude_newer_args_with_cutoff() { + let before_date = "2024-01-02T03:04:05Z".parse().unwrap(); + let args = PIPXBackend::uv_exclude_newer_args(Some(before_date)); + + assert_eq!( + args, + vec![ + OsString::from("--exclude-newer"), + OsString::from("2024-01-02T03:04:05Z"), + ] + ); + } + + #[test] + fn test_uv_exclude_newer_args_without_cutoff() { + assert_eq!( + PIPXBackend::uv_exclude_newer_args(None), + Vec::::new() + ); + } +}