Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add forward compatibility error #389

Merged
Merged
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
59 changes: 7 additions & 52 deletions crates/rattler_lock/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@
//! It is modeled on the definitions found at: [conda-lock models](https://github.com/conda/conda-lock/blob/main/conda_lock/lockfile/models.py)
//! Most names were kept the same as in the models file. So you can refer to those exactly.
//! However, some types were added to enforce a bit more type safety.
use ::serde::{Deserialize, Serialize};
use indexmap::IndexMap;
use rattler_conda_types::{MatchSpec, PackageName};
use rattler_conda_types::{NoArchType, ParsePlatformError, Platform, RepoDataRecord};
use serde::{Deserialize, Serialize, Serializer};
use rattler_conda_types::{NoArchType, Platform, RepoDataRecord};
use serde_with::serde_as;
use std::cmp::Ordering;
use std::{collections::BTreeMap, fs::File, io::Read, path::Path, str::FromStr};
use std::{collections::BTreeMap, io::Read, path::Path, str::FromStr};
use url::Url;

pub mod builder;
mod conda;
mod content_hash;
mod hash;
mod pip;
mod serde;
mod utils;

use crate::conda::ConversionError;
Expand All @@ -26,7 +26,7 @@ pub use pip::PipLockedDependency;
/// Represents the conda-lock file
/// Contains the metadata regarding the lock files
/// also the locked packages
#[derive(Deserialize, Clone, Debug)]
#[derive(Clone, Debug)]
pub struct CondaLock {
/// Metadata for the lock file
pub metadata: LockMeta,
Expand All @@ -38,10 +38,6 @@ pub struct CondaLock {
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
pub enum ParseCondaLockError {
/// The platform could not be parsed
#[error(transparent)]
InvalidPlatform(#[from] ParsePlatformError),

#[error(transparent)]
IoError(#[from] std::io::Error),

Expand Down Expand Up @@ -77,7 +73,8 @@ impl CondaLock {

/// Parses an conda-lock file from a file.
pub fn from_path(path: &Path) -> Result<Self, ParseCondaLockError> {
Self::from_reader(File::open(path)?)
let source = std::fs::read_to_string(path)?;
Self::from_str(&source)
}

/// Writes the conda lock to a file
Expand Down Expand Up @@ -246,48 +243,6 @@ impl From<&str> for Channel {
}
}

impl Serialize for CondaLock {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
struct Raw<'a> {
metadata: &'a LockMeta,
package: Vec<&'a LockedDependency>,
version: u32,
}

// Sort all packages in alphabetical order. We choose to use alphabetic order instead of
// topological because the alphabetic order will create smaller diffs when packages change
// or are added.
// See: https://github.com/conda/conda-lock/issues/491
let mut sorted_deps = self.package.iter().collect::<Vec<_>>();
sorted_deps.sort_by(|&a, &b| {
a.name
.cmp(&b.name)
.then_with(|| a.platform.cmp(&b.platform))
.then_with(|| a.version.cmp(&b.version))
.then_with(|| match (&a.kind, &b.kind) {
(LockedDependencyKind::Conda(a), LockedDependencyKind::Conda(b)) => {
a.build.cmp(&b.build)
}
(LockedDependencyKind::Pip(_), LockedDependencyKind::Pip(_)) => Ordering::Equal,
(LockedDependencyKind::Pip(_), _) => Ordering::Less,
(_, LockedDependencyKind::Pip(_)) => Ordering::Greater,
})
});

let raw = Raw {
metadata: &self.metadata,
package: sorted_deps,
version: 1,
};

raw.serialize(serializer)
}
}

impl CondaLock {
/// Returns all the packages in the lock-file for a certain platform.
pub fn get_packages_by_platform(
Expand Down
116 changes: 116 additions & 0 deletions crates/rattler_lock/src/serde.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use super::{CondaLock, LockMeta, LockedDependency, LockedDependencyKind};
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::cmp::Ordering;

const FILE_VERSION: u32 = 2;

/// A helper struct to deserialize the version field of the lock file and provide potential errors
/// in-line.
#[derive(Serialize)]
#[serde(transparent)]
struct Version(u32);

impl Default for Version {
fn default() -> Self {
Self(FILE_VERSION)
}
}

impl<'de> Deserialize<'de> for Version {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let version = u32::deserialize(deserializer)?;

if version > FILE_VERSION {
return Err(D::Error::custom(format!(
"found newer file format version {}, but only up to including version {} is supported",
version, FILE_VERSION
)));
}

Ok(Self(version))
}
}

impl<'de> Deserialize<'de> for CondaLock {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[allow(dead_code)]
#[derive(Deserialize)]
struct Raw {
version: Version,
metadata: LockMeta,
package: Vec<LockedDependency>,
}

let raw = Raw::deserialize(deserializer)?;
Ok(Self {
metadata: raw.metadata,
package: raw.package,
})
}
}

impl Serialize for CondaLock {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
struct Raw<'a> {
version: Version,
metadata: &'a LockMeta,
package: Vec<&'a LockedDependency>,
}

// Sort all packages in alphabetical order. We choose to use alphabetic order instead of
// topological because the alphabetic order will create smaller diffs when packages change
// or are added.
// See: https://github.com/conda/conda-lock/issues/491
let mut sorted_deps = self.package.iter().collect::<Vec<_>>();
sorted_deps.sort_by(|&a, &b| {
a.name
.cmp(&b.name)
.then_with(|| a.platform.cmp(&b.platform))
.then_with(|| a.version.cmp(&b.version))
.then_with(|| match (&a.kind, &b.kind) {
(LockedDependencyKind::Conda(a), LockedDependencyKind::Conda(b)) => {
a.build.cmp(&b.build)
}
(LockedDependencyKind::Pip(_), LockedDependencyKind::Pip(_)) => Ordering::Equal,
(LockedDependencyKind::Pip(_), _) => Ordering::Less,
(_, LockedDependencyKind::Pip(_)) => Ordering::Greater,
})
});

let raw = Raw {
version: Default::default(),
metadata: &self.metadata,
package: sorted_deps,
};

raw.serialize(serializer)
}
}

#[cfg(test)]
mod test {
use super::*;
use std::path::Path;

#[test]
fn read_conda_lock() {
let err = CondaLock::from_path(
&Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../test-data/conda-lock/forward-compatible-lock.yml"),
)
.unwrap_err();

insta::assert_snapshot!(format!("{}", err), @"found newer file format version 1000, but only up to including version 2 is supported");
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
---
source: crates/rattler_lock/src/lib.rs
assertion_line: 606
assertion_line: 300
expression: conda_lock
---
version: 2
metadata:
content_hash:
linux-64: db07b15e6c03c3be1c2b06b6b6c916d625f68bba2d5911b013b31970eaa2e5c3
Expand Down Expand Up @@ -12281,5 +12282,4 @@ package:
platform: osx-arm64
url: "https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.2-hf913c23_6.conda"
version: 1.5.2
version: 1

Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
---
source: crates/rattler_lock/src/lib.rs
assertion_line: 616
assertion_line: 310
expression: conda_lock
---
version: 2
metadata:
content_hash:
linux-64: 63fe6f5b3868946659fb21a6f0a5871c672c00ce5846fe67e044d5d3fa7c2b3b
Expand Down Expand Up @@ -1331,5 +1332,4 @@ package:
platform: win-64
url: "https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2"
version: 5.2.6
version: 1

11 changes: 11 additions & 0 deletions test-data/conda-lock/forward-compatible-lock.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
metadata:
content_hash:
win-64: e90c2ee71ad70fc0a1c8302029533a7d1498f2bffcd0eaa8d2934700e775dc1d
channels:
- url: https://conda.anaconda.org/conda-forge/
used_env_vars: []
platforms:
- win-64
sources: []
package:
version: 1000
Loading