Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ htmlescape = "0.3.1"
indicatif = "0.18.3"
scraper = "0.25.0"
nix = { version = "0.30.1", features = ["fs", "user"] }
reqwest = { version = "0.11.27", features = ["gzip", "socks"] }
reqwest = { version = "0.11.27", features = ["blocking", "gzip", "socks"] }
rss = { version = "2.0.12", default-features = false }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
Expand Down
12 changes: 9 additions & 3 deletions man/paru.conf.5
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ paru.conf \- paru configuration file
$PARU_CONF, $XDG_CONFIG_HOME/paru/paru.conf, $HOME/.config/paru/paru.conf, /etc/paru.conf

.SH DESCRIPTION
Paru's config file. Based on the format used by
Paru's config file. Based on the format used by
.BR pacman.conf (5)

Paru first attempts to read the file at $PARU_CONF. If $PARU_CONF is not
Expand Down Expand Up @@ -178,7 +178,7 @@ during builds allowing an option to be chosen then.
.TP
.B UpgradeMenu
Show a detailed list of updates in a similar format to pacman's VerbosePkgLists
option. (See
option. (See
.BR pacman.conf(5)).
Upgrades can be skipped using numbers, number ranges, or repo
names.
Expand Down Expand Up @@ -208,7 +208,7 @@ visible here: https://aur.archlinux.org/packages/

.TP
.B SearchBy = <name|name-desc|maintainer|depends|checkdepends|makedepends|optdepends>
Defaults to name-desc. Search AUR packages according to the options in
Defaults to name-desc. Search AUR packages according to the options in
"Search by" visible here: https://aur.archlinux.org/packages/

.TP
Expand Down Expand Up @@ -466,6 +466,12 @@ MakepkgConf option for this package only.
Set environment variables for building this package. Values may optionally be
quoted with double quotes. Multiple key-value pairs are separated by commas.

.TP
.B PkgbuildPatches = { https://url-patch.patch, /local/patch.patch }
List patches for the PKGBUILD to apply to the PKGBUILD before building this
package. You may for example add patches to the source of PKGBUILD by adding them
locally then generating a PKGBUILD patch.

.SS GROUP OVERRIDES

A group override section applies the same settings to multiple packages:
Expand Down
161 changes: 139 additions & 22 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::exec::{self, Status};
use crate::fmt::color_repo;
use crate::info::get_terminal_width;
use crate::pkgbuild::PkgbuildRepos;
use crate::util::{get_provider, reopen_stdin};
use crate::util::{get_provider, reopen_stdin, split_by_comma};
use crate::{alpm_debug_enabled, help, printtr, repo};

use std::collections::HashMap;
Expand All @@ -14,6 +14,7 @@ use std::fmt;
use std::fs::{remove_file, OpenOptions};
use std::io::{stderr, stdin, stdout, BufRead, IsTerminal};
use std::path::{Path, PathBuf};
use std::process::Command;
use std::str::FromStr;

use alpm::{
Expand Down Expand Up @@ -381,10 +382,88 @@ impl ConfigEnum for YesNoAllTree {
];
}

#[derive(Debug, Clone)]
pub enum PatchSource {
Path(PathBuf),
Url(Url),
}

impl PatchSource {
pub fn apply(&self, config: &Config, dir: &Path) -> Result<()> {
match self {
PatchSource::Path(path) => {
let patch_path = if path.is_absolute() {
path.clone()
} else {
dir.join(path)
Comment thread
ninetailedtori marked this conversation as resolved.
};

let mut cmd = Command::new(&config.git_bin);
cmd.arg("apply").arg("-v").arg(&patch_path).current_dir(dir);

exec::command(&mut cmd)
.with_context(|| tr!("failed to apply patch '{}'", patch_path.display()))?;
}
PatchSource::Url(url) => {
let resp = reqwest::blocking::get(url.clone())
Comment thread
ninetailedtori marked this conversation as resolved.
Outdated
.with_context(|| tr!("Failed to download patch from {}", url))?;
let resp = resp
.error_for_status()
.with_context(|| tr!("Failed to download patch from {}", url))?;

let bytes = resp.bytes()?;
let mut temp = tempfile::NamedTempFile::new()?;
use std::io::Write;
temp.write_all(&bytes)?;

let mut cmd = Command::new(&config.git_bin);
cmd.arg("apply").arg("-v").arg(temp.path()).current_dir(dir);

exec::command(&mut cmd)
.with_context(|| tr!("failed to apply patch '{}'", temp.path().display()))?;
}
}

Ok(())
}
}

impl FromStr for PatchSource {
type Err = Error;

fn from_str(s: &str) -> Result<Self> {
if let Ok(url) = Url::parse(s) {
Ok(PatchSource::Url(url))
} else {
Ok(PatchSource::Path(PathBuf::from(s)))
}
}
}

#[derive(Debug, Default, Clone)]
pub struct PackageOverride {
pub makepkg_conf: Option<String>,
pub env: Vec<(String, String)>,
pub pkgbuild_patches: Vec<PatchSource>,
}

impl PackageOverride {
pub(crate) fn merge_from(&mut self, src: &PackageOverride) {
if src.makepkg_conf.is_some() {
self.makepkg_conf = src.makepkg_conf.clone();
}

for (k, v) in &src.env {
if let Some(existing) = self.env.iter_mut().find(|(ek, _)| ek == k) {
existing.1 = v.clone();
} else {
self.env.push((k.clone(), v.clone()));
}
}

self.pkgbuild_patches
.extend(src.pkgbuild_patches.iter().cloned());
}
}

#[derive(Debug)]
Expand All @@ -393,6 +472,7 @@ enum OverrideParseState {
name: String,
makepkg_conf: Option<String>,
env: Vec<(String, String)>,
pkgbuild_patches: Vec<PatchSource>,
},
Group {
name: String,
Expand Down Expand Up @@ -597,6 +677,7 @@ impl Ini for Config {
name: rest.to_string(),
makepkg_conf: None,
env: Vec::new(),
pkgbuild_patches: Vec::new(),
});
} else if let Some(rest) = section.strip_prefix("override.group.") {
ensure!(
Expand Down Expand Up @@ -1255,6 +1336,22 @@ then initialise it with:
}
}
}
"PkgbuildPatches" => {
let value = value.context(tr!("value can not be empty for key '{}'", key))?;
let parsed = parse_patches_list(value)?;
match state {
OverrideParseState::Package {
pkgbuild_patches, ..
} => {
pkgbuild_patches.extend(parsed);
}
OverrideParseState::Group { .. } => {
bail!(tr!(
"'PkgbuildPatches' is not valid in [override.group.*] sections"
));
}
}
}
_ => eprintln!(
"{}",
tr!("error: unknown option '{}' in override section", key)
Expand All @@ -1275,16 +1372,23 @@ then initialise it with:
name,
makepkg_conf,
env,
pkgbuild_patches,
} => {
ensure!(
makepkg_conf.is_some() || !env.is_empty(),
makepkg_conf.is_some() || !env.is_empty() || !pkgbuild_patches.is_empty(),
tr!(
"override.package.{} must have MakepkgConf or Overrides",
"override.package.{} must have MakepkgConf, Overrides or PkgbuildPatches",
name
)
);
self.overrides
.insert(name, PackageOverride { makepkg_conf, env });
self.overrides.insert(
name,
PackageOverride {
makepkg_conf,
env,
pkgbuild_patches,
},
);
}
OverrideParseState::Group {
name,
Expand All @@ -1302,7 +1406,6 @@ then initialise it with:
);
for pkg in packages {
let entry = self.overrides.entry(pkg).or_default();
// Group overrides merge: later groups overwrite conflicting keys
if let Some(ref conf) = makepkg_conf {
entry.makepkg_conf = Some(conf.clone());
}
Expand Down Expand Up @@ -1343,22 +1446,7 @@ fn parse_overrides_map(input: &str) -> Result<Vec<(String, String)>> {
}

let mut result = Vec::new();
let mut pairs = Vec::new();
let mut current = String::new();
let mut in_quotes = false;
for ch in inner.chars() {
if ch == '"' {
in_quotes = !in_quotes;
current.push(ch);
} else if ch == ',' && !in_quotes {
pairs.push(std::mem::take(&mut current));
} else {
current.push(ch);
}
}
if !current.is_empty() {
pairs.push(current);
}
let pairs = split_by_comma(inner);

for pair in &pairs {
let pair = pair.trim();
Expand Down Expand Up @@ -1392,6 +1480,35 @@ fn parse_overrides_map(input: &str) -> Result<Vec<(String, String)>> {
Ok(result)
}

fn parse_patches_list(input: &str) -> Result<Vec<PatchSource>> {
let input = input.trim();
let inner = input
.strip_prefix('{')
.and_then(|s| s.strip_suffix('}'))
.context(tr!("PkgbuildPatches value must be wrapped in { }"))?
.trim();

if inner.is_empty() {
return Ok(Vec::new());
}

let parts = split_by_comma(inner);

let mut result = Vec::new();
for part in parts {
let value = part
Comment thread
ninetailedtori marked this conversation as resolved.
.strip_prefix('"')
.and_then(|s| s.strip_suffix('"'))
.unwrap_or(part.as_str())
.to_string();

ensure!(!value.is_empty(), tr!("Patch source can not be empty"));
result.push(value.parse()?);
}

Ok(result)
}

pub fn version() {
let ver = option_env!("PARU_VERSION").unwrap_or(env!("CARGO_PKG_VERSION"));
print!("paru v{}", ver);
Expand Down
30 changes: 15 additions & 15 deletions src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ use crate::args::{Arg, Args};
use crate::chroot::Chroot;
use crate::clean::clean_untracked;
use crate::completion::update_aur_cache;
use crate::config::{Config, LocalRepos, Mode, Op, PackageOverride, Sign, YesNoAllTree, YesNoAsk};
use crate::config::{
Config, LocalRepos, Mode, Op, PackageOverride, PatchSource, Sign, YesNoAllTree, YesNoAsk,
};
use crate::devel::{fetch_devel_info, load_devel_info, save_devel_info, DevelInfo};
use crate::download::{self, Bases};
use crate::exec::{command_status, has_command};
Expand Down Expand Up @@ -549,6 +551,8 @@ impl Installer {
self.chroot.makepkg_conf = conf.clone();
}
}

apply_patches(config, dir, &ov.pkgbuild_patches)?;
}

let result = self.build_pkgbuild_inner(config, base, repo, dir, pkgdest, &pkg_override);
Expand Down Expand Up @@ -1039,7 +1043,7 @@ impl Installer {

targets.extend(self.upgrades.repo_keep.iter().map(Targ::from));

if self.shoud_just_pacman(config.mode, aur_targets, &self.upgrades, self.ran_pacman) {
if self.should_just_pacman(config.mode, aur_targets, &self.upgrades, self.ran_pacman) {
print_warnings(config, &cache, None);
let mut args = config.pacman_args();
let targets = targets.iter().map(|t| t.to_string()).collect::<Vec<_>>();
Expand Down Expand Up @@ -1102,7 +1106,7 @@ impl Installer {
err
}

fn shoud_just_pacman(
fn should_just_pacman(
&self,
mode: Mode,
aur_targets: &[Targ<'_>],
Expand Down Expand Up @@ -1307,25 +1311,21 @@ impl Installer {
}
}

fn apply_patches(config: &Config, dir: &Path, patches: &[PatchSource]) -> Result<()> {
for patch in patches {
patch.apply(config, dir)?;
}
Ok(())
}

fn get_base_override(config: &Config, base: &Base) -> Option<PackageOverride> {
let mut result: Option<PackageOverride> = None;

for pkg_name in base.packages() {
if let Some(ov) = config.overrides.get(pkg_name) {
match result {
None => result = Some(ov.clone()),
Some(ref mut merged) => {
if ov.makepkg_conf.is_some() {
merged.makepkg_conf = ov.makepkg_conf.clone();
}
for (k, v) in &ov.env {
if let Some(existing) = merged.env.iter_mut().find(|(ek, _)| ek == k) {
existing.1 = v.clone();
} else {
merged.env.push((k.clone(), v.clone()));
}
}
}
Some(ref mut merged) => merged.merge_from(ov),
}
}
}
Expand Down
23 changes: 23 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,3 +400,26 @@ pub fn is_arch_repo(name: &str) -> bool {
| "multilib-testing"
)
}

pub fn split_by_comma(inner: &str) -> Vec<String> {
let mut items = Vec::new();
let mut current = String::new();
let mut in_quotes = false;

for ch in inner.chars() {
if ch == '"' {
in_quotes = !in_quotes;
current.push(ch);
} else if ch == ',' && !in_quotes {
items.push(std::mem::take(&mut current));
} else {
current.push(ch);
}
}

if !current.is_empty() {
items.push(current);
}

items
}
Loading