Skip to content

Commit 44b7420

Browse files
committed
Reworks unneeded packages removal to be more maintainable
1 parent 1775258 commit 44b7420

File tree

1 file changed

+154
-90
lines changed

1 file changed

+154
-90
lines changed

src/util/unneeded_pkgs.rs

Lines changed: 154 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,147 +1,211 @@
11
use std::{cell::Cell, cmp::Ordering, collections::HashMap};
22

3-
use alpm::{DepMod, PackageReason, Ver};
3+
use alpm::{AlpmList, Dep, DepMod, PackageReason, Ver};
44
use alpm_utils::DbListExt;
55

66
use crate::config::Config;
77

8+
type PkgName<'a> = &'a str;
9+
10+
/// Removal state of a package
811
#[derive(Hash, PartialEq, Eq, Default, Copy, Clone)]
912
enum State {
13+
/// This package should be kept (probably explicitely installed or used by an explicitely installed package)
14+
Keep { deps: DepState },
15+
16+
/// This package should be removed
1017
#[default]
1118
Remove,
12-
/// The same as keep but whose dependencies need to be checked first
13-
CheckDeps,
14-
Keep,
19+
}
20+
21+
/// Traversal state of a package's dependencies.
22+
#[derive(Hash, PartialEq, Eq, Copy, Clone)]
23+
enum DepState {
24+
/// All dependencies have already been traversed and marked [`State::Keep`]
25+
AlreadyMarked,
26+
27+
/// The package has already been marked [`State::Keep`] but its dependencies haven't been marked yet
28+
NotMarkedYet,
1529
}
1630

1731
// A provider of a dependency
1832
#[derive(Debug)]
1933
struct Provider<'a> {
2034
// Name of the package that provides some dependency
21-
name: &'a str,
35+
pkg_name: PkgName<'a>,
2236

2337
// Version of the dependency that package provides
2438
ver: Option<&'a Ver>,
2539
}
2640

