Skip to content

Commit ee0a61e

Browse files
committed
remove drop-bomb, move empty folder removal to post_process
1 parent 38ae450 commit ee0a61e

File tree

8 files changed

+242
-189
lines changed

8 files changed

+242
-189
lines changed

crates/rattler-bin/src/commands/create.rs

+3-5
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,8 @@ async fn execute_transaction(
353353
link_pb.enable_steady_tick(Duration::from_millis(100));
354354

355355
// Perform all transactions operations in parallel.
356-
stream::iter(transaction.operations)
356+
let operations = transaction.operations.clone();
357+
stream::iter(operations)
357358
.map(Ok)
358359
.try_for_each_concurrent(50, |op| {
359360
let target_prefix = target_prefix.clone();
@@ -380,11 +381,8 @@ async fn execute_transaction(
380381
.await?;
381382

382383
// Perform any post processing that is required.
383-
let prefix_records = find_installed_packages(&target_prefix, 100)
384-
.await
385-
.context("failed to determine currently installed packages")?;
386384
install_driver
387-
.post_process(&prefix_records, &target_prefix)
385+
.post_process(&transaction, &target_prefix)
388386
.expect("bla");
389387

390388
Ok(())

crates/rattler/src/install/clobber_registry.rs

+4-21
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,16 @@ use std::{
66
path::{Path, PathBuf},
77
};
88

9-
use drop_bomb::DropBomb;
109
use rattler_conda_types::{package::PathsJson, PackageName, PrefixRecord};
1110

1211
/// A registry for clobbering files
1312
/// The registry keeps track of all files that are installed by a package and
1413
/// can be used to rename files that are already installed by another package.
15-
#[derive(Debug)]
14+
#[derive(Debug, Default, Clone)]
1615
pub struct ClobberRegistry {
1716
paths_registry: HashMap<PathBuf, usize>,
1817
clobbers: HashMap<PathBuf, Vec<usize>>,
1918
package_names: Vec<PackageName>,
20-
drop_bomb: DropBomb,
21-
}
22-
23-
impl Default for ClobberRegistry {
24-
fn default() -> Self {
25-
Self {
26-
paths_registry: HashMap::new(),
27-
clobbers: HashMap::new(),
28-
package_names: Vec::new(),
29-
drop_bomb: DropBomb::new(
30-
"did not call post_process on InstallDriver / ClobberRegistry",
31-
),
32-
}
33-
}
3419
}
3520

3621
static CLOBBER_TEMPLATE: &str = "__clobber-from-";
@@ -147,7 +132,6 @@ impl ClobberRegistry {
147132
sorted_prefix_records: &[&PrefixRecord],
148133
target_prefix: &Path,
149134
) -> Result<(), std::io::Error> {
150-
self.drop_bomb.defuse();
151135
let sorted_names = sorted_prefix_records
152136
.iter()
153137
.map(|p| p.repodata_record.package_record.name.clone())
@@ -426,21 +410,20 @@ mod tests {
426410
install_driver: &InstallDriver,
427411
install_options: &InstallOptions,
428412
) {
429-
for op in transaction.operations {
413+
for op in &transaction.operations {
430414
execute_operation(
431415
target_prefix,
432416
download_client,
433417
package_cache,
434418
install_driver,
435-
op,
419+
op.clone(),
436420
install_options,
437421
)
438422
.await;
439423
}
440424

441-
let prefix_records = PrefixRecord::collect_from_prefix(target_prefix).unwrap();
442425
install_driver
443-
.post_process(&prefix_records, target_prefix)
426+
.post_process(&transaction, target_prefix)
444427
.unwrap();
445428
}
446429

crates/rattler/src/install/driver.rs

+74-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
use super::clobber_registry::ClobberRegistry;
2-
use super::InstallError;
2+
use super::unlink::{recursively_remove_empty_directories, UnlinkError};
3+
use super::{InstallError, Transaction};
34
use futures::stream::FuturesUnordered;
45
use futures::{FutureExt, StreamExt};
5-
use rattler_conda_types::{PackageRecord, PrefixRecord};
6+
use indexmap::IndexSet;
7+
use itertools::Itertools;
8+
use rattler_conda_types::prefix_record::PathType;
9+
use rattler_conda_types::{PackageRecord, PrefixRecord, RepoDataRecord};
10+
use std::collections::HashSet;
611
use std::future::pending;
712
use std::path::Path;
813
use std::sync::MutexGuard;
@@ -152,18 +157,84 @@ impl InstallDriver {
152157
/// and will also execute any `post-link.sh/bat` scripts
153158
pub fn post_process(
154159
&self,
155-
prefix_records: &[PrefixRecord],
160+
transaction: &Transaction<PrefixRecord, RepoDataRecord>,
156161
target_prefix: &Path,
157162
) -> Result<(), InstallError> {
163+
let prefix_records = PrefixRecord::collect_from_prefix(target_prefix)
164+
.map_err(InstallError::PostProcessFailed)?;
165+
158166
let required_packages =
159167
PackageRecord::sort_topologically(prefix_records.iter().collect::<Vec<_>>());
160168

169+
self.remove_empty_directories(transaction, &prefix_records, target_prefix)
170+
.unwrap_or_else(|e| {
171+
tracing::warn!("Failed to remove empty directories: {} (ignored)", e);
172+
});
173+
161174
self.clobber_registry()
162175
.unclobber(&required_packages, target_prefix)
163176
.map_err(InstallError::PostProcessFailed)?;
164177

165178
Ok(())
166179
}
180+
181+
/// Remove all empty directories that are not part of the new prefix records.
182+
pub fn remove_empty_directories(
183+
&self,
184+
transaction: &Transaction<PrefixRecord, RepoDataRecord>,
185+
new_prefix_records: &[PrefixRecord],
186+
target_prefix: &Path,
187+
) -> Result<(), UnlinkError> {
188+
let mut keep_directories = HashSet::new();
189+
190+
// find all forced directories in the prefix records
191+
for record in new_prefix_records {
192+
for paths in record.paths_data.paths.iter() {
193+
if paths.path_type == PathType::Directory {
194+
let path = target_prefix.join(&paths.relative_path);
195+
keep_directories.insert(path);
196+
}
197+
}
198+
}
199+
200+
// find all removed directories
201+
for record in transaction.removed_packages() {
202+
let mut removed_directories = HashSet::new();
203+
204+
for paths in record.paths_data.paths.iter() {
205+
if paths.path_type != PathType::Directory {
206+
if let Some(parent) = paths.relative_path.parent() {
207+
removed_directories.insert(parent);
208+
}
209+
}
210+
}
211+
212+
let is_python_noarch = record.repodata_record.package_record.noarch.is_python();
213+
214+
// Sort the directories by length, so that we delete the deepest directories first.
215+
let mut directories: IndexSet<_> = removed_directories.into_iter().sorted().collect();
216+
217+
while let Some(directory) = directories.pop() {
218+
let directory_path = target_prefix.join(directory);
219+
let removed_until = recursively_remove_empty_directories(
220+
&directory_path,
221+
target_prefix,
222+
is_python_noarch,
223+
&keep_directories,
224+
)?;
225+
226+
// The directory is not empty which means our parent directory is also not empty,
227+
// recursively remove the parent directory from the set as well.
228+
while let Some(parent) = removed_until.parent() {
229+
if !directories.shift_remove(parent) {
230+
break;
231+
}
232+
}
233+
}
234+
}
235+
236+
Ok(())
237+
}
167238
}
168239

169240
impl Drop for InstallDriverInner {

crates/rattler/src/install/mod.rs

-10
Original file line numberDiff line numberDiff line change
@@ -722,12 +722,6 @@ mod test {
722722
})
723723
.await;
724724

725-
// test doesn't write conda-meta, so we ignore the post processing
726-
let prefix_records = vec![];
727-
install_driver
728-
.post_process(&prefix_records, target_dir.path())
729-
.unwrap();
730-
731725
// Run the python command and validate the version it outputs
732726
let python_path = if Platform::current().is_windows() {
733727
"python.exe"
@@ -771,10 +765,6 @@ mod test {
771765
.await
772766
.unwrap();
773767

774-
install_driver
775-
.post_process(&vec![], environment_dir.path())
776-
.unwrap();
777-
778768
insta::assert_yaml_snapshot!(paths);
779769
}
780770
}

crates/rattler/src/install/transaction.rs

+19-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub enum TransactionError {
1313
}
1414

1515
/// Describes an operation to perform
16-
#[derive(Debug)]
16+
#[derive(Debug, Clone, PartialEq, Eq)]
1717
pub enum TransactionOperation<Old, New> {
1818
/// The given package record should be installed
1919
Install(New),
@@ -77,6 +77,24 @@ pub struct Transaction<Old, New> {
7777
pub platform: Platform,
7878
}
7979

80+
impl<Old, New> Transaction<Old, New> {
81+
/// Return an iterator over the prefix records of all packages that are going to be removed.
82+
pub fn removed_packages(&self) -> impl Iterator<Item = &Old> + '_ {
83+
self.operations
84+
.iter()
85+
.filter_map(TransactionOperation::record_to_remove)
86+
}
87+
}
88+
89+
impl<Old: AsRef<New>, New> Transaction<Old, New> {
90+
/// Return an iterator over the prefix records of all packages that are going to be installed.
91+
pub fn installed_packages(&self) -> impl Iterator<Item = &New> + '_ {
92+
self.operations
93+
.iter()
94+
.filter_map(TransactionOperation::record_to_install)
95+
}
96+
}
97+
8098
impl<Old: AsRef<PackageRecord>, New: AsRef<PackageRecord>> Transaction<Old, New> {
8199
/// Constructs a [`Transaction`] by taking the current situation and diffing that against the
82100
/// desired situation.

0 commit comments

Comments
 (0)