PHANTOM
🇮🇳 IN
Skip to content

Commit 7889045

Browse files
author
Vasili Novikov
committed
add support for split packages
fixes GH-21
1 parent f1402a1 commit 7889045

File tree

8 files changed

+167
-79
lines changed

8 files changed

+167
-79
lines changed

res/shellcheck-wrapper

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ test "${#pkgrel[@]}" -gt 0
1717
test "${#arch[@]}" -gt 0
1818

1919
# avoid "unused" warning for optional PKGBUILD variables:
20+
export pkgbase
2021
export epoch
2122
export pkgdesc
2223
export url

src/action_install.rs

Lines changed: 130 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,39 @@ use std::path::PathBuf;
2121

2222
pub fn install(targets: Vec<String>, dirs: &ProjectDirs, is_offline: bool, asdeps: bool) {
2323
let mut pacman_deps = HashSet::new();
24-
let mut aur_packages = HashMap::new();
24+
let mut split_to_depth = HashMap::new();
25+
let mut split_to_pkgbase = HashMap::new();
26+
let mut split_to_version = HashMap::new();
2527
let alpm = pacman::create_alpm();
2628
for install_target in targets {
2729
resolve_dependencies(
2830
&install_target,
2931
&mut pacman_deps,
30-
&mut aur_packages,
32+
&mut split_to_depth,
33+
&mut split_to_pkgbase,
34+
&mut split_to_version,
3135
0,
3236
&alpm,
3337
);
3438
}
3539
pacman_deps.retain(|name| !pacman::is_package_installed(&alpm, name));
36-
show_install_summary(&pacman_deps, &aur_packages);
37-
for name in aur_packages.keys() {
38-
let dir = rua_files::review_dir(dirs, name);
39-
fs::create_dir_all(&dir)
40-
.unwrap_or_else(|err| panic!("Failed to create repository dir for {}, {}", name, err));
41-
reviewing::review_repo(&dir, name, dirs);
40+
show_install_summary(&pacman_deps, &split_to_depth);
41+
for pkgbase in split_to_pkgbase.values().collect::<HashSet<_>>() {
42+
let dir = rua_files::review_dir(dirs, pkgbase);
43+
fs::create_dir_all(&dir).unwrap_or_else(|err| {
44+
panic!("Failed to create repository dir for {}, {}", pkgbase, err)
45+
});
46+
reviewing::review_repo(&dir, pkgbase, dirs);
4247
}
4348
pacman::ensure_pacman_packages_installed(pacman_deps);
44-
install_all(dirs, aur_packages, is_offline, asdeps);
49+
install_all(
50+
dirs,
51+
split_to_depth,
52+
split_to_pkgbase,
53+
split_to_version,
54+
is_offline,
55+
asdeps,
56+
);
4557
}
4658

4759
fn show_install_summary(pacman_deps: &HashSet<String>, aur_packages: &HashMap<String, i32>) {
@@ -69,14 +81,42 @@ fn show_install_summary(pacman_deps: &HashSet<String>, aur_packages: &HashMap<St
6981
}
7082
}
7183

72-
fn install_all(dirs: &ProjectDirs, packages: HashMap<String, i32>, offline: bool, asdeps: bool) {
73-
let mut packages = packages.iter().collect::<Vec<_>>();
74-
packages.sort_by_key(|pair| -*pair.1);
75-
for (depth, packages) in &packages.iter().group_by(|pair| *pair.1) {
76-
let packages: Vec<_> = packages.map(|pair| pair.0).collect();
77-
for name in &packages {
78-
let review_dir = rua_files::review_dir(dirs, name);
79-
let build_dir = rua_files::build_dir(dirs, name);
84+
fn install_all(
85+
dirs: &ProjectDirs,
86+
split_to_depth: HashMap<String, i32>,
87+
split_to_pkgbase: HashMap<String, String>,
88+
split_to_version: HashMap<String, String>,
89+
offline: bool,
90+
asdeps: bool,
91+
) {
92+
let archive_whitelist = split_to_version
93+
.into_iter()
94+
.map(|pair| format!("{}-{}", pair.0, pair.1))
95+
.collect::<Vec<_>>();
96+
trace!("All expected archive files: {:?}", archive_whitelist);
97+
// get a list of (pkgbase, depth)
98+
let packages = split_to_pkgbase.iter().map(|(split, pkgbase)| {
99+
let depth = split_to_depth
100+
.get(split)
101+
.expect("Internal error: split package doesn't have recursive depth");
102+
(pkgbase.to_string(), *depth, split.to_string())
103+
});
104+
// sort pairs in descending depth order
105+
let packages = packages.sorted_by_key(|(_pkgbase, depth, _split)| -depth);
106+
// Note that a pkgbase can appear at multiple depths because
107+
// multiple split pkgnames can be at multiple depths.
108+
// In this case, we only take the first occurrence of pkgbase,
109+
// which would be the maximum depth because of sort order.
110+
// We only take one occurrence because we want the package to only be built once.
111+
let packages: Vec<(String, i32, String)> = packages
112+
.unique_by(|(pkgbase, _depth, _split)| pkgbase.to_string())
113+
.collect::<Vec<_>>();
114+
// once we have a collection of pkgname-s and their depth, proceed straightforwardly.
115+
for (depth, packages) in &packages.iter().group_by(|(_pkgbase, depth, _split)| *depth) {
116+
let packages = packages.collect::<Vec<&(String, i32, String)>>();
117+
for (pkgbase, _depth, _split) in &packages {
118+
let review_dir = rua_files::review_dir(dirs, pkgbase);
119+
let build_dir = rua_files::build_dir(dirs, pkgbase);
80120
rm_rf::force_remove_all(&build_dir, true).expect("Failed to remove old build dir");
81121
std::fs::create_dir_all(&build_dir).expect("Failed to create build dir");
82122
fs_extra::copy_items(
@@ -92,31 +132,35 @@ fn install_all(dirs: &ProjectDirs, packages: HashMap<String, i32>, offline: bool
92132
offline,
93133
);
94134
}
95-
for name in &packages {
96-
check_tars_and_move(name, dirs);
135+
for (pkgbase, _depth, _split) in &packages {
136+
check_tars_and_move(pkgbase, dirs, &archive_whitelist);
97137
}
138+
// This relation between split_name and the archive file is not actually correct here.
139+
// Instead, all archive files of some group will be bound to one split name only here.
140+
// This is probably still good enough for install verification though --
141+
// and we only use this relation for this purpose. Feel free to improve, if you want...
98142
let mut files_to_install: Vec<(String, PathBuf)> = Vec::new();
99-
for name in &packages {
100-
let checked_tars = rua_files::checked_tars_dir(dirs, &name);
143+
for (pkgbase, _depth, split) in &packages {
144+
let checked_tars = rua_files::checked_tars_dir(dirs, &pkgbase);
101145
let read_dir_iterator = fs::read_dir(checked_tars).unwrap_or_else(|e| {
102146
panic!(
103147
"Failed to read 'checked_tars' directory for {}, {}",
104-
name, e
148+
pkgbase, e
105149
)
106150
});
151+
107152
for file in read_dir_iterator {
108153
files_to_install.push((
109-
name.to_string(),
110-
file.expect("Failed to open file for tar_check analysis")
111-
.path(),
154+
split.to_string(),
155+
file.expect("Failed to access checked_tars dir").path(),
112156
));
113157
}
114158
}
115159
pacman::ensure_aur_packages_installed(files_to_install, asdeps || depth > 0);
116160
}
117161
}
118162

119-
pub fn check_tars_and_move(name: &str, dirs: &ProjectDirs) {
163+
pub fn check_tars_and_move(name: &str, dirs: &ProjectDirs, archive_whitelist: &[String]) {
120164
debug!("{}:{} checking tars for package {}", file!(), line!(), name);
121165
let build_dir = rua_files::build_dir(dirs, name);
122166
let dir_items: ReadDir = build_dir.read_dir().unwrap_or_else(|err| {
@@ -125,14 +169,23 @@ pub fn check_tars_and_move(name: &str, dirs: &ProjectDirs) {
125169
&build_dir, err
126170
)
127171
});
128-
let checked_files = dir_items.flat_map(|file| {
129-
tar_check::tar_check(
130-
&file
131-
.expect("Failed to open file for tar_check analysis")
132-
.path(),
133-
)
134-
});
135-
debug!("all package (tar) files checked, moving them",);
172+
let dir_items = dir_items.map(|f| f.expect("Failed to open file for tar_check analysis"));
173+
let dir_items = dir_items
174+
.filter(|file| {
175+
let file_name = file.file_name();
176+
let file_name = file_name
177+
.to_str()
178+
.expect("Non-UTF8 characters in tar file name");
179+
archive_whitelist
180+
.iter()
181+
.any(|prefix| file_name.starts_with(prefix))
182+
})
183+
.collect::<Vec<_>>();
184+
trace!("Files filtered for tar checking: {:?}", &dir_items);
185+
for file in dir_items.iter() {
186+
tar_check::tar_check(&file.path())
187+
}
188+
debug!("all package (tar) files checked, moving them");
136189
let checked_tars_dir = rua_files::checked_tars_dir(dirs, name);
137190
rm_rf::force_remove_all(&checked_tars_dir, true).unwrap_or_else(|err| {
138191
panic!(
@@ -147,12 +200,12 @@ pub fn check_tars_and_move(name: &str, dirs: &ProjectDirs) {
147200
);
148201
});
149202

150-
for file in checked_files {
151-
let file_name = file.file_name().expect("Failed to parse package tar name");
203+
for file in dir_items {
204+
let file_name = file.file_name();
152205
let file_name = file_name
153206
.to_str()
154207
.expect("Non-UTF8 characters in tar file name");
155-
fs::rename(&file, checked_tars_dir.join(file_name)).unwrap_or_else(|e| {
208+
fs::rename(&file.path(), checked_tars_dir.join(file_name)).unwrap_or_else(|e| {
156209
panic!(
157210
"Failed to move {:?} (build artifact) to {:?}, {}",
158211
&file, &checked_tars_dir, e,
@@ -162,32 +215,50 @@ pub fn check_tars_and_move(name: &str, dirs: &ProjectDirs) {
162215
}
163216

164217
/// Check that the package name is easy to work with in shell
165-
fn check_package_name(name: &str) {
218+
fn clean_and_check_package_name(name: &str) -> String {
219+
lazy_static! {
220+
static ref CLEANUP: Regex = Regex::new(r"(=.*|>.*|<.*)").unwrap_or_else(|err| panic!(
221+
"{}:{} Failed to parse regexp, {}",
222+
file!(),
223+
line!(),
224+
err
225+
));
226+
}
227+
let name: String = CLEANUP.replace_all(name, "").to_string();
166228
lazy_static! {
167-
static ref NAME_REGEX: Regex = Regex::new(r"[a-zA-Z][a-zA-Z._-]*")
168-
.unwrap_or_else(|_| panic!("{}:{} Failed to parse regexp", file!(), line!()));
229+
static ref NAME_REGEX: Regex = Regex::new(r"^[a-zA-Z][a-zA-Z0-9._+-]*$").unwrap_or_else(
230+
|err| panic!("{}:{} Failed to parse regexp, {}", file!(), line!(), err)
231+
);
169232
}
170-
if !NAME_REGEX.is_match(name) {
233+
if !NAME_REGEX.is_match(&name) {
171234
eprintln!("Unexpected package name {}", name);
172235
std::process::exit(1)
173236
}
237+
name
174238
}
175239

240+
/// Resolve dependencies recursively.
241+
/// "split_name" is the `pkgname` in PKGBUILD terminology. It's called "split" to avoid
242+
/// ambiguity of "package name" meaning.
176243
fn resolve_dependencies(
177-
name: &str,
244+
split_name: &str,
178245
pacman_deps: &mut HashSet<String>,
179-
aur_packages: &mut HashMap<String, i32>,
246+
split_to_depth: &mut HashMap<String, i32>,
247+
split_to_pkgbase: &mut HashMap<String, String>,
248+
split_to_version: &mut HashMap<String, String>,
180249
depth: i32,
181250
alpm: &Alpm,
182251
) {
183-
check_package_name(&name);
184-
if let Some(old_depth) = aur_packages.get(name) {
252+
let split_name = clean_and_check_package_name(&split_name);
253+
if let Some(old_depth) = split_to_depth.get(&split_name) {
185254
let old_depth = *old_depth;
186-
aur_packages.insert(name.to_owned(), cmp::max(depth + 1, old_depth));
187-
info!("Skipping already resolved package {}", name);
255+
split_to_depth.insert(split_name.to_owned(), cmp::max(depth + 1, old_depth));
256+
info!("Skipping already resolved package {}", split_name);
188257
} else {
189-
aur_packages.insert(name.to_owned(), depth);
190-
let info = raur_info(&name);
258+
split_to_depth.insert(split_name.to_owned(), depth);
259+
let info = raur_info(&split_name);
260+
split_to_pkgbase.insert(split_name.to_string(), info.package_base);
261+
split_to_version.insert(split_name.to_string(), info.version);
191262
let deps = info
192263
.depends
193264
.iter()
@@ -199,9 +270,17 @@ fn resolve_dependencies(
199270
} else if !pacman::is_package_installable(alpm, &dep) {
200271
info!(
201272
"{} depends on AUR package {}. Trying to resolve it...",
202-
name, &dep
273+
split_name, &dep
274+
);
275+
resolve_dependencies(
276+
&dep,
277+
pacman_deps,
278+
split_to_depth,
279+
split_to_pkgbase,
280+
split_to_version,
281+
depth + 1,
282+
alpm,
203283
);
204-
resolve_dependencies(&dep, pacman_deps, aur_packages, depth + 1, alpm);
205284
} else {
206285
pacman_deps.insert(dep.to_owned());
207286
}

src/main.rs

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -124,20 +124,6 @@ fn main() {
124124
env::remove_var("CLICOLOR");
125125
}
126126
}
127-
assert!(
128-
env::var_os("PKGDEST").is_none(),
129-
"PKGDEST environment is set, but RUA needs to modify it. Please run RUA without it"
130-
);
131-
let is_extension_compatible = env::var_os("PKGEXT").map_or(true, |ext| {
132-
let ext = ext.to_string_lossy();
133-
ext.ends_with(".tar") || ext.ends_with(".tar.xz")
134-
});
135-
assert!(
136-
is_extension_compatible,
137-
"PKGEXT environment is set to an incompatible value. \
138-
Only *.tar and *.tar.xz are supported."
139-
);
140-
set_env_if_not_set("PKGEXT", ".pkg.tar.xz");
141127

142128
let locked_file = File::open(dirs.config_dir()).expect("Failed to find config dir for locking");
143129
locked_file.try_lock_exclusive().unwrap_or_else(|_| {
@@ -181,6 +167,28 @@ fn prepare_for_jailed_action(dirs: &ProjectDirs) {
181167
eprintln!("Also, makepkg will not allow you building as root anyway.");
182168
exit(1)
183169
}
170+
assert!(
171+
env::var_os("PKGDEST").is_none(),
172+
"Cannot work with PKGDEST environment being set. Please run RUA without it"
173+
);
174+
assert!(
175+
env::var_os("SRCDEST").is_none(),
176+
"Cannot work with SRCDEST environment being set. Please run RUA without it"
177+
);
178+
assert!(
179+
env::var_os("BUILDDIR").is_none(),
180+
"Cannot work with BUILDDIR environment being set. Please run RUA without it"
181+
);
182+
if let Some(extension) = std::env::var_os("PKGEXT") {
183+
assert!(
184+
extension == ".pkg.tar" || extension == ".pkg.tar.xz",
185+
"PKGEXT environment is set to an incompatible value. \
186+
Only .pkg.tar and .pkg.tar.xz are supported for now.\
187+
RUA needs those extensions to look inside the archives for 'tar_check' analysis."
188+
);
189+
} else {
190+
env::set_var("PKGEXT", ".pkg.tar.xz");
191+
};
184192
if !Command::new("bwrap")
185193
.args(&["--ro-bind", "/", "/", "true"])
186194
.status()

src/pacman.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,6 @@ lazy_static! {
142142
}
143143
let arch = str::from_utf8(&process_output.stdout).expect("Found non-utf8 in pacman-conf output");
144144
// Trim away the "/n" & convert into a String
145-
arch.trim().into()
145+
arch.trim().to_string()
146146
};
147147
}

src/reviewing.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use directories::ProjectDirs;
66
use log::debug;
77
use std::path::PathBuf;
88

9-
pub fn review_repo(dir: &PathBuf, pkg_name: &str, dirs: &ProjectDirs) {
9+
pub fn review_repo(dir: &PathBuf, pkgbase: &str, dirs: &ProjectDirs) {
1010
let mut dir_contents = dir.read_dir().unwrap_or_else(|err| {
1111
panic!(
1212
"{}:{} Failed to read directory for reviewing, {}",
@@ -17,13 +17,13 @@ pub fn review_repo(dir: &PathBuf, pkg_name: &str, dirs: &ProjectDirs) {
1717
});
1818
if dir_contents.next().is_none() {
1919
debug!("Directory {:?} is empty, using git clone", &dir);
20-
git_utils::init_repo(pkg_name, &dir);
20+
git_utils::init_repo(pkgbase, &dir);
2121
} else {
2222
debug!("Directory {:?} is not empty, fetching new version", &dir);
2323
git_utils::fetch(&dir);
2424
}
2525

26-
let build_dir = rua_files::build_dir(dirs, pkg_name);
26+
let build_dir = rua_files::build_dir(dirs, pkgbase);
2727
if build_dir.exists() && git_utils::is_upstream_merged(&dir) {
2828
eprintln!("WARNING: your AUR repo is up-to-date.");
2929
eprintln!(

src/rua_files.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ pub fn global_review_dir(dirs: &ProjectDirs) -> PathBuf {
66
dirs.config_dir().join("pkg")
77
}
88

9-
pub fn review_dir(dirs: &ProjectDirs, pkg_name: &str) -> PathBuf {
10-
global_review_dir(dirs).join(pkg_name)
9+
pub fn review_dir(dirs: &ProjectDirs, pkgbase: &str) -> PathBuf {
10+
global_review_dir(dirs).join(pkgbase)
1111
}
1212

1313
/// Directory where packages are built after review

0 commit comments

Comments
 (0)