Skip to content

Commit 4fc2d38

Browse files
authored
fix: keep in mind noarch: python packages in clobber calculations (#511)
1 parent b369cb8 commit 4fc2d38

9 files changed

+197
-50
lines changed

crates/rattler/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ chrono = { workspace = true }
2323
digest = { workspace = true }
2424
dirs = { workspace = true }
2525
drop_bomb = { workspace = true }
26+
fs-err = "2.11.0"
2627
futures = { workspace = true }
2728
fxhash = { workspace = true }
2829
hex = { workspace = true }

crates/rattler/src/install/clobber_registry.rs

+117-14
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
33
use std::{
44
collections::{HashMap, HashSet},
5-
fs,
65
path::{Path, PathBuf},
76
};
87

9-
use rattler_conda_types::{package::PathsJson, PackageName, PrefixRecord};
8+
use rattler_conda_types::{
9+
package::{IndexJson, PathsEntry},
10+
PackageName, PrefixRecord,
11+
};
1012

13+
use fs_err as fs;
1114
/// A registry for clobbering files
1215
/// The registry keeps track of all files that are installed by a package and
1316
/// can be used to rename files that are already installed by another package.
@@ -88,10 +91,11 @@ impl ClobberRegistry {
8891
/// will "unclobber" the files after all packages have been installed.
8992
pub fn register_paths(
9093
&mut self,
91-
name: &PackageName,
92-
paths_json: &PathsJson,
94+
index_json: &IndexJson,
95+
computed_paths: &Vec<(PathsEntry, PathBuf)>,
9396
) -> HashMap<PathBuf, PathBuf> {
9497
let mut clobber_paths = HashMap::new();
98+
let name = &index_json.name.clone();
9599

96100
// check if we have the package name already registered
97101
let name_idx = if let Some(idx) = self.package_names.iter().position(|n| n == name) {
@@ -101,32 +105,30 @@ impl ClobberRegistry {
101105
self.package_names.len() - 1
102106
};
103107

104-
for entry in paths_json.paths.iter() {
105-
let path = entry.relative_path.clone();
106-
108+
for (_, path) in computed_paths {
107109
// if we find an entry, we have a clobbering path!
108-
if let Some(e) = self.paths_registry.get(&path) {
110+
if let Some(e) = self.paths_registry.get(path) {
109111
if e == &name_idx {
110112
// A name cannot appear twice in an environment.
111113
// We get into this case if a package is updated (removed and installed again with a new version)
112114
continue;
113115
}
114-
let new_path = Self::clobber_name(&path, &self.package_names[name_idx]);
116+
let new_path = Self::clobber_name(path, &self.package_names[name_idx]);
115117
self.clobbers
116118
.entry(path.clone())
117119
.or_insert_with(|| vec![*e])
118120
.push(name_idx);
119121

120-
clobber_paths.insert(path, new_path);
122+
// We insert the non-renamed path here
123+
clobber_paths.insert(path.clone(), new_path);
121124
} else {
122-
self.paths_registry.insert(path, name_idx);
125+
self.paths_registry.insert(path.clone(), name_idx);
123126
}
124127
}
125128

126129
clobber_paths
127130
}
128131

129-
/// Unclobber the paths after all installation steps have been completed.
130132
/// Unclobber the paths after all installation steps have been completed.
131133
pub fn unclobber(
132134
&mut self,
@@ -268,13 +270,14 @@ mod tests {
268270
use std::{
269271
fs,
270272
path::{Path, PathBuf},
273+
str::FromStr,
271274
};
272275

273276
use futures::TryFutureExt;
274277
use insta::assert_yaml_snapshot;
275278
use rand::seq::SliceRandom;
276279
use rattler_conda_types::{
277-
package::IndexJson, PackageRecord, Platform, PrefixRecord, RepoDataRecord,
280+
package::IndexJson, PackageRecord, Platform, PrefixRecord, RepoDataRecord, Version,
278281
};
279282
use rattler_digest::{Md5, Sha256};
280283
use rattler_networking::retry_policies::default_retry_policy;
@@ -283,12 +286,13 @@ mod tests {
283286

284287
use crate::{
285288
get_test_data_dir,
286-
install::{transaction, unlink_package, InstallDriver, InstallOptions},
289+
install::{transaction, unlink_package, InstallDriver, InstallOptions, PythonInfo},
287290
package_cache::PackageCache,
288291
};
289292

290293
fn get_repodata_record(filename: &str) -> RepoDataRecord {
291294
let path = fs::canonicalize(get_test_data_dir().join(filename)).unwrap();
295+
print!("{:?}", path);
292296
let index_json = read_package_file::<IndexJson>(&path).unwrap();
293297

294298
// find size and hash
@@ -458,6 +462,18 @@ mod tests {
458462
]
459463
}
460464

465+
fn test_python_noarch_operations() -> Vec<TransactionOperation<PrefixRecord, RepoDataRecord>> {
466+
let repodata_record_1 =
467+
get_repodata_record("clobber/clobber-pynoarch-1-0.1.0-pyh4616a5c_0.tar.bz2");
468+
let repodata_record_2 =
469+
get_repodata_record("clobber/clobber-pynoarch-2-0.1.0-pyh4616a5c_0.tar.bz2");
470+
471+
vec![
472+
TransactionOperation::Install(repodata_record_1),
473+
TransactionOperation::Install(repodata_record_2),
474+
]
475+
}
476+
461477
fn test_operations_nested() -> Vec<TransactionOperation<PrefixRecord, RepoDataRecord>> {
462478
let repodata_record_1 =
463479
get_repodata_record("clobber/clobber-nested-1-0.1.0-h4616a5c_0.tar.bz2");
@@ -495,6 +511,8 @@ mod tests {
495511
.collect::<Vec<_>>();
496512
println!("Files: {:?}", files);
497513
assert_eq!(files.len(), expected_files.len());
514+
println!("{:?}", files);
515+
498516
for file in files {
499517
assert!(expected_files.contains(&file.file_name().unwrap().to_string_lossy().as_ref()));
500518
}
@@ -961,6 +979,7 @@ mod tests {
961979
platform: Platform::current(),
962980
};
963981

982+
let prefix_records = PrefixRecord::collect_from_prefix(target_prefix.path()).unwrap();
964983
let install_driver = InstallDriver::new(100, Some(&prefix_records));
965984

966985
execute_transaction(
@@ -980,5 +999,89 @@ mod tests {
980999
fs::read_to_string(target_prefix.path().join("clobber.txt")).unwrap(),
9811000
"clobber-3 v2\n"
9821001
);
1002+
1003+
let update_ops = test_operations_update();
1004+
1005+
// remove one of the clobbering files
1006+
let transaction = transaction::Transaction::<PrefixRecord, RepoDataRecord> {
1007+
operations: vec![TransactionOperation::Install(update_ops[0].clone())],
1008+
python_info: None,
1009+
current_python_info: None,
1010+
platform: Platform::current(),
1011+
};
1012+
1013+
let prefix_records = PrefixRecord::collect_from_prefix(target_prefix.path()).unwrap();
1014+
let install_driver = InstallDriver::new(100, Some(&prefix_records));
1015+
1016+
execute_transaction(
1017+
transaction,
1018+
target_prefix.path(),
1019+
&reqwest_middleware::ClientWithMiddleware::from(reqwest::Client::new()),
1020+
&cache,
1021+
&install_driver,
1022+
&InstallOptions::default(),
1023+
)
1024+
.await;
1025+
1026+
assert_check_files(
1027+
target_prefix.path(),
1028+
&["clobber.txt", "clobber.txt__clobber-from-clobber-3"],
1029+
);
1030+
1031+
// content of clobber.txt
1032+
assert_eq!(
1033+
fs::read_to_string(target_prefix.path().join("clobber.txt")).unwrap(),
1034+
"clobber-1 v2\n"
1035+
);
1036+
}
1037+
1038+
#[tokio::test]
1039+
async fn test_clobber_python_noarch() {
1040+
// Create a transaction
1041+
let operations = test_python_noarch_operations();
1042+
1043+
let python_info =
1044+
PythonInfo::from_version(&Version::from_str("3.11.0").unwrap(), Platform::current())
1045+
.unwrap();
1046+
let transaction = transaction::Transaction::<PrefixRecord, RepoDataRecord> {
1047+
operations,
1048+
python_info: Some(python_info.clone()),
1049+
current_python_info: Some(python_info.clone()),
1050+
platform: Platform::current(),
1051+
};
1052+
1053+
// execute transaction
1054+
let target_prefix = tempfile::tempdir().unwrap();
1055+
1056+
let packages_dir = tempfile::tempdir().unwrap();
1057+
let cache = PackageCache::new(packages_dir.path());
1058+
1059+
let mut install_options = InstallOptions::default();
1060+
install_options.python_info = Some(python_info.clone());
1061+
1062+
execute_transaction(
1063+
transaction,
1064+
target_prefix.path(),
1065+
&reqwest_middleware::ClientWithMiddleware::from(reqwest::Client::new()),
1066+
&cache,
1067+
&InstallDriver::default(),
1068+
&install_options,
1069+
)
1070+
.await;
1071+
1072+
// check that the files are there
1073+
if cfg!(unix) {
1074+
assert_check_files(
1075+
&target_prefix
1076+
.path()
1077+
.join("lib/python3.11/site-packages/clobber"),
1078+
&["clobber.py", "clobber.py__clobber-from-clobber-pynoarch-2"],
1079+
);
1080+
} else {
1081+
assert_check_files(
1082+
&target_prefix.path().join("Lib/site-packages/clobber"),
1083+
&["clobber.py", "clobber.py__clobber-from-clobber-pynoarch-2"],
1084+
);
1085+
}
9831086
}
9841087
}

crates/rattler/src/install/driver.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,10 @@ impl InstallDriver {
173173

174174
self.clobber_registry()
175175
.unclobber(&required_packages, target_prefix)
176-
.map_err(InstallError::PostProcessFailed)?;
176+
.map_err(|e| {
177+
tracing::error!("Error unclobbering packages: {:?}", e);
178+
InstallError::PostProcessFailed(e)
179+
})?;
177180

178181
Ok(())
179182
}

crates/rattler/src/install/link.rs

+4-26
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
//! This module contains the logic to link a give file from the package cache into the target directory.
22
//! See [`link_file`] for more information.
3-
use crate::install::python::PythonInfo;
43
use memmap2::Mmap;
54
use rattler_conda_types::package::{FileMode, PathType, PathsEntry, PrefixPlaceholder};
6-
use rattler_conda_types::{NoArchType, Platform};
5+
use rattler_conda_types::Platform;
76
use rattler_digest::HashingWriter;
87
use rattler_digest::Sha256;
98
use reflink_copy::reflink;
@@ -131,47 +130,26 @@ pub struct LinkedFile {
131130
/// [`crate::install::InstallOptions::target_prefix`] for more information.
132131
#[allow(clippy::too_many_arguments)] // TODO: Fix this properly
133132
pub fn link_file(
134-
noarch_type: NoArchType,
135133
path_json_entry: &PathsEntry,
134+
destination_relative_path: PathBuf,
136135
package_dir: &Path,
137136
target_dir: &Path,
138137
target_prefix: &str,
139138
allow_symbolic_links: bool,
140139
allow_hard_links: bool,
141140
allow_ref_links: bool,
142141
target_platform: Platform,
143-
target_python: Option<&PythonInfo>,
144142
apple_codesign_behavior: AppleCodeSignBehavior,
145-
clobber_rename: Option<&PathBuf>,
146143
) -> Result<LinkedFile, LinkFileError> {
147144
let source_path = package_dir.join(&path_json_entry.relative_path);
148145

149-
// Determine the destination path
150-
let destination_relative_path = if noarch_type.is_python() {
151-
match target_python {
152-
Some(python_info) => {
153-
python_info.get_python_noarch_target_path(&path_json_entry.relative_path)
154-
}
155-
None => return Err(LinkFileError::MissingPythonInfo),
156-
}
157-
} else if let Some(clobber_rename) = clobber_rename {
158-
clobber_rename.into()
159-
} else {
160-
path_json_entry.relative_path.as_path().into()
161-
};
162-
163146
let destination_path = target_dir.join(&destination_relative_path);
164147

165148
// Ensure that all directories up to the path exist.
166149
if let Some(parent) = destination_path.parent() {
167150
std::fs::create_dir_all(parent).map_err(LinkFileError::FailedToCreateParentDirectory)?;
168151
}
169152

170-
// If the file already exists it most likely means that the file is clobbered. This means that
171-
// different packages are writing to the same file. This function simply reports back to the
172-
// caller that this is the case but there is no special handling here.
173-
let clobbered = clobber_rename.is_some();
174-
175153
// Temporary variables to store intermediate computations in. If we already computed the file
176154
// size or the sha hash we dont have to recompute them at the end of the function.
177155
let mut sha256 = None;
@@ -300,10 +278,10 @@ pub fn link_file(
300278
};
301279

302280
Ok(LinkedFile {
303-
clobbered,
281+
clobbered: false,
304282
sha256,
305283
file_size,
306-
relative_path: destination_relative_path.into_owned(),
284+
relative_path: destination_relative_path,
307285
method: link_method,
308286
})
309287
}

0 commit comments

Comments
 (0)