From 589afa44fd601c8e9221fdf2617f6fb699ea79e7 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Wed, 14 Jan 2026 18:24:07 -0800 Subject: [PATCH] Fix Python package version detection after upgrade in conda history --- crates/pet-conda/src/package.rs | 116 ++++++++++-------- crates/pet-conda/tests/package_test.rs | 30 +++++ .../conda-meta/history | 21 ++++ .../conda-meta/python-3.9.21-h789abc_0.json | 13 ++ 4 files changed, 130 insertions(+), 50 deletions(-) create mode 100644 crates/pet-conda/tests/unix/conda_env_with_python_upgrade/conda-meta/history create mode 100644 crates/pet-conda/tests/unix/conda_env_with_python_upgrade/conda-meta/python-3.9.21-h789abc_0.json diff --git a/crates/pet-conda/src/package.rs b/crates/pet-conda/src/package.rs index 2aa86456..7a043b83 100644 --- a/crates/pet-conda/src/package.rs +++ b/crates/pet-conda/src/package.rs @@ -93,59 +93,75 @@ fn get_conda_package_info_from_history(path: &Path, name: &Package) -> Option = 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 = 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::(&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 = 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::(&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 + ); + } } } } diff --git a/crates/pet-conda/tests/package_test.rs b/crates/pet-conda/tests/package_test.rs index b2dd3d53..f4169734 100644 --- a/crates/pet-conda/tests/package_test.rs +++ b/crates/pet-conda/tests/package_test.rs @@ -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" + ]) + ); +} diff --git a/crates/pet-conda/tests/unix/conda_env_with_python_upgrade/conda-meta/history b/crates/pet-conda/tests/unix/conda_env_with_python_upgrade/conda-meta/history new file mode 100644 index 00000000..36d3198b --- /dev/null +++ b/crates/pet-conda/tests/unix/conda_env_with_python_upgrade/conda-meta/history @@ -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'] diff --git a/crates/pet-conda/tests/unix/conda_env_with_python_upgrade/conda-meta/python-3.9.21-h789abc_0.json b/crates/pet-conda/tests/unix/conda_env_with_python_upgrade/conda-meta/python-3.9.21-h789abc_0.json new file mode 100644 index 00000000..756b1b98 --- /dev/null +++ b/crates/pet-conda/tests/unix/conda_env_with_python_upgrade/conda-meta/python-3.9.21-h789abc_0.json @@ -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" +}