mirror of
https://github.com/pesde-pkg/pesde.git
synced 2025-04-04 10:50:55 +01:00
feat: add proper versioning to lockfiles
Lockfiles now store a format field which is used to migrate the them easily without brute forcing the version it's at.
This commit is contained in:
parent
b8c4f7486b
commit
412ce90e7f
3 changed files with 132 additions and 105 deletions
23
src/lib.rs
23
src/lib.rs
|
@ -232,18 +232,7 @@ impl Project {
|
|||
#[instrument(skip(self), ret(level = "trace"), level = "debug")]
|
||||
pub async fn deser_lockfile(&self) -> Result<Lockfile, errors::LockfileReadError> {
|
||||
let string = fs::read_to_string(self.package_dir().join(LOCKFILE_FILE_NAME)).await?;
|
||||
Ok(match toml::from_str(&string) {
|
||||
Ok(lockfile) => lockfile,
|
||||
Err(e) => {
|
||||
#[allow(deprecated)]
|
||||
let Ok(old_lockfile) = toml::from_str::<lockfile::old::LockfileOld>(&string) else {
|
||||
return Err(errors::LockfileReadError::Serde(e));
|
||||
};
|
||||
|
||||
#[allow(deprecated)]
|
||||
old_lockfile.to_new()
|
||||
}
|
||||
})
|
||||
lockfile::parse_lockfile(&string).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Write the lockfile
|
||||
|
@ -256,7 +245,9 @@ impl Project {
|
|||
let lockfile = format!(
|
||||
r"# This file is automatically @generated by pesde.
|
||||
# It is not intended for manual editing.
|
||||
{lockfile}"
|
||||
format = {}
|
||||
{lockfile}",
|
||||
lockfile::CURRENT_FORMAT
|
||||
);
|
||||
|
||||
fs::write(self.package_dir().join(LOCKFILE_FILE_NAME), lockfile).await?;
|
||||
|
@ -511,9 +502,9 @@ pub mod errors {
|
|||
#[error("io error reading lockfile")]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
/// An error occurred while deserializing the lockfile
|
||||
#[error("error deserializing lockfile")]
|
||||
Serde(#[from] toml::de::Error),
|
||||
/// An error occurred while parsing the lockfile
|
||||
#[error("error parsing lockfile")]
|
||||
Parse(#[from] crate::lockfile::errors::ParseLockfileError),
|
||||
}
|
||||
|
||||
/// Errors that can occur when writing the lockfile
|
||||
|
|
201
src/lockfile.rs
201
src/lockfile.rs
|
@ -3,12 +3,15 @@ use crate::{
|
|||
graph::DependencyGraph,
|
||||
manifest::{overrides::OverrideKey, target::TargetKind},
|
||||
names::PackageName,
|
||||
source::specifiers::DependencySpecifiers,
|
||||
source::{ids::PackageId, specifiers::DependencySpecifiers},
|
||||
};
|
||||
use relative_path::RelativePathBuf;
|
||||
use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::{collections::BTreeMap, fmt::Debug};
|
||||
|
||||
/// The current format of the lockfile
|
||||
pub const CURRENT_FORMAT: usize = 1;
|
||||
|
||||
/// A lockfile
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
|
@ -32,91 +35,34 @@ pub struct Lockfile {
|
|||
pub graph: DependencyGraph,
|
||||
}
|
||||
|
||||
/// Old lockfile stuff. Will be removed in a future version.
|
||||
#[deprecated(
|
||||
note = "Intended to be used to migrate old lockfiles to the new format. Will be removed in a future version."
|
||||
)]
|
||||
pub mod old {
|
||||
use crate::{
|
||||
manifest::{
|
||||
overrides::OverrideKey,
|
||||
target::{Target, TargetKind},
|
||||
Alias, DependencyType,
|
||||
},
|
||||
names::{PackageName, PackageNames},
|
||||
source::{
|
||||
ids::{PackageId, VersionId},
|
||||
refs::PackageRefs,
|
||||
specifiers::DependencySpecifiers,
|
||||
},
|
||||
};
|
||||
use relative_path::RelativePathBuf;
|
||||
use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// An old dependency graph node
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct DependencyGraphNodeOld {
|
||||
/// The alias, specifier, and original (as in the manifest) type for the dependency, if it is a direct dependency (i.e. used by the current project)
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub direct: Option<(Alias, DependencySpecifiers, DependencyType)>,
|
||||
/// The dependencies of the package
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub dependencies: BTreeMap<PackageNames, (VersionId, Alias)>,
|
||||
/// The resolved (transformed, for example Peer -> Standard) type of the dependency
|
||||
pub resolved_ty: DependencyType,
|
||||
/// Whether the resolved type should be Peer if this isn't depended on
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub is_peer: bool,
|
||||
/// The package reference
|
||||
pub pkg_ref: PackageRefs,
|
||||
/// Parses the lockfile, updating it to the [`CURRENT_FORMAT`] from the format it's at
|
||||
pub fn parse_lockfile(lockfile: &str) -> Result<Lockfile, errors::ParseLockfileError> {
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct LockfileFormat {
|
||||
#[serde(default)]
|
||||
pub format: usize,
|
||||
}
|
||||
|
||||
/// A downloaded dependency graph node, i.e. a `DependencyGraphNode` with a `Target`
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct DownloadedDependencyGraphNodeOld {
|
||||
/// The target of the package
|
||||
pub target: Target,
|
||||
/// The node
|
||||
#[serde(flatten)]
|
||||
pub node: DependencyGraphNodeOld,
|
||||
}
|
||||
let format: LockfileFormat = toml::de::from_str(lockfile)?;
|
||||
let format = format.format;
|
||||
|
||||
/// An old version of a lockfile
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct LockfileOld {
|
||||
/// The name of the package
|
||||
pub name: PackageName,
|
||||
/// The version of the package
|
||||
pub version: Version,
|
||||
/// The target of the package
|
||||
pub target: TargetKind,
|
||||
/// The overrides of the package
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub overrides: BTreeMap<OverrideKey, DependencySpecifiers>,
|
||||
match format {
|
||||
0 => {
|
||||
let this = match toml::from_str(lockfile) {
|
||||
Ok(lockfile) => return Ok(lockfile),
|
||||
Err(e) => match toml::from_str::<v0::Lockfile>(lockfile) {
|
||||
Ok(this) => this,
|
||||
Err(_) => return Err(errors::ParseLockfileError::De(e)),
|
||||
},
|
||||
};
|
||||
|
||||
/// The workspace members
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub workspace: BTreeMap<PackageName, BTreeMap<TargetKind, RelativePathBuf>>,
|
||||
|
||||
/// The graph of dependencies
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub graph: BTreeMap<PackageNames, BTreeMap<VersionId, DownloadedDependencyGraphNodeOld>>,
|
||||
}
|
||||
|
||||
impl LockfileOld {
|
||||
/// Converts this lockfile to a new lockfile
|
||||
#[must_use]
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub fn to_new(self) -> super::Lockfile {
|
||||
super::Lockfile {
|
||||
name: self.name,
|
||||
version: self.version,
|
||||
target: self.target,
|
||||
overrides: self.overrides,
|
||||
workspace: self.workspace,
|
||||
graph: self
|
||||
Ok(Lockfile {
|
||||
name: this.name,
|
||||
version: this.version,
|
||||
target: this.target,
|
||||
overrides: this.overrides,
|
||||
workspace: this.workspace,
|
||||
graph: this
|
||||
.graph
|
||||
.into_iter()
|
||||
.flat_map(|(name, versions)| {
|
||||
|
@ -141,7 +87,94 @@ pub mod old {
|
|||
})
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
})
|
||||
}
|
||||
CURRENT_FORMAT => toml::de::from_str(lockfile).map_err(Into::into),
|
||||
format => Err(errors::ParseLockfileError::TooNew(format)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Lockfile v0
|
||||
pub mod v0 {
|
||||
use crate::{
|
||||
manifest::{
|
||||
overrides::OverrideKey,
|
||||
target::{Target, TargetKind},
|
||||
Alias, DependencyType,
|
||||
},
|
||||
names::{PackageName, PackageNames},
|
||||
source::{ids::VersionId, refs::PackageRefs, specifiers::DependencySpecifiers},
|
||||
};
|
||||
use relative_path::RelativePathBuf;
|
||||
use semver::Version;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// A dependency graph node
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct DependencyGraphNode {
|
||||
/// The alias, specifier, and original (as in the manifest) type for the dependency, if it is a direct dependency (i.e. used by the current project)
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub direct: Option<(Alias, DependencySpecifiers, DependencyType)>,
|
||||
/// The dependencies of the package
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub dependencies: BTreeMap<PackageNames, (VersionId, Alias)>,
|
||||
/// The resolved (transformed, for example Peer -> Standard) type of the dependency
|
||||
pub resolved_ty: DependencyType,
|
||||
/// Whether the resolved type should be Peer if this isn't depended on
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub is_peer: bool,
|
||||
/// The package reference
|
||||
pub pkg_ref: PackageRefs,
|
||||
}
|
||||
|
||||
/// A downloaded dependency graph node, i.e. a `DependencyGraphNode` with a `Target`
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct DownloadedDependencyGraphNode {
|
||||
/// The target of the package
|
||||
pub target: Target,
|
||||
/// The node
|
||||
#[serde(flatten)]
|
||||
pub node: DependencyGraphNode,
|
||||
}
|
||||
|
||||
/// A lockfile
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Lockfile {
|
||||
/// The name of the package
|
||||
pub name: PackageName,
|
||||
/// The version of the package
|
||||
pub version: Version,
|
||||
/// The target of the package
|
||||
pub target: TargetKind,
|
||||
/// The overrides of the package
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub overrides: BTreeMap<OverrideKey, DependencySpecifiers>,
|
||||
|
||||
/// The workspace members
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub workspace: BTreeMap<PackageName, BTreeMap<TargetKind, RelativePathBuf>>,
|
||||
|
||||
/// The graph of dependencies
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub graph: BTreeMap<PackageNames, BTreeMap<VersionId, DownloadedDependencyGraphNode>>,
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can occur when working with lockfiles
|
||||
pub mod errors {
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors that can occur when parsing a lockfile
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum ParseLockfileError {
|
||||
/// The lockfile format is too new
|
||||
#[error("lockfile format {} is too new. newest supported format: {}", .0, super::CURRENT_FORMAT)]
|
||||
TooNew(usize),
|
||||
|
||||
/// Deserializing the lockfile failed
|
||||
#[error("deserializing the lockfile failed")]
|
||||
De(#[from] toml::de::Error),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,10 +111,10 @@ fn transform_pesde_dependencies(
|
|||
})?;
|
||||
|
||||
let lockfile = match lockfile {
|
||||
Some(l) => match toml::from_str::<crate::Lockfile>(&l) {
|
||||
Some(l) => match crate::lockfile::parse_lockfile(&l) {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
return Err(errors::ResolveError::DeserLockfile(
|
||||
return Err(errors::ResolveError::ParseLockfile(
|
||||
Box::new(repo_url.clone()),
|
||||
e,
|
||||
))
|
||||
|
@ -603,9 +603,12 @@ pub mod errors {
|
|||
#[source] crate::source::git_index::errors::ReadFile,
|
||||
),
|
||||
|
||||
/// An error occurred while deserializing the lockfile
|
||||
#[error("error deserializing lockfile for repository {0}")]
|
||||
DeserLockfile(Box<gix::Url>, #[source] toml::de::Error),
|
||||
/// An error occurred while parsing the lockfile
|
||||
#[error("error parsing lockfile for repository {0}")]
|
||||
ParseLockfile(
|
||||
Box<gix::Url>,
|
||||
#[source] crate::lockfile::errors::ParseLockfileError,
|
||||
),
|
||||
|
||||
/// The repository is missing a lockfile
|
||||
#[error("no lockfile found in repository {0}")]
|
||||
|
|
Loading…
Add table
Reference in a new issue