Skip to content

Commit f02bcd3

Browse files
authored
fix: roundtrip of arch/platform in lock files (#1124)
1 parent 6e13ecb commit f02bcd3

File tree

6 files changed

+92
-51
lines changed

6 files changed

+92
-51
lines changed

crates/rattler_conda_types/src/repo_data/mod.rs

+21-31
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use crate::{
2626
serde::{sort_map_alphabetically, DeserializeFromStrUnchecked},
2727
UrlWithTrailingSlash,
2828
},
29-
Channel, MatchSpec, Matches, NoArchType, PackageName, PackageUrl, ParseMatchSpecError,
29+
Arch, Channel, MatchSpec, Matches, NoArchType, PackageName, PackageUrl, ParseMatchSpecError,
3030
ParseStrictness, Platform, RepoDataRecord, VersionWithSource,
3131
};
3232

@@ -93,7 +93,10 @@ pub trait RecordFromPath {
9393
#[sorted]
9494
#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone, Hash)]
9595
pub struct PackageRecord {
96-
/// Optionally the architecture the package supports
96+
/// Optionally the architecture the package supports. This is almost
97+
/// always the second part of the `subdir` string. Except for `64` which
98+
/// maps to `x86_64` and `32` which maps to `x86`. This will be `None` if
99+
/// the package is `noarch`.
97100
pub arch: Option<String>,
98101

99102
/// The build string of the package
@@ -152,8 +155,11 @@ pub struct PackageRecord {
152155
#[serde(skip_serializing_if = "NoArchType::is_none")]
153156
pub noarch: NoArchType,
154157

155-
/// Optionally the platform the package supports
156-
pub platform: Option<String>, // Note that this does not match the [`Platform`] enum..
158+
/// Optionally the platform the package supports.
159+
/// Note that this does not match the [`Platform`] enum, but is only the first
160+
/// part of the platform (e.g. `linux`, `osx`, `win`, ...).
161+
/// The `subdir` field contains the `Platform` enum.
162+
pub platform: Option<String>,
157163

158164
/// Package identifiers of packages that are equivalent to this package but
159165
/// from other ecosystems.
@@ -457,33 +463,17 @@ fn determine_subdir(
457463
let platform = platform.ok_or(ConvertSubdirError::PlatformEmpty)?;
458464
let arch = arch.ok_or(ConvertSubdirError::ArchEmpty)?;
459465

460-
let plat = match platform.as_ref() {
461-
"linux" => match arch.as_ref() {
462-
"x86" => Ok(Platform::Linux32),
463-
"x86_64" => Ok(Platform::Linux64),
464-
"aarch64" => Ok(Platform::LinuxAarch64),
465-
"armv61" => Ok(Platform::LinuxArmV6l),
466-
"armv71" => Ok(Platform::LinuxArmV7l),
467-
"ppc64le" => Ok(Platform::LinuxPpc64le),
468-
"ppc64" => Ok(Platform::LinuxPpc64),
469-
"s390x" => Ok(Platform::LinuxS390X),
470-
_ => Err(ConvertSubdirError::NoKnownCombination { platform, arch }),
471-
},
472-
"osx" => match arch.as_ref() {
473-
"x86_64" => Ok(Platform::Osx64),
474-
"arm64" => Ok(Platform::OsxArm64),
475-
_ => Err(ConvertSubdirError::NoKnownCombination { platform, arch }),
476-
},
477-
"windows" => match arch.as_ref() {
478-
"x86" => Ok(Platform::Win32),
479-
"x86_64" => Ok(Platform::Win64),
480-
"arm64" => Ok(Platform::WinArm64),
481-
_ => Err(ConvertSubdirError::NoKnownCombination { platform, arch }),
482-
},
483-
_ => Err(ConvertSubdirError::NoKnownCombination { platform, arch }),
484-
}?;
485-
// Convert back to Platform string which should correspond to known subdirs
486-
Ok(plat.to_string())
466+
match arch.parse::<Arch>() {
467+
Ok(arch) => {
468+
let arch_str = match arch {
469+
Arch::X86 => "32",
470+
Arch::X86_64 => "64",
471+
_ => arch.as_str(),
472+
};
473+
Ok(format!("{}-{}", platform, arch_str))
474+
}
475+
Err(_) => Err(ConvertSubdirError::NoKnownCombination { platform, arch }),
476+
}
487477
}
488478

489479
impl PackageRecord {

crates/rattler_lock/Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@ typed-path = { workspace = true }
3131

3232
[dev-dependencies]
3333
insta = { workspace = true, features = ["yaml"] }
34+
serde_json = { workspace = true }
3435
similar-asserts = { workspace = true }
35-
rstest = { workspace = true }
36+
rstest = { workspace = true }

crates/rattler_lock/src/lib.rs

+51-1
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ mod test {
527527
str::FromStr,
528528
};
529529

530-
use rattler_conda_types::Platform;
530+
use rattler_conda_types::{Platform, RepoDataRecord};
531531
use rstest::*;
532532

533533
use super::{LockFile, DEFAULT_ENVIRONMENT_NAME};
@@ -661,4 +661,54 @@ mod test {
661661
let conda_lock = LockFile::from_path(&path).unwrap();
662662
assert!(!conda_lock.is_empty());
663663
}
664+
665+
#[test]
666+
fn solve_roundtrip() {
667+
// load repodata from JSON
668+
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
669+
.join("../../test-data/repodata-records/_libgcc_mutex-0.1-conda_forge.json");
670+
let content = std::fs::read_to_string(&path).unwrap();
671+
let repodata_record: RepoDataRecord = serde_json::from_str(&content).unwrap();
672+
673+
// check that the repodata record is as expected
674+
assert_eq!(repodata_record.package_record.arch, None);
675+
assert_eq!(repodata_record.package_record.platform, None);
676+
677+
// create a lockfile with the repodata record
678+
let lock_file = LockFile::builder()
679+
.with_conda_package(
680+
DEFAULT_ENVIRONMENT_NAME,
681+
Platform::Linux64,
682+
repodata_record.clone().into(),
683+
)
684+
.finish();
685+
686+
// serialize the lockfile
687+
let rendered_lock_file = lock_file.render_to_string().unwrap();
688+
689+
// parse the lockfile
690+
let parsed_lock_file = LockFile::from_str(&rendered_lock_file).unwrap();
691+
// get repodata record from parsed lockfile
692+
let repodata_records = parsed_lock_file
693+
.environment(DEFAULT_ENVIRONMENT_NAME)
694+
.unwrap()
695+
.conda_repodata_records(Platform::Linux64)
696+
.unwrap()
697+
.unwrap();
698+
699+
// These are not equal because the one from `repodata_records[0]` contains arch and platform.
700+
let repodata_record_two = repodata_records[0].clone();
701+
assert_eq!(
702+
repodata_record_two.package_record.arch,
703+
Some("x86_64".to_string())
704+
);
705+
assert_eq!(
706+
repodata_record_two.package_record.platform,
707+
Some("linux".to_string())
708+
);
709+
710+
// But if we render it again, the lockfile should look the same at least
711+
let rerendered_lock_file_two = parsed_lock_file.render_to_string().unwrap();
712+
assert_eq!(rendered_lock_file, rerendered_lock_file_two);
713+
}
664714
}

crates/rattler_lock/src/parse/models/v6/conda_package_data.rs

+2-16
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,6 @@ pub(crate) struct CondaPackageDataModel<'a> {
8080
pub extra_depends: Cow<'a, BTreeMap<String, Vec<String>>>,
8181

8282
// Additional properties (in semi alphabetic order but grouped by commonality)
83-
#[serde(default, skip_serializing_if = "Option::is_none")]
84-
pub arch: Option<Cow<'a, Option<String>>>,
85-
#[serde(default, skip_serializing_if = "Option::is_none")]
86-
pub platform: Option<Cow<'a, Option<String>>>,
87-
8883
#[serde(default, skip_serializing_if = "Option::is_none")]
8984
pub channel: Option<Cow<'a, Option<Url>>>,
9085

@@ -175,8 +170,8 @@ impl<'a> TryFrom<CondaPackageDataModel<'a>> for CondaPackageData {
175170
.or(derived.name)
176171
.ok_or_else(|| ConversionError::Missing("name".to_string()))?,
177172
noarch,
178-
arch: value.arch.map_or(derived_arch, Cow::into_owned),
179-
platform: value.platform.map_or(derived_platform, Cow::into_owned),
173+
arch: derived_arch,
174+
platform: derived_platform,
180175
purls: value.purls.into_owned(),
181176
sha256: value.sha256,
182177
size: value.size.into_owned(),
@@ -241,13 +236,6 @@ impl<'a> From<&'a CondaPackageData> for CondaPackageDataModel<'a> {
241236
derived.subdir.as_deref().unwrap_or(&package_record.subdir),
242237
derived.build.as_deref().unwrap_or(&package_record.build),
243238
);
244-
let (derived_arch, derived_platform) = derived_fields::derive_arch_and_platform(
245-
derived.subdir.as_deref().unwrap_or(&package_record.subdir),
246-
);
247-
248-
// Polyfill the arch and platform values if they are not present.
249-
let arch = package_record.arch.clone().or(derived_arch);
250-
let platform = package_record.platform.clone().or(derived_platform);
251239

252240
let channel = value.as_binary().and_then(|binary| binary.channel.as_ref());
253241
let file_name = value.as_binary().map(|binary| binary.file_name.as_str());
@@ -282,8 +270,6 @@ impl<'a> From<&'a CondaPackageData> for CondaPackageDataModel<'a> {
282270
depends: Cow::Borrowed(&package_record.depends),
283271
constrains: Cow::Borrowed(&package_record.constrains),
284272
extra_depends: Cow::Borrowed(&package_record.extra_depends),
285-
arch: (package_record.arch != arch).then_some(Cow::Owned(arch)),
286-
platform: (package_record.platform != platform).then_some(Cow::Owned(platform)),
287273
md5: package_record.md5,
288274
legacy_bz2_md5: package_record.legacy_bz2_md5,
289275
sha256: package_record.sha256,

crates/rattler_lock/src/snapshots/rattler_lock__builder__test__merge_records_and_purls.snap

-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ environments:
1616
- conda: https://prefix.dev/example/linux-64/foobar-1.0.0-build.tar.bz2
1717
packages:
1818
- conda: https://prefix.dev/example/linux-64/foobar-1.0.0-build.tar.bz2
19-
arch: x86_64
20-
platform: linux
2119
channel: null
2220
purls:
2321
- pkg:pypi/foobar@1.0.0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"build": "conda_forge",
3+
"build_number": 0,
4+
"depends": [],
5+
"license": "None",
6+
"md5": "d7c89558ba9fa0495403155b64376d81",
7+
"name": "_libgcc_mutex",
8+
"sha256": "fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726",
9+
"size": 2562,
10+
"subdir": "linux-64",
11+
"timestamp": 1578324546067,
12+
"version": "0.1",
13+
"fn": "_libgcc_mutex-0.1-conda_forge.tar.bz2",
14+
"url": "https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2",
15+
"channel": "https://conda.anaconda.org/conda-forge/"
16+
}

0 commit comments

Comments
 (0)