pacco/src/pkg/package.rs
JMARyA e6362f69a7
Some checks failed
ci/woodpecker/push/pkgbuild/2 Pipeline is pending
ci/woodpecker/push/pkgbuild/1 Pipeline was successful
ci/woodpecker/push/container Pipeline failed
api
2025-06-28 03:49:27 +02:00

939 lines
28 KiB
Rust

use std::{
io::Write,
path::{Path, PathBuf},
process::{Command, Stdio},
};
use based::get_pg;
use serde::Serialize;
use sqlx::FromRow;
use super::{Repository, arch::Architecture};
/// General Package
#[derive(Debug, Clone)]
pub struct Package {
/// Repository of the package
pub repo: String,
/// `Architecture` of the package
pub arch: Architecture,
/// Name of the package
pub name: String,
pub rel: String,
/// Version of the package
pub version: Option<String>,
/// Compression used
pub compression: Compression,
}
pub struct PackageFile {
pub path: PathBuf,
pub compression: Compression,
}
impl PackageFile {
pub fn new(path: PathBuf) -> Self {
Self {
path: path.clone(),
compression: Compression::from_filename(path.to_str().unwrap()).unwrap(),
}
}
pub fn pkginfo(&self) -> PackageInfo {
let content = read_file_tar(&self.path, ".PKGINFO", self.compression.clone()).unwrap();
let keys = Package::pkginfo_from_str(&content);
PackageInfo::new(keys)
}
pub fn pkginfo_raw(&self) -> Vec<(String, String)> {
let content = read_file_tar(&self.path, ".PKGINFO", self.compression.clone()).unwrap();
Package::pkginfo_from_str(&content)
}
}
impl Package {
/// Create a new package
pub fn new(
repo: &str,
arch: Architecture,
pkg_name: &str,
version: &str,
rel: String,
compression: Compression,
) -> Self {
let pkg = Package {
repo: repo.to_string(),
arch,
name: pkg_name.to_string(),
rel: rel,
version: Some(version.to_string()),
compression,
};
std::fs::create_dir_all(pkg.base_path()).unwrap();
pkg
}
pub fn version(ver: &str) -> (String, String) {
let mut splitted = ver.split('-').collect::<Vec<_>>();
let rel = splitted.pop().unwrap();
let ver = splitted.join("-");
(ver, rel.parse().unwrap())
}
pub fn install_script(&self) -> Option<String> {
let pkg = self.base_path().join(self.file_name());
read_file_tar(&pkg, ".INSTALL", self.compression.clone())
}
pub fn file_list(&self) -> Vec<String> {
list_tar_file(&self.base_path().join(self.file_name()))
.unwrap()
.into_iter()
.filter(|x| !x.ends_with("/"))
.collect()
}
pub fn binaries(&self) -> Vec<String> {
list_tar_file(&self.base_path().join(self.file_name()))
.unwrap_or_default()
.into_iter()
.filter_map(|x| {
let mut paths: Vec<_> = x.split("/").collect();
paths.reverse();
let parent = paths.get(1)?;
if (*parent == "bin" || *parent == "sbin") && !x.ends_with("/") {
return Some(x);
}
None
})
.collect()
}
pub fn pkginfo_from_str(content: &str) -> Vec<(String, String)> {
let mut ret: Vec<(String, Vec<String>)> = Vec::new();
for line in content.split("\n") {
if line.starts_with('#') || line.is_empty() {
continue;
}
let (key, val) = line.split_once(" = ").unwrap();
if let Some(e) = ret.iter_mut().find(|x| x.0 == key) {
e.1.push(val.trim().to_string());
} else {
ret.push((key.to_string(), vec![val.trim().to_string()]));
}
}
let mut ret: Vec<_> = ret.into_iter().map(|x| (x.0, x.1.join(";"))).collect();
ret.sort_by(|a, b| a.0.cmp(&b.0));
ret
}
pub fn pkginfo(&self) -> PackageInfo {
PackageFile::new(self.base_path().join(self.file_name())).pkginfo()
}
pub fn pkginfo_raw(&self) -> Vec<(String, String)> {
PackageFile::new(self.base_path().join(self.file_name())).pkginfo_raw()
}
pub fn arch(&self) -> Vec<Architecture> {
let mut ret = Vec::new();
for a in [
Architecture::x86_64,
Architecture::aarch64,
Architecture::any,
] {
let check_pkg = self.switch_arch(a.clone());
if check_pkg.exists() {
ret.push(a);
}
}
ret
}
/// Extract values from a package filename
///
/// # Example
///
/// ```
/// use pacco::pkg::Package;
///
/// let (name, version, rel, arch, compress) = Package::extract_pkg_name("pkg-ver-rel-x86_64.pkg.tar.zst").unwrap();
/// assert_eq!(name, "pkg");
/// assert_eq!(version, "ver");
/// assert_eq!(rel, "rel");
/// assert_eq!(arch, pacco::pkg::arch::Architecture::x86_64);
/// assert_eq!(compress, pacco::pkg::package::Compression::Zstd);
///
/// let (name, version, rel, arch, compress) = Package::extract_pkg_name("my-pkg-ver-rel-x86_64.pkg.tar.xz").unwrap();
/// assert_eq!(name, "my-pkg");
/// assert_eq!(version, "ver");
/// assert_eq!(rel, "rel");
/// assert_eq!(arch, pacco::pkg::arch::Architecture::x86_64);
/// assert_eq!(compress, pacco::pkg::package::Compression::Xz);
/// ```
pub fn extract_pkg_name(
file_name: &str,
) -> Option<(String, String, String, Architecture, Compression)> {
// Extract (assuming the filename is "<pkg_name>-<version>-<relation>-<arch>.pkg.tar.zst")
let file_name = file_name.trim_end_matches(".sig").to_string();
let mut splitted = file_name.split('-').collect::<Vec<&str>>();
let arch = splitted.pop()?;
let compression = Compression::from_filename(arch);
assert!(compression.is_some());
let compression = compression.unwrap();
let arch = match compression {
Compression::Zstd => arch.trim_end_matches(".pkg.tar.zst"),
Compression::Xz => arch.trim_end_matches(".pkg.tar.xz"),
};
let relation = splitted.pop()?;
let version = splitted.pop()?;
let pkg_name = splitted.join("-");
Some((
pkg_name,
version.to_string(),
relation.to_string(),
Architecture::parse(arch)?,
compression,
))
}
/// Parse a pkg filename
pub fn from_filename(repo: &str, file_name: &str) -> Package {
let (pkg_name, version, rel, arch, compression) =
Package::extract_pkg_name(file_name).unwrap();
Self {
repo: repo.to_string(),
arch,
name: pkg_name,
rel: rel.parse().unwrap(),
version: Some(version.to_string()),
compression,
}
}
/// Find a package with latest version
pub fn find(repo: &str, arch: Architecture, pkg_name: &str) -> Option<Self> {
if let Some(pkg) = Self::find_compressed(repo, arch.clone(), pkg_name, Compression::Zstd) {
return Some(pkg);
}
if let Some(pkg) = Self::find_compressed(repo, arch, pkg_name, Compression::Xz) {
return Some(pkg);
}
None
}
/// Find a package with latest version
fn find_compressed(
repo: &str,
arch: Architecture,
pkg_name: &str,
compression: Compression,
) -> Option<Self> {
let mut base = Package {
repo: repo.to_string(),
arch,
name: pkg_name.to_string(),
rel: 1.to_string(),
version: None,
compression,
};
let versions = base.versions();
let ver = versions.first()?;
let (ver, rel) = Package::version(&ver);
base.version = Some(ver.clone());
base.rel = rel;
Some(base)
}
pub fn man_entries(&self) -> Vec<String> {
let files = self.file_list();
files
.into_iter()
.filter(|x| x.starts_with("usr/share/man"))
.map(|x| {
x.trim_start_matches("usr/share/man/")
.trim_end_matches(".gz")
.to_string()
})
.collect()
}
pub fn kernel_modules(&self) -> Vec<String> {
let files = self.file_list();
files
.into_iter()
.filter(|x| x.starts_with("usr/lib/modules"))
.map(|x| {
x.trim_start_matches("usr/lib/modules/")
.trim_end_matches(".zst")
.to_string()
})
.collect()
}
pub fn fonts(&self) -> Vec<String> {
let files = self.file_list();
files
.into_iter()
.filter(|x| x.starts_with("usr/share/fonts"))
.map(|x| x.trim_start_matches("usr/share/fonts/").to_string())
.collect()
}
pub fn firmware(&self) -> Vec<String> {
let files = self.file_list();
files
.into_iter()
.filter(|x| x.starts_with("usr/lib/firmware"))
.map(|x| {
x.trim_start_matches("usr/lib/firmware/")
.trim_end_matches(".zst")
.to_string()
})
.collect()
}
pub fn keyrings(&self) -> Vec<String> {
let files = self.file_list();
files
.into_iter()
.filter(|x| x.starts_with("usr/share/pacman/keyrings"))
.map(|x| {
x.trim_start_matches("usr/share/pacman/keyrings/")
.to_string()
})
.collect()
}
pub fn shared_objects(&self) -> Vec<String> {
list_tar_file(&self.base_path().join(self.file_name()))
.unwrap_or_default()
.into_iter()
.filter(|x| {
let file_name = x.split("/").last().unwrap();
file_name.contains(".so.") || file_name.ends_with(".so")
})
.collect()
}
pub fn icons(&self) -> Vec<String> {
let files = self.file_list();
files
.into_iter()
.filter(|x| x.starts_with("usr/share/icons"))
.map(|x| x.trim_start_matches("usr/share/icons/").to_string())
.collect()
}
pub fn etc_entries(&self) -> Vec<String> {
let files = self.file_list();
files.into_iter().filter(|x| x.starts_with("etc")).collect()
}
pub fn systemd_units(&self) -> Vec<String> {
// TODO : Extract unit infos
list_tar_file(&self.base_path().join(self.file_name()))
.unwrap_or_default()
.into_iter()
.filter(|x| {
let ext = x.split(".").last().unwrap();
ext == "service" || ext == "timer" || ext == "mount" || ext == "socket"
})
.collect()
}
pub fn readmes(&self) -> Vec<(String, String)> {
let pkg_file = self.base_path().join(self.file_name());
let files = self.file_list();
files
.into_iter()
.filter(|x| {
let file_name = x.split("/").last().unwrap();
let cleaned = file_name.trim_end_matches(".md").trim_end_matches(".txt");
cleaned == "README"
})
.map(|x| {
let content = read_file_tar(&pkg_file, &x, self.compression.clone()).unwrap();
(x, content)
})
.collect()
}
pub fn pacman_hooks(&self) -> Vec<(String, String)> {
let pkg_file = self.base_path().join(self.file_name());
let files = self.file_list();
files
.into_iter()
.filter(|x| {
x.starts_with("etc/pacman.d/hooks/") || x.starts_with("usr/share/libalpm/hooks/")
})
.map(|x| {
let content = read_file_tar(&pkg_file, &x, self.compression.clone()).unwrap();
(x, content)
})
.collect()
}
/// Save a new package to repository
pub fn save(&self, pkg: Vec<u8>, sig: Option<Vec<u8>>) {
let pkg_file = self.base_path().join(self.file_name());
let sig_file = self.base_path().join(format!("{}.sig", self.file_name()));
std::fs::write(&pkg_file, pkg).unwrap();
if let Some(sig) = sig {
std::fs::write(sig_file, sig).unwrap();
}
let db_file = PathBuf::from("./data")
.join(&self.repo)
.join(self.arch.to_string())
.join(format!("{}.db.tar.gz", self.repo));
repo_add(db_file.to_str().unwrap(), pkg_file.to_str().unwrap());
// Add to any arch repo db if `arch=any`
if self.arch == Architecture::any {
let archs = Repository::new(&self.repo).unwrap().arch();
for arch in archs {
if arch == Architecture::any {
continue;
}
let db_file = PathBuf::from("./data")
.join(&self.repo)
.join(arch.to_string())
.join(format!("{}.db.tar.gz", self.repo));
repo_add(db_file.to_str().unwrap(), pkg_file.to_str().unwrap());
}
}
}
fn base_path(&self) -> PathBuf {
// <repo>/<arch>/<pkg>/
Path::new("./data")
.join(&self.repo)
.join(self.arch.to_string())
.join(&self.name)
}
/// Switch the `Architecture` of the package
pub fn switch_arch(&self, arch: Architecture) -> Self {
let mut new = self.clone();
new.arch = arch;
new
}
/// Switch to a specific `version` of the package
pub fn get_version(&self, version: &str) -> Self {
let mut new_pkg = self.clone();
new_pkg.version = Some(version.to_string());
new_pkg
}
/// Check if the specific package exists
pub fn exists(&self) -> bool {
let pkg_file = self.base_path().join(self.file_name());
pkg_file.exists()
}
/// Checks if the package has a signature
pub fn is_signed(&self) -> bool {
let signed_file = self.base_path().join(format!("{}.sig", self.file_name()));
signed_file.exists()
}
/// Build a pkg filename from the packages values
pub fn file_name(&self) -> String {
format!(
"{}-{}-{}-{}.pkg.tar.{}",
self.name,
if let Some(ver) = &self.version {
ver.to_string()
} else {
let versions = self.versions();
Package::version(&versions.first().unwrap().clone()).0
},
self.rel,
self.arch.to_string(),
self.compression.extension()
)
}
pub fn has_pkg_ext(filename: &str) -> bool {
filename.ends_with(".pkg.tar.zst") || filename.ends_with(".pkg.tar.xz")
}
/// Get all versions of the package
pub fn versions(&self) -> Vec<String> {
let dir_path = self.base_path();
let mut versions: Vec<String> = vec![];
if let Ok(entries) = std::fs::read_dir(dir_path) {
for entry in entries.filter_map(Result::ok) {
let file_name = entry.file_name().into_string().unwrap_or_default();
if file_name.starts_with(&self.name) && Package::has_pkg_ext(&file_name) {
let (_, version, rel, _, _) = Package::extract_pkg_name(&file_name).unwrap();
versions.push(format!("{version}-{rel}"));
}
}
}
// Sort versions in descending order (most recent version first)
versions.sort_by(|a, b| b.cmp(a));
versions
}
pub fn pkg_content_path(&self) -> Option<String> {
if self.exists() {
return Some(
self.base_path()
.join(self.file_name())
.to_str()
.unwrap()
.to_string(),
);
}
None
}
/// Get the content of the `.pkg.tar.zst`
pub fn pkg_content(&self) -> Option<Vec<u8>> {
if self.exists() {
return std::fs::read(self.base_path().join(self.file_name())).ok();
}
None
}
/// Get the content of the `.pkg.tar.zst.sig`
pub fn sig_content(&self) -> Option<Vec<u8>> {
if self.exists() {
return std::fs::read(self.base_path().join(format!("{}.sig", &self.file_name()))).ok();
}
None
}
}
pub fn run_command(cmd: Vec<&str>) {
std::process::Command::new(cmd.first().unwrap())
.args(cmd.into_iter().skip(1).collect::<Vec<&str>>())
.spawn()
.unwrap()
.wait()
.unwrap();
}
/// Add a package file to a repo db
pub fn repo_add(db_file: &str, pkg_file: &str) {
run_command(vec!["repo-add", db_file, pkg_file]);
}
pub fn read_file_tar(tar: &Path, file_path: &str, compression: Compression) -> Option<String> {
let mut output = Command::new("tar");
let output = match compression {
Compression::Zstd => output.arg("-xO").arg("--zstd"),
Compression::Xz => output.arg("-xOJ"),
};
let output = output.arg("-f").arg(tar).arg(file_path).output().ok()?;
if output.status.success() {
Some(String::from_utf8(output.stdout).ok()?.to_string())
} else {
None
}
}
pub fn read_file_tar_raw(
tar_data: &[u8],
file_path: &str,
compression: Compression,
) -> Option<String> {
let mut output = Command::new("tar");
let output = match compression {
Compression::Zstd => output.arg("-xO").arg("--zstd"),
Compression::Xz => output.arg("xOJ"),
};
let mut output = output
.arg("-f")
.arg("-") // Indicate that the file input comes from stdin
.arg(file_path)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.ok()?;
if let Some(stdin) = output.stdin.as_mut() {
stdin.write_all(tar_data).ok()?;
stdin.flush().ok()?;
}
let output = output.wait_with_output().ok()?;
if output.status.success() {
Some(String::from_utf8(output.stdout).ok()?.to_string())
} else {
None
}
}
pub fn list_tar_file(tar: &Path) -> Option<Vec<String>> {
let output = Command::new("tar")
.arg("-tf") // List the contents of the tar file
.arg(tar)
.output()
.ok()?;
if output.status.success() {
let output_str = String::from_utf8(output.stdout).ok()?;
let paths = output_str
.lines()
.map(|line| line.to_string())
.collect::<Vec<String>>();
Some(paths)
} else {
None
}
}
#[derive(Debug, Clone, FromRow)]
pub struct PackageMeta {
pub repo: String,
pub name: String,
pub arch: String,
pub version: String,
pub rel: String,
pub download_count: i32,
}
pub trait PackageMetaInfo {
fn download_amount(&self) -> impl std::future::Future<Output = i32>;
fn increase_download_count(&self) -> impl std::future::Future<Output = ()>;
}
impl PackageMetaInfo for Package {
async fn download_amount(&self) -> i32 {
let res: Option<(i32,)> = sqlx::query_as("SELECT download_count FROM package_meta WHERE repo = $1 AND name = $2 AND version = $3 AND arch = $4 AND rel = $5")
.bind(&self.repo)
.bind(&self.name)
.bind(self.version.as_ref().unwrap())
.bind(&self.arch.to_string())
.bind(&self.rel)
.fetch_optional(get_pg!()).await.unwrap();
res.map(|x| x.0).unwrap_or(0)
}
async fn increase_download_count(&self) {
sqlx::query("INSERT INTO package_meta (repo, name, arch, version, rel) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (repo, name, arch, version, rel) DO UPDATE SET download_count = package_meta.download_count + 1")
.bind(&self.repo)
.bind(&self.name)
.bind(&self.arch.to_string())
.bind(&self.version.as_ref().map(|x| x.to_string()).unwrap_or_default())
.bind(&self.rel)
.execute(get_pg!()).await.unwrap();
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Compression {
Zstd,
Xz,
}
impl Compression {
pub fn from_filename(name: &str) -> Option<Self> {
if name.ends_with("zst") {
Some(Self::Zstd)
} else if name.ends_with("xz") {
Some(Self::Xz)
} else {
None
}
}
pub fn extension(&self) -> &str {
match self {
Compression::Zstd => "zst",
Compression::Xz => "xz",
}
}
}
#[derive(Debug, Default, Serialize)]
pub struct PackageInfo {
/// Architecture
pub architectures: Vec<Architecture>,
/// Build Timestamp
pub build_date: i64,
/// Licenses
pub licenses: Vec<String>,
/// Make Dependencies
pub makedepends: Vec<String>,
/// Packager
pub packager: Option<String>,
/// Package Base
pub pkgbase: String,
/// Description
pub description: String,
/// Package Name
pub name: String,
/// Version
pub version: String,
/// Size
pub size: i64,
/// Website
pub website: Option<String>,
/// XDATA
pub xdata: Option<String>,
/// Dependencies
pub dependencies: Vec<String>,
/// Optional Dependencies
pub optdepends: Vec<OptionalDependency>,
/// Provides
pub provides: Vec<String>,
// Replaces
pub replaces: Vec<String>,
/// Backup
pub backup: Vec<String>,
/// Conflicts
pub conflicts: Vec<String>,
/// Check Dependencies
pub checkdepends: Vec<String>,
/// Groups
pub groups: Vec<String>,
}
#[derive(Debug, Default, Serialize)]
pub struct OptionalDependency {
pub pkg: String,
pub reason: Option<String>,
}
impl OptionalDependency {
pub fn as_str(&self) -> String {
if let Some(reason) = &self.reason {
format!("{}: {}", self.pkg, reason)
} else {
self.pkg.clone()
}
}
}
impl PackageInfo {
pub fn new(keys: Vec<(String, String)>) -> Self {
let mut info = Self::default();
for (key, val) in keys {
match key.as_str() {
"arch" => {
info.architectures.push(Architecture::parse(&val).unwrap());
}
"builddate" => {
info.build_date = val.parse().unwrap();
}
"license" => {
let elements: Vec<_> = val.split(';').collect();
for e in elements {
let licenses: Vec<_> = e.split(" OR ").collect();
for e in licenses {
info.licenses.push(e.to_string());
}
}
}
"makedepend" => {
let pkgs: Vec<_> = val.split(';').collect();
for pkg in pkgs {
info.makedepends.push(pkg.to_string());
}
}
"packager" => {
info.packager = Some(val);
}
"pkgbase" => {
info.pkgbase = val;
}
"pkgdesc" => {
info.description = val;
}
"pkgname" => {
info.name = val;
}
"pkgver" => {
info.version = val;
}
"size" => {
info.size = val.parse().unwrap();
}
"url" => {
info.website = Some(val);
}
"xdata" => {
info.xdata = Some(val);
}
"depend" => {
let pkgs: Vec<_> = val.split(';').collect();
for pkg in pkgs {
info.dependencies.push(pkg.to_string());
}
}
"optdepend" => {
let pkgs: Vec<_> = val.split(';').collect();
for pkg in pkgs {
let (pkg, reason) = if let Some((pkg, reason)) = pkg.split_once(':') {
let reason = if !reason.is_empty() {
Some(reason.trim().to_string())
} else {
None
};
(pkg, reason)
} else {
(pkg, None)
};
info.optdepends.push(OptionalDependency {
pkg: pkg.to_string(),
reason: reason,
});
}
}
"provides" => {
let provides: Vec<_> = val.split(';').collect();
for p in provides {
info.provides.push(p.to_string());
}
}
"replaces" => {
let replaces: Vec<_> = val.split(';').collect();
for r in replaces {
info.replaces.push(r.to_string());
}
}
"backup" => {
let backups: Vec<_> = val.split(';').collect();
for b in backups {
info.backup.push(b.to_string());
}
}
"conflict" => {
let conflicts: Vec<_> = val.split(';').collect();
for e in conflicts {
info.conflicts.push(e.to_string());
}
}
"checkdepend" => {
let pkgs: Vec<_> = val.split(';').collect();
for pkg in pkgs {
info.checkdepends.push(pkg.to_string());
}
}
"group" => {
let groups: Vec<_> = val.split(';').collect();
for group in groups {
info.groups.push(group.to_string());
}
}
_ => {
println!("Unrecognized key {key} -> {val}");
std::process::exit(1);
}
}
}
info
}
pub fn gen_array(key: &str, vals: &[String]) -> String {
if vals.is_empty() {
return String::new();
}
let mut ret = format!("{key}=(");
for (index, val) in vals.iter().enumerate() {
if index == vals.len() - 1 {
ret.push_str(&format!("'{val}'"));
} else {
ret.push_str(&format!("'{val}' "));
}
}
ret.push_str(")\n");
ret
}
/// Generate a basic `PKGBUILD` from `PKGINFO`
pub fn pkgbuild(&self) -> String {
#[allow(non_snake_case)]
let mut PKGBUILD = String::new();
if let Some(packager) = &self.packager {
PKGBUILD.push_str(&format!("# Packager: {packager}\n\n"));
}
PKGBUILD.push_str(&format!("pkgbase={}\n", self.pkgbase));
PKGBUILD.push_str(&format!("pkgname={}\n", self.name));
PKGBUILD.push_str(&format!("pkgdesc={}\n", self.description));
PKGBUILD.push_str(&format!("pkgver={}\n", self.version));
let arch: Vec<_> = self.architectures.iter().map(|x| x.to_string()).collect();
PKGBUILD.push_str(&PackageInfo::gen_array("arch", &arch));
PKGBUILD.push_str(&PackageInfo::gen_array("license", &self.licenses));
PKGBUILD.push_str(&PackageInfo::gen_array("makedepends", &self.makedepends));
PKGBUILD.push_str(&PackageInfo::gen_array("depends", &self.dependencies));
PKGBUILD.push_str(&PackageInfo::gen_array("checkdepends", &self.checkdepends));
PKGBUILD.push_str(&PackageInfo::gen_array(
"optdepends",
&self
.optdepends
.iter()
.map(|x| x.as_str())
.collect::<Vec<_>>(),
));
if let Some(website) = &self.website {
PKGBUILD.push_str(&format!("url={}\n", website));
}
PKGBUILD.push_str(&PackageInfo::gen_array("provides", &self.provides));
PKGBUILD.push_str(&PackageInfo::gen_array("replaces", &self.replaces));
PKGBUILD.push_str(&PackageInfo::gen_array("backup", &self.backup));
PKGBUILD.push_str(&PackageInfo::gen_array("conflicts", &self.conflicts));
PKGBUILD.push_str(&PackageInfo::gen_array("groups", &self.groups));
PKGBUILD.push_str("\npackage() {\n\trsync -avzhruP ../root/ \"$pkdir/\"\n}");
PKGBUILD
}
}