diff --git a/README.md b/README.md index 57b9c4e..fb5c9f8 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,7 @@ disabled_backends: [] # backends that pacdef should not manage, e.g. ["python"] warn_not_symlinks: true # warn if a group file is not a symlink flatpak_systemwide: true # whether flatpak packages should be installed system-wide or per user +pip_binary: pip # choose whether to use pipx instead of pip for python package management [See [Pitfalls while using pipx](#pitfalls-while-using-pipx)] ``` @@ -219,3 +220,6 @@ Pacdef is supported by [topgrade](https://github.com/topgrade-rs/topgrade). MSRV is 1.70.0 due to dependencies that require this specific version. Development is conducted against the latest stable version. +### Pitfalls while using pipx + +Some packages like [mdformat-myst](https://github.com/executablebooks/mdformat-myst) do not provide an executable themselves but rather act as a plugin to their dependency, which is mdformat in this case. Please install such packages explicitly by running `pipx install --include-deps`. diff --git a/crates/pacdef_core/src/backend/actual/python.rs b/crates/pacdef_core/src/backend/actual/python.rs index 8ba3438..eecd199 100644 --- a/crates/pacdef_core/src/backend/actual/python.rs +++ b/crates/pacdef_core/src/backend/actual/python.rs @@ -12,6 +12,7 @@ use crate::{Group, Package}; #[derive(Debug, Clone)] pub struct Python { + pub(crate) binary: String, pub(crate) packages: HashSet, } @@ -26,19 +27,30 @@ const SWITCHES_REMOVE: Switches = &["uninstall"]; const SUPPORTS_AS_DEPENDENCY: bool = false; +macro_rules! ERROR{ + ($bin:expr) => { + panic!("Cannot use {} for package management in python. Please use a valid package manager like pip or pipx.", $bin) + }; +} + impl Backend for Python { impl_backend_constants!(); - fn get_all_installed_packages(&self) -> Result> { - let output = run_pip_command(&["list", "--format", "json", "--user"])?; + fn get_binary(&self) -> Text { + let r#box = self.binary.clone().into_boxed_str(); + Box::leak(r#box) + } - extract_pacdef_packages(output) + fn get_all_installed_packages(&self) -> Result> { + let mut cmd = Command::new(self.get_binary()); + let output = run_pip_command(&mut cmd, self.get_switches_runtime())?; + self.extract_packages(output) } fn get_explicitly_installed_packages(&self) -> Result> { - let output = run_pip_command(&["list", "--format", "json", "--not-required", "--user"])?; - - extract_pacdef_packages(output) + let mut cmd = Command::new(self.get_binary()); + let output = run_pip_command(&mut cmd, self.get_switches_explicit())?; + self.extract_packages(output) } fn make_dependency(&self, _packages: &[Package]) -> Result { @@ -46,8 +58,7 @@ impl Backend for Python { } } -fn run_pip_command(args: &[&str]) -> Result { - let mut cmd = Command::new(BINARY); +fn run_pip_command(cmd: &mut Command, args: &[&str]) -> Result { cmd.args(args); let output = String::from_utf8(cmd.output()?.stdout)?; let val: Value = serde_json::from_str(&output)?; @@ -57,9 +68,33 @@ fn run_pip_command(args: &[&str]) -> Result { impl Python { pub(crate) fn new() -> Self { Self { + binary: BINARY.to_string(), packages: HashSet::new(), } } + + fn get_switches_runtime(&self) -> Switches { + match self.get_binary() { + "pip" => &["list", "--format", "json", "--not-required", "--user"], + "pipx" => &["list", "--json"], + _ => ERROR!(self.get_binary()), + } + } + fn get_switches_explicit(&self) -> Switches { + match self.get_binary() { + "pip" => &["list", "--format", "json", "--user"], + "pipx" => &["list", "--json"], + _ => ERROR!(self.get_binary()), + } + } + + fn extract_packages(&self, output: Value) -> Result> { + match self.get_binary() { + "pip" => extract_pacdef_packages(output), + "pipx" => extract_pacdef_packages_pipx(output), + _ => ERROR!(self.get_binary()), + } + } } fn extract_pacdef_packages(value: Value) -> Result> { @@ -72,3 +107,13 @@ fn extract_pacdef_packages(value: Value) -> Result> { .collect(); Ok(result) } + +fn extract_pacdef_packages_pipx(value: Value) -> Result> { + let result = value["venvs"] + .as_object() + .context("getting inner json object")? + .iter() + .map(|(name, _)| Package::from(name.as_str())) + .collect(); + Ok(result) +} diff --git a/crates/pacdef_core/src/config.rs b/crates/pacdef_core/src/config.rs index dbbec27..dbcafc1 100644 --- a/crates/pacdef_core/src/config.rs +++ b/crates/pacdef_core/src/config.rs @@ -24,6 +24,9 @@ pub struct Config { /// Backends the user does not want to use even though the binary exists. #[serde(default)] pub disabled_backends: Vec, + /// Choose whether to use pipx instead of pip for python package management + #[serde(default)] + pub pip_binary: String, } fn yes() -> bool { @@ -86,6 +89,7 @@ impl Default for Config { flatpak_systemwide: true, warn_not_symlinks: true, disabled_backends: vec![], + pip_binary: "pip".into(), } } } diff --git a/crates/pacdef_core/src/core.rs b/crates/pacdef_core/src/core.rs index ccf2839..b79b6e8 100644 --- a/crates/pacdef_core/src/core.rs +++ b/crates/pacdef_core/src/core.rs @@ -140,6 +140,13 @@ impl Pacdef { { flatpak.systemwide = self.config.flatpak_systemwide; } + + if let Some(python) = backend + .as_any_mut() + .downcast_mut::() + { + python.binary = self.config.pip_binary.clone(); + } } fn install_packages(&mut self, noconfirm: bool) -> Result<()> {