Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 66 additions & 50 deletions crates/pet-conda/src/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,59 +93,75 @@ fn get_conda_package_info_from_history(path: &Path, name: &Package) -> Option<Co
let package_entry = format!(":{}-", name.to_name());

let history_contents = fs::read_to_string(history).ok()?;
for line in history_contents

// Filter to only include lines that:
// 1. Start with '+' (installed packages, not '-' for removed packages)
// 2. Contain the package entry (e.g., ":python-")
//
// We need the LAST matching entry because conda appends to history chronologically.
// When a package is upgraded, the old version is removed (-) and new version installed (+).
// The last '+' entry represents the currently installed version.
//
// Sample history for Python upgrade from 3.9.18 to 3.9.21:
// +defaults::python-3.9.18-h123456_0 <- initial install
// ...
// -defaults::python-3.9.18-h123456_0 <- removed during upgrade
// +defaults::python-3.9.21-h789abc_0 <- current version (we want this)
let matching_lines: Vec<&str> = history_contents
.lines()
.filter(|l| l.contains(&package_entry))
{
// Sample entry in the history file
// +conda-forge/osx-arm64::psutil-5.9.8-py312he37b823_0
// +conda-forge/osx-arm64::python-3.12.2-hdf0ec26_0_cpython
// +conda-forge/osx-arm64::python_abi-3.12-4_cp312
let regex = get_package_version_history_regex(name);
if let Some(captures) = regex.captures(line) {
if let Some(version) = captures.get(1) {
if let Some(hash) = captures.get(2) {
let package_path = format!(
"{}-{}-{}.json",
name.to_name(),
version.as_str(),
hash.as_str()
);
let package_path = path.join(package_path);
let mut arch: Option<Architecture> = None;
// Sample contents
// {
// "build": "h966fe2a_2",
// "build_number": 2,
// "channel": "https://repo.anaconda.com/pkgs/main/win-64",
// "constrains": [],
// }
// 32bit channel is https://repo.anaconda.com/pkgs/main/win-32/
// 64bit channel is "channel": "https://repo.anaconda.com/pkgs/main/osx-arm64",
if let Ok(contents) = read_to_string(&package_path) {
if let Ok(js) = serde_json::from_str::<CondaMetaPackageStructure>(&contents)
{
if let Some(channel) = js.channel {
if channel.ends_with("64") {
arch = Some(Architecture::X64);
} else if channel.ends_with("32") {
arch = Some(Architecture::X86);
}
}
if let Some(version) = js.version {
return Some(CondaPackageInfo {
package: name.clone(),
path: package_path,
version,
arch,
});
} else {
warn!(
"Unable to find version for package {} in {:?}",
name, package_path
);
.filter(|l| l.starts_with('+') && l.contains(&package_entry))
.collect();

// Get the last matching line (most recent installation)
let line = matching_lines.last()?;

// Sample entry in the history file
// +conda-forge/osx-arm64::psutil-5.9.8-py312he37b823_0
// +conda-forge/osx-arm64::python-3.12.2-hdf0ec26_0_cpython
// +conda-forge/osx-arm64::python_abi-3.12-4_cp312
let regex = get_package_version_history_regex(name);
if let Some(captures) = regex.captures(line) {
if let Some(version) = captures.get(1) {
if let Some(hash) = captures.get(2) {
let package_path = format!(
"{}-{}-{}.json",
name.to_name(),
version.as_str(),
hash.as_str()
);
let package_path = path.join(package_path);
let mut arch: Option<Architecture> = None;
// Sample contents
// {
// "build": "h966fe2a_2",
// "build_number": 2,
// "channel": "https://repo.anaconda.com/pkgs/main/win-64",
// "constrains": [],
// }
// 32bit channel is https://repo.anaconda.com/pkgs/main/win-32/
// 64bit channel is "channel": "https://repo.anaconda.com/pkgs/main/osx-arm64",
if let Ok(contents) = read_to_string(&package_path) {
if let Ok(js) = serde_json::from_str::<CondaMetaPackageStructure>(&contents) {
if let Some(channel) = js.channel {
if channel.ends_with("64") {
arch = Some(Architecture::X64);
} else if channel.ends_with("32") {
arch = Some(Architecture::X86);
}
}
if let Some(version) = js.version {
return Some(CondaPackageInfo {
package: name.clone(),
path: package_path,
version,
arch,
});
} else {
warn!(
"Unable to find version for package {} in {:?}",
name, package_path
);
}
}
}
}
Expand Down
30 changes: 30 additions & 0 deletions crates/pet-conda/tests/package_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,33 @@ fn get_python_package_info_without_history() {
])
);
}

/// Test that when Python is upgraded, we get the current (last installed) version,
/// not the original (first installed) version.
/// This is a regression test for https://github.com/microsoft/python-environment-tools/issues/239
///
/// The history file contains:
/// +defaults::python-3.9.18-h1a28f6b_0 (initial install)
/// -defaults::python-3.9.18-h1a28f6b_0 (removed during upgrade)
/// +defaults::python-3.9.21-h789abc_0 (current version)
///
/// We should detect version 3.9.21, not 3.9.18.
#[cfg(unix)]
#[test]
fn get_python_package_info_after_upgrade() {
let path: PathBuf = resolve_test_path(&["unix", "conda_env_with_python_upgrade"]);
let pkg = CondaPackageInfo::from(&path, &package::Package::Python).unwrap();

assert_eq!(pkg.package, package::Package::Python);
// Should be 3.9.21 (current version), NOT 3.9.18 (original version)
assert_eq!(pkg.version, "3.9.21".to_string());
assert_eq!(
pkg.path,
resolve_test_path(&[
"unix",
"conda_env_with_python_upgrade",
"conda-meta",
"python-3.9.21-h789abc_0.json"
])
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
==> 2024-01-15 10:00:00 <==
# cmd: /home/user/miniforge3/bin/conda create -n waa python=3.9.18
+defaults::ca-certificates-2023.01.10-hca03da5_0
+defaults::openssl-1.1.1t-h1a28f6b_0
+defaults::python-3.9.18-h1a28f6b_0
+defaults::pip-22.3.1-py39hca03da5_0
+defaults::setuptools-65.6.3-py39hca03da5_0
+defaults::wheel-0.38.4-py39hca03da5_0
# update specs: ['python=3.9.18']

==> 2024-06-20 14:30:00 <==
# cmd: /home/user/miniforge3/bin/conda update python
-defaults::python-3.9.18-h1a28f6b_0
-defaults::pip-22.3.1-py39hca03da5_0
-defaults::setuptools-65.6.3-py39hca03da5_0
-defaults::wheel-0.38.4-py39hca03da5_0
+defaults::python-3.9.21-h789abc_0
+defaults::pip-23.3.1-py39hca03da5_0
+defaults::setuptools-68.0.0-py39hca03da5_0
+defaults::wheel-0.41.2-py39hca03da5_0
# update specs: ['python']
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"build": "h789abc_0",
"build_number": 0,
"channel": "https://repo.anaconda.com/pkgs/main/linux-64",
"constrains": [],
"depends": [],
"files": [],
"license": "PSF",
"name": "python",
"noarch": null,
"package_type": "conda",
"version": "3.9.21"
}
Loading