Skip to content

Commit 5d60ac4

Browse files
committed
Handle source timestamp hint conflicts and test sibling reuse
1 parent 8e62e96 commit 5d60ac4

2 files changed

Lines changed: 118 additions & 10 deletions

File tree

crates/pixi/tests/integration_rust/build_tests.rs

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ use crate::{
1212
};
1313
use pixi_test_utils::{MockRepoData, Package};
1414

15-
fn write_basic_source_package_manifest(path: &std::path::Path, version: &str, extra: &str) {
15+
fn write_source_package_manifest(path: &std::path::Path, name: &str, version: &str, extra: &str) {
1616
let source_pixi_toml = format!(
1717
r#"
1818
[package]
19-
name = "my-package"
19+
name = "{name}"
2020
version = "{version}"
2121
2222
[package.build]
@@ -27,12 +27,25 @@ backend = {{ name = "in-memory", version = "0.1.0" }}
2727
fs::write(path.join("pixi.toml"), source_pixi_toml).unwrap();
2828
}
2929

30-
fn write_basic_source_workspace_manifest(path: &std::path::Path, channels: &[&str]) {
30+
fn write_basic_source_package_manifest(path: &std::path::Path, version: &str, extra: &str) {
31+
write_source_package_manifest(path, "my-package", version, extra);
32+
}
33+
34+
fn write_source_workspace_manifest(
35+
path: &std::path::Path,
36+
channels: &[&str],
37+
source_dependencies: &[&str],
38+
) {
3139
let channels = channels
3240
.iter()
3341
.map(|c| format!(r#""{c}""#))
3442
.collect::<Vec<_>>()
3543
.join(", ");
44+
let source_dependencies = source_dependencies
45+
.iter()
46+
.map(|name| format!(r#"{name} = {{ path = "./{name}" }}"#))
47+
.collect::<Vec<_>>()
48+
.join("\n");
3649
let manifest_content = format!(
3750
r#"
3851
[workspace]
@@ -41,13 +54,17 @@ platforms = ["{}"]
4154
preview = ["pixi-build"]
4255
4356
[dependencies]
44-
my-package = {{ path = "./my-package" }}
57+
{source_dependencies}
4558
"#,
4659
Platform::current()
4760
);
4861
fs::write(path, manifest_content).unwrap();
4962
}
5063

64+
fn write_basic_source_workspace_manifest(path: &std::path::Path, channels: &[&str]) {
65+
write_source_workspace_manifest(path, channels, &["my-package"]);
66+
}
67+
5168
fn write_source_workspace_manifest_with_binary_dependencies(
5269
path: &std::path::Path,
5370
channels: &[&str],
@@ -1515,3 +1532,67 @@ async fn test_source_timestamp_changes_for_explicit_update() {
15151532
"source timestamp should change when the package is explicitly updated"
15161533
);
15171534
}
1535+
1536+
#[tokio::test]
1537+
async fn test_source_timestamp_reuse_survives_sibling_metadata_change() {
1538+
setup_tracing();
1539+
1540+
let backend_override = BackendOverride::from_memory(PassthroughBackend::instantiator());
1541+
let pixi = PixiControl::new()
1542+
.unwrap()
1543+
.with_backend_override(backend_override);
1544+
1545+
let my_package_dir = pixi.workspace_path().join("my-package");
1546+
fs::create_dir_all(&my_package_dir).unwrap();
1547+
write_source_package_manifest(&my_package_dir, "my-package", "1.0.0", "");
1548+
1549+
let other_package_dir = pixi.workspace_path().join("other-package");
1550+
fs::create_dir_all(&other_package_dir).unwrap();
1551+
write_source_package_manifest(&other_package_dir, "other-package", "1.0.0", "");
1552+
1553+
write_source_workspace_manifest(&pixi.manifest_path(), &[], &["my-package", "other-package"]);
1554+
1555+
let initial_lock = pixi.update_lock_file().await.unwrap();
1556+
let initial_my_timestamp = initial_lock
1557+
.get_conda_source_timestamp(
1558+
consts::DEFAULT_ENVIRONMENT_NAME,
1559+
Platform::current(),
1560+
"my-package",
1561+
)
1562+
.expect("my-package should have a source timestamp");
1563+
let initial_other_timestamp = initial_lock
1564+
.get_conda_source_timestamp(
1565+
consts::DEFAULT_ENVIRONMENT_NAME,
1566+
Platform::current(),
1567+
"other-package",
1568+
)
1569+
.expect("other-package should have a source timestamp");
1570+
1571+
std::thread::sleep(Duration::from_millis(25));
1572+
write_source_package_manifest(&other_package_dir, "other-package", "1.1.0", "");
1573+
1574+
let relocked = pixi.update_lock_file().await.unwrap();
1575+
let relocked_my_timestamp = relocked
1576+
.get_conda_source_timestamp(
1577+
consts::DEFAULT_ENVIRONMENT_NAME,
1578+
Platform::current(),
1579+
"my-package",
1580+
)
1581+
.expect("my-package should still have a source timestamp");
1582+
let relocked_other_timestamp = relocked
1583+
.get_conda_source_timestamp(
1584+
consts::DEFAULT_ENVIRONMENT_NAME,
1585+
Platform::current(),
1586+
"other-package",
1587+
)
1588+
.expect("other-package should still have a source timestamp");
1589+
1590+
assert_eq!(
1591+
initial_my_timestamp, relocked_my_timestamp,
1592+
"unchanged source package should keep its timestamp even when a sibling source package changes"
1593+
);
1594+
assert_ne!(
1595+
initial_other_timestamp, relocked_other_timestamp,
1596+
"changed sibling source package should get a fresh timestamp"
1597+
);
1598+
}

crates/pixi_core/src/lock_file/update.rs

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,15 +1187,42 @@ impl<'p> UpdateContext<'p> {
11871187
.get(&(env.clone(), platform))
11881188
.cloned()
11891189
.unwrap_or_default(),
1190-
GroupedEnvironment::Group(group) => group
1191-
.environments()
1192-
.filter_map(|env| {
1190+
GroupedEnvironment::Group(group) => {
1191+
let mut merged = HashMap::new();
1192+
let mut conflicts = std::collections::HashSet::new();
1193+
1194+
for timestamps in group.environments().filter_map(|env| {
11931195
self.outdated_envs
11941196
.validated_source_timestamps
11951197
.get(&(env, platform))
1196-
})
1197-
.flat_map(|timestamps| timestamps.iter().map(|(key, value)| (key.clone(), *value)))
1198-
.collect(),
1198+
}) {
1199+
for (key, value) in timestamps {
1200+
if conflicts.contains(key) {
1201+
continue;
1202+
}
1203+
1204+
match merged.entry(key.clone()) {
1205+
std::collections::hash_map::Entry::Vacant(entry) => {
1206+
entry.insert(*value);
1207+
}
1208+
std::collections::hash_map::Entry::Occupied(entry)
1209+
if entry.get() == value => {}
1210+
std::collections::hash_map::Entry::Occupied(_) => {
1211+
tracing::debug!(
1212+
package = %key.package.as_source(),
1213+
variants = ?key.variants,
1214+
platform = %platform,
1215+
"ignoring conflicting source timestamp hints across solve-group environments"
1216+
);
1217+
conflicts.insert(key.clone());
1218+
merged.remove(key);
1219+
}
1220+
}
1221+
}
1222+
}
1223+
1224+
merged
1225+
}
11991226
}
12001227
}
12011228

0 commit comments

Comments
 (0)