27-
pub fn unneeded_pkgs(config: &Config, keep_make: bool, keep_optional: bool) -> Vec<&str> {
28-
let mut states = HashMap::new();
29-
let mut remove = Vec::new();
30-
let mut providers: HashMap<&str, Vec<Provider>> = HashMap::new();
41+
pub fn unneeded_pkgs(config: &Config, keep_make: bool, keep_optional: bool) -> Vec<PkgName<'_>> {
42+
// Removal state of each package on the system
43+
let mut states: HashMap<PkgName, Cell<State>> = HashMap::new();
44+
45+
// A list of every provided dependency.
46+
// Maps from provided dependency name -> provided dependency version and the name of the package that provides it.
47+
let mut providers: HashMap<PkgName, Vec<Provider>> = HashMap::new();
3148
let db = config.alpm.localdb();
3249

33-
// iterate over all packages
50+
// Iterate over all packages and populate `states` and `providers`
3451
for pkg in db.pkgs() {
35-
// add self as a provider of self
52+
// Add pkg as a provider of pkg
3653
let provider = Provider {
37-
name: pkg.name(),
54+
pkg_name: pkg.name(),
3855
ver: Some(pkg.version()),
3956
};
4057
providers.entry(pkg.name()).or_default().push(provider);
4158

42-
// add self as a provider of all packages that it "provides"
59+
// Add pkg as a provider of all dependencies that pkg provides
4360
for dep in pkg.provides() {
4461
let provider = Provider {
45-
name: pkg.name(),
62+
pkg_name: pkg.name(),
4663
ver: dep.version(),
4764
};
4865

4966
providers.entry(dep.name()).or_default().push(provider);
5067
}
5168

52-
// mark the package to be removed if not explicitely installed
69+
// By default, mark every explicitely installed package to be kept, and every dependency to be removed
5370
if pkg.reason() == PackageReason::Explicit {
54-
states.insert(pkg.name(), Cell::new(State::CheckDeps));
71+
states.insert(
72+
pkg.name(),
73+
Cell::new(State::Keep {
74+
deps: DepState::NotMarkedYet,
75+
}),
76+
);
5577
} else {
5678
states.insert(pkg.name(), Cell::new(State::Remove));
5779
}
5880
}
5981

60-
// now
61-
// states contains names of pkgs mapped to -> checkdeps if explicit, remove otherwise
62-
// providers contains a list of all package providers
63-
64-
let mut again = true;
65-
while again {
66-
again = false;
67-
68-
// marks all providers of the dependency this package requires as "CheckDeps"
69-
let mut check_deps = |deps: alpm::AlpmList<&alpm::Dep>| {
70-
for dep in deps {
71-
// get all providers of the dependency dep
72-
for provider in providers
73-
.get(dep.name())
74-
.into_iter()
75-
.flatten()
76-
.filter(|provider| {
77-
let Some(required_version) = dep.version() else {
78-
// pass through all providers if the package doesn't depend on a particular version
79-
return true;
80-
};
81-
82-
let Some(provided_version) = provider.ver else {
83-
// the provider doesn't specify what version it provides but we depend on a particular version
84-
return false;
85-
};
86-
87-
let ver_cmp = provided_version.vercmp(required_version);
88-
let ver_requirement = dep.depmod();
89-
match (ver_cmp, ver_requirement) {
90-
// Note: I don't believe this should ever be hit because a version requirement ~does~ exist
91-
// (because dep.version() is Some)
92-
(_, DepMod::Any) => true,
93-
(Ordering::Less, DepMod::Lt | DepMod::Le) => true,
94-
(Ordering::Equal, DepMod::Eq | DepMod::Le | DepMod::Ge) => true,
95-
(Ordering::Greater, DepMod::Gt | DepMod::Ge) => true,
96-
_ => false,
97-
}
98-
})
99-
{
100-
let state = states.get(provider.name).unwrap();
101-
102-
// if the dependency isn't marked to keep (probably marked to remove?), then mark to check its dependencies instead
103-
// this means this package is needed
104-
if state.get() != State::Keep {
105-
state.set(State::CheckDeps);
106-
again = true;
107-
}
82+
// Go through all packages that are marked to be kept and mark their dependencies as also needed to be kept
83+
let mut every_dependency_checked = false;
84+
while !every_dependency_checked {
85+
// assume we checked every dependency
86+
// this is set to false if we found some that haven't been checked yet
87+
every_dependency_checked = true;
88+
89+
// Iterate over all packages that need their dependencies to be marked to keep, and mark them to keep.
90+
// At the start these are only those that have been explicitely installed but later can also be the dependencies of them and their dependencies.
91+
// This also means that packages that were installed as dependencies but then never reaced through .depends() and friends are left to be removed
92+
for (&pkg, state) in states.iter().filter(|(_, state)| {
93+
state.get()
94+
== State::Keep {
95+
deps: DepState::NotMarkedYet,
10896
}
97+
}) {
98+
let pkg = db.pkg(pkg).unwrap();
99+
100+
state.set(State::Keep {
101+
deps: DepState::AlreadyMarked,
102+
});
103+
104+
mark_deps_as_kept(
105+
pkg.depends(),
106+
&providers,
107+
&states,
108+
&mut every_dependency_checked,
109+
);
110+
111+
if keep_optional {
112+
mark_deps_as_kept(
113+
pkg.optdepends(),
114+
&providers,
115+
&states,
116+
&mut every_dependency_checked,
117+
);
109118
}
110-
};
111119

112-
// iterate over all packages that need their dependencies to be checked, and mark them to keep
113-
// at the start these are only those that have been explicitely installed but later can also be the dependencies of them and their dependencies
114-
// this also means that packages that were installed as dependencies but then never reaced through .depends() and friends are left to be removed
115-
for (&pkg, state) in &states {
116-
if state.get() != State::CheckDeps {
120+
if keep_make {
117121
continue;
118122
}
119123

120-
if let Ok(pkg) = db.pkg(pkg) {
121-
state.set(State::Keep);
122-
check_deps(pkg.depends());
123-
124-
if keep_optional {
125-
check_deps(pkg.optdepends());
126-
}
127-
128-
if keep_make {
129-
continue;
130-
}
131-
132-
if config.alpm.syncdbs().pkg(pkg.name()).is_err() {
133-
check_deps(pkg.makedepends());
134-
check_deps(pkg.checkdepends());
135-
}
124+
if config.alpm.syncdbs().pkg(pkg.name()).is_err() {
125+
mark_deps_as_kept(
126+
pkg.makedepends(),
127+
&providers,
128+
&states,
129+
&mut every_dependency_checked,
130+
);
131+
mark_deps_as_kept(
132+
pkg.checkdepends(),
133+
&providers,
134+
&states,
135+
&mut every_dependency_checked,
136+
);
136137
}
137138
}
138139
}
139140

140-
for pkg in db.pkgs() {
141-
if states.get(pkg.name()).unwrap().get() == State::Remove {
142-
remove.push(pkg.name());
141+
states
142+
.into_iter()
143+
.filter_map(|(pkg, state)| {
144+
if state.get() == State::Remove {
145+
Some(pkg)
146+
} else {
147+
None
148+
}
149+
})
150+
.collect()
151+
}
152+
153+
/// Marks all dependency providers that satisfy the dependencies of this package requires with [`State::Keep`]
154+
fn mark_deps_as_kept(
155+
dependencies: AlpmList<&Dep>,
156+
providers: &HashMap<PkgName, Vec<Provider>>,
157+
states: &HashMap<PkgName, Cell<State>>,
158+
every_dependency_marked: &mut bool,
159+
) {
160+
for dep in dependencies {
161+
// mark all providers that satisfy the dependency as to be kept
162+
for provider in providers
163+
.get(dep.name())
164+
.into_iter()
165+
.flatten()
166+
.filter(|provider| provider_satisfies_dependency(dep, provider))
167+
{
168+
let state = states
169+
.get(provider.pkg_name)
170+
.expect("state should have all packages");
171+
172+
// if the dependency hasn't already been recursively marked as to kept, mark it, and edit the marking loop to go through this package's dependencies, too.
173+
if state.get()
174+
!= (State::Keep {
175+
deps: DepState::AlreadyMarked,
176+
})
177+
{
178+
state.set(State::Keep {
179+
deps: DepState::NotMarkedYet,
180+
});
181+
*every_dependency_marked = false;
182+
}
143183
}
144184
}
185+
}
145186

146-
remove
187+
/// Checks if the provider of the dependency satisfies its version requirements
188+
fn provider_satisfies_dependency(dep: &Dep, provider: &Provider) -> bool {
189+
let Some(required_version) = dep.version() else {
190+
// dependency doesn't depend on any particular version
191+
return true;
192+
};
193+
194+
let Some(provided_version) = provider.ver else {
195+
// provider doesn't specify what version it provides but we depend on a particular version
196+
return false;
197+
};
198+
199+
let ver_cmp = provided_version.vercmp(required_version);
200+
let ver_requirement = dep.depmod();
201+
202+
match (ver_cmp, ver_requirement) {
203+
// Note: I don't believe this should ever be hit because a version requirement ~does~ exist
204+
// (because dep.version() is Some)
205+
(_, DepMod::Any) => true,
206+
(Ordering::Less, DepMod::Lt | DepMod::Le) => true,
207+
(Ordering::Equal, DepMod::Eq | DepMod::Le | DepMod::Ge) => true,
208+
(Ordering::Greater, DepMod::Gt | DepMod::Ge) => true,
209+
_ => false,
210+
}
147211
}

0 commit comments

Comments
 (0)