Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pm): add support for pkcon #538

Merged
merged 3 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ jobs:
toolchain: stable
override: true
- uses: Swatinem/rust-cache@v2
- run: apk add -U build-base
- run: apk add -U build-base tar
- name: Build
run: cargo build --verbose
- name: Run unit tests
Expand All @@ -313,7 +313,7 @@ jobs:
- name: Run heavy tests
run: cargo test apk --verbose -- --ignored

pip-conda-test:
pkcon-pip-conda-test:
runs-on: ubuntu-latest
needs: skip-check
if: ${{ needs.skip-check.outputs.should_skip != 'true' }}
Expand All @@ -325,15 +325,18 @@ jobs:
toolchain: stable
override: true
- uses: Swatinem/rust-cache@v2
- run: |
sudo apt update
sudo apt install -y packagekit packagekit-tools
- name: Build
run: cargo build --verbose
# - name: Run unit tests
# run: cargo test tests --verbose
- name: Run smoke tests
run: |
cargo test pkcon --verbose
cargo test pip --verbose
cargo test conda --verbose
- name: Run heavy tests
run: |
cargo test pkcon --verbose -- --ignored
cargo test pip --verbose -- --ignored
cargo test conda --verbose -- --ignored
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"olegtarasov",
"pacaptr",
"phinx",
"pkcon",
"Pkgng",
"pkgver",
"printf",
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ That's why I decided to take inspiration from the existing `sh`-based [icy/pacap
- Windows: `scoop`, [`choco`](#choco), `winget`
- macOS: [`brew`](#brew), `port`, `apt` (through [Procursus])
- Linux: `apt`, `apk`, `dnf`, `emerge`, `xbps`, `zypper`
- External: `brew`, `conda`, `pip`/`pip3`, `tlmgr`
- External: `brew`, `conda`, `pip`/`pip3`, `pkcon`, `tlmgr`
- These are only available with the [`pacaptr --using <name>`](#--using---pm) syntax.

As for now, the precedence is still (unfortunately) hard-coded. For example, if both `scoop` and `choco` are installed, `scoop` will be the default. You can however edit the default package manager in your [config](#configuration).
Expand Down
7 changes: 5 additions & 2 deletions src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ fn detect_pm_str<'s>() -> &'s str {
_ if cfg!(target_os = "linux") => &[
("apk", "/sbin/apk"),
("apt", "/usr/bin/apt"),
("emerge", "/usr/bin/emerge"),
("dnf", "/usr/bin/dnf"),
("emerge", "/usr/bin/emerge"),
("xbps-install", "/usr/bin/xbps-install"),
("zypper", "/usr/bin/zypper"),
],
Expand All @@ -56,7 +56,7 @@ impl From<Config> for BoxPm<'_> {
/// current `Config`.
fn from(mut cfg: Config) -> Self {
use crate::pm::{
Apk, Apt, Brew, Choco, Conda, Dnf, Emerge, Pip, Pm, Port, Scoop, Tlmgr, Unknown,
Apk, Apt, Brew, Choco, Conda, Dnf, Emerge, Pip, Pkcon, Pm, Port, Scoop, Tlmgr, Unknown,
Winget, Xbps, Zypper,
};

Expand Down Expand Up @@ -107,6 +107,9 @@ impl From<Config> for BoxPm<'_> {
// Pip
"pip" | "pip3" => Pip::new(cfg).boxed(),

// PackageKit
"pkcon" => Pkcon::new(cfg).boxed(),

// Tlmgr
"tlmgr" => Tlmgr::new(cfg).boxed(),

Expand Down
3 changes: 2 additions & 1 deletion src/pm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pm_mods! {
dnf;
emerge;
pip;
pkcon;
port;
scoop;
tlmgr;
Expand Down Expand Up @@ -128,7 +129,7 @@ macro_rules! methods {
/// Sii displays packages which require X to be installed, aka reverse dependencies.
async fn sii;

/// Sl displays a list of all packages in all installation sources that are handled by the packages management.
/// Sl displays a list of all packages in all installation sources that are handled by the package management.
async fn sl;

/// Ss searches for package(s) by searching the expression in name, description, short description.
Expand Down
2 changes: 1 addition & 1 deletion src/pm/apk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ impl Pm for Apk {
}

/// Sl displays a list of all packages in all installation sources that are
/// handled by the packages management.
/// handled by the package management.
async fn sl(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
self.run(Cmd::new(["apk", "search"]).kws(kws).flags(flags))
.await
Expand Down
10 changes: 5 additions & 5 deletions src/pm/dnf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,12 @@ impl Pm for Dnf {

/// Qi displays local package information: name, version, description, etc.
async fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
stream::iter(&[
&["dnf", "info", "--installed"],
&["dnf", "repoquery", "--deplist"],
stream::iter([
["dnf", "info", "--installed"],
["dnf", "repoquery", "--deplist"],
])
.map(Ok)
.try_for_each(|&cmd| self.run(Cmd::new(cmd).kws(kws).flags(flags)))
.try_for_each(|cmd| self.run(Cmd::new(cmd).kws(kws).flags(flags)))
.await
}

Expand Down Expand Up @@ -219,7 +219,7 @@ impl Pm for Dnf {
}

/// Sl displays a list of all packages in all installation sources that are
/// handled by the packages management.
/// handled by the package management.
async fn sl(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
Cmd::new(["dnf", "list", "--available"])
.kws(kws)
Expand Down
210 changes: 210 additions & 0 deletions src/pm/pkcon.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
#![doc = docs_self!()]

use async_trait::async_trait;
use futures::prelude::*;
use indoc::indoc;
use once_cell::sync::Lazy;
use tap::prelude::*;

use super::{Pm, PmHelper, PmMode, PromptStrategy, Strategy};
use crate::{dispatch::Config, error::Result, exec::Cmd};

macro_rules! docs_self {
() => {
indoc! {"
The [PackageKit Console Client](https://www.freedesktop.org/software/PackageKit).
"}
};
}

#[doc = docs_self!()]
#[derive(Debug)]
pub struct Pkcon {
cfg: Config,
}

static STRAT_PROMPT: Lazy<Strategy> = Lazy::new(|| Strategy {
prompt: PromptStrategy::native_no_confirm(["-y"]),
..Strategy::default()
});

impl Pkcon {
#[must_use]
#[allow(missing_docs)]
pub const fn new(cfg: Config) -> Self {
Self { cfg }
}
}

#[async_trait]
impl Pm for Pkcon {
/// Gets the name of the package manager.
fn name(&self) -> &str {
"pkcon"
}

fn cfg(&self) -> &Config {
&self.cfg
}

/// Q generates a list of installed packages.
async fn q(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
if kws.is_empty() {
Cmd::new(["pkcon", "get-packages", "--filter", "installed"])
.kws(kws)
.flags(flags)
.pipe(|cmd| self.run(cmd))
.await
} else {
self.qs(kws, flags).await
}
}

/// Qc shows the changelog of a package.
async fn qc(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
Cmd::new(["pkcon", "get-update-detail"])
.kws(kws)
.flags(flags)
.pipe(|cmd| self.run(cmd))
.await
}

/// Qi displays local package information: name, version, description, etc.
async fn qi(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
self.si(kws, flags).await
}

/// Qii displays local packages which require X to be installed, aka local
/// reverse dependencies.
async fn qii(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
self.sii(kws, flags).await
}

/// Ql displays files provided by local package.
async fn ql(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
self.run(Cmd::new(["pkcon", "get-files"]).kws(kws).flags(flags))
.await
}

/// Qo queries the package which provides FILE.
async fn qo(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
self.run(Cmd::new(["pkcon", "what-provides"]).kws(kws).flags(flags))
.await
}

/// Qs searches locally installed package for names or descriptions.
// According to https://www.archlinux.org/pacman/pacman.8.html#_query_options_apply_to_em_q_em_a_id_qo_a,
// when including multiple search terms, only packages with descriptions
// matching ALL of those terms are returned.
async fn qs(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
Cmd::new(["pkcon", "get-packages", "--filter", "installed"])
.flags(flags)
.pipe(|cmd| self.search_regex(cmd, kws))
.await
}

/// Qu lists packages which have an update available.
async fn qu(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
Cmd::with_sudo(["pkcon", "get-updates"])
.kws(kws)
.flags(flags)
.pipe(|cmd| self.run(cmd))
.await
}

/// R removes a single package, leaving all of its dependencies installed.
async fn r(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
stream::iter(kws)
.map(Ok)
.try_for_each(|kw| {
Cmd::with_sudo(["pkcon", "remove"])
.kws([kw])
.flags(flags)
.pipe(|cmd| self.run_with(cmd, PmMode::default(), &STRAT_PROMPT))
})
.await
}

/// Rs removes a package and its dependencies which are not required by any
/// other installed package, and not explicitly installed by the user.
async fn rs(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
stream::iter(kws)
.map(Ok)
.try_for_each(|kw| {
Cmd::with_sudo(["pkcon", "remove", "--autoremove"])
.kws([kw])
.flags(flags)
.pipe(|cmd| self.run_with(cmd, PmMode::default(), &STRAT_PROMPT))
})
.await
}

/// S installs one or more packages by name.
async fn s(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
Cmd::with_sudo(if self.cfg.needed {
&["pkcon", "install"][..]
} else {
&["pkcon", "install", "--allow-reinstall"][..]
})
.kws(kws)
.flags(flags)
.pipe(|cmd| self.run_with(cmd, PmMode::default(), &STRAT_PROMPT))
.await
}

/// Si displays remote package information: name, version, description, etc.
async fn si(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
self.run(Cmd::new(["pkcon", "get-details"]).kws(kws).flags(flags))
.await
}

/// Sii displays packages which require X to be installed, aka reverse
/// dependencies.
async fn sii(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
self.run(Cmd::new(["pkcon", "required-by"]).kws(kws).flags(flags))
.await
}

/// Ss searches for package(s) by searching the expression in name,
/// description, short description.
async fn ss(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
self.run(Cmd::new(["pkcon", "search", "name"]).kws(kws).flags(flags))
.await
}

/// Su updates outdated packages.
async fn su(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
Cmd::with_sudo(["pkcon", "update"])
.kws(kws)
.flags(flags)
.pipe(|cmd| self.run_with(cmd, PmMode::default(), &STRAT_PROMPT))
.await
}

/// Suy refreshes the local package database, then updates outdated
/// packages.
async fn suy(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
self.sy(&[], flags).await?;
self.su(kws, flags).await
}

/// Sw retrieves all packages from the server, but does not install/upgrade
/// anything.
async fn sw(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
Cmd::with_sudo(["pkcon", "install", "--only-download"])
.kws(kws)
.flags(flags)
.pipe(|cmd| self.run_with(cmd, PmMode::default(), &STRAT_PROMPT))
.await
}

/// Sy refreshes the local package database.
async fn sy(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
self.run(Cmd::with_sudo(["pkcon", "refresh"]).flags(flags))
.await?;
if !kws.is_empty() {
self.s(kws, flags).await?;
}
Ok(())
}
}
2 changes: 1 addition & 1 deletion src/pm/tlmgr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ impl Pm for Tlmgr {
}

/// Sl displays a list of all packages in all installation sources that are
/// handled by the packages management.
/// handled by the package management.
async fn sl(&self, _kws: &[&str], flags: &[&str]) -> Result<()> {
self.run(Cmd::new(["tlmgr", "info"]).flags(flags)).await
}
Expand Down
2 changes: 1 addition & 1 deletion src/pm/zypper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ impl Pm for Zypper {
}

/// Sl displays a list of all packages in all installation sources that are
/// handled by the packages management.
/// handled by the package management.
async fn sl(&self, kws: &[&str], flags: &[&str]) -> Result<()> {
let cmd = &["zypper", "packages", "-R"];
if kws.is_empty() {
Expand Down
Loading