mirror of
https://github.com/pesde-pkg/pesde.git
synced 2025-04-10 22:00: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")]
|
#[instrument(skip(self), ret(level = "trace"), level = "debug")]
|
||||||
pub async fn deser_lockfile(&self) -> Result<Lockfile, errors::LockfileReadError> {
|
pub async fn deser_lockfile(&self) -> Result<Lockfile, errors::LockfileReadError> {
|
||||||
let string = fs::read_to_string(self.package_dir().join(LOCKFILE_FILE_NAME)).await?;
|
let string = fs::read_to_string(self.package_dir().join(LOCKFILE_FILE_NAME)).await?;
|
||||||
Ok(match toml::from_str(&string) {
|
lockfile::parse_lockfile(&string).map_err(Into::into)
|
||||||
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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the lockfile
|
/// Write the lockfile
|
||||||
|
@ -256,7 +245,9 @@ impl Project {
|
||||||
let lockfile = format!(
|
let lockfile = format!(
|
||||||
r"# This file is automatically @generated by pesde.
|
r"# This file is automatically @generated by pesde.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
{lockfile}"
|
format = {}
|
||||||
|
{lockfile}",
|
||||||
|
lockfile::CURRENT_FORMAT
|
||||||
);
|
);
|
||||||
|
|
||||||
fs::write(self.package_dir().join(LOCKFILE_FILE_NAME), lockfile).await?;
|
fs::write(self.package_dir().join(LOCKFILE_FILE_NAME), lockfile).await?;
|
||||||
|
@ -511,9 +502,9 @@ pub mod errors {
|
||||||
#[error("io error reading lockfile")]
|
#[error("io error reading lockfile")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
|
|
||||||
/// An error occurred while deserializing the lockfile
|
/// An error occurred while parsing the lockfile
|
||||||
#[error("error deserializing lockfile")]
|
#[error("error parsing lockfile")]
|
||||||
Serde(#[from] toml::de::Error),
|
Parse(#[from] crate::lockfile::errors::ParseLockfileError),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Errors that can occur when writing the lockfile
|
/// Errors that can occur when writing the lockfile
|
||||||
|
|
201
src/lockfile.rs
201
src/lockfile.rs
|
@ -3,12 +3,15 @@ use crate::{
|
||||||
graph::DependencyGraph,
|
graph::DependencyGraph,
|
||||||
manifest::{overrides::OverrideKey, target::TargetKind},
|
manifest::{overrides::OverrideKey, target::TargetKind},
|
||||||
names::PackageName,
|
names::PackageName,
|
||||||
source::specifiers::DependencySpecifiers,
|
source::{ids::PackageId, specifiers::DependencySpecifiers},
|
||||||
};
|
};
|
||||||
use relative_path::RelativePathBuf;
|
use relative_path::RelativePathBuf;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use serde::{Deserialize, Serialize};
|
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
|
/// A lockfile
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
@ -32,91 +35,34 @@ pub struct Lockfile {
|
||||||
pub graph: DependencyGraph,
|
pub graph: DependencyGraph,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Old lockfile stuff. Will be removed in a future version.
|
/// Parses the lockfile, updating it to the [`CURRENT_FORMAT`] from the format it's at
|
||||||
#[deprecated(
|
pub fn parse_lockfile(lockfile: &str) -> Result<Lockfile, errors::ParseLockfileError> {
|
||||||
note = "Intended to be used to migrate old lockfiles to the new format. Will be removed in a future version."
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
)]
|
pub struct LockfileFormat {
|
||||||
pub mod old {
|
#[serde(default)]
|
||||||
use crate::{
|
pub format: usize,
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A downloaded dependency graph node, i.e. a `DependencyGraphNode` with a `Target`
|
let format: LockfileFormat = toml::de::from_str(lockfile)?;
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
let format = format.format;
|
||||||
pub struct DownloadedDependencyGraphNodeOld {
|
|
||||||
/// The target of the package
|
|
||||||
pub target: Target,
|
|
||||||
/// The node
|
|
||||||
#[serde(flatten)]
|
|
||||||
pub node: DependencyGraphNodeOld,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An old version of a lockfile
|
match format {
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
0 => {
|
||||||
pub struct LockfileOld {
|
let this = match toml::from_str(lockfile) {
|
||||||
/// The name of the package
|
Ok(lockfile) => return Ok(lockfile),
|
||||||
pub name: PackageName,
|
Err(e) => match toml::from_str::<v0::Lockfile>(lockfile) {
|
||||||
/// The version of the package
|
Ok(this) => this,
|
||||||
pub version: Version,
|
Err(_) => return Err(errors::ParseLockfileError::De(e)),
|
||||||
/// 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
|
Ok(Lockfile {
|
||||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
name: this.name,
|
||||||
pub workspace: BTreeMap<PackageName, BTreeMap<TargetKind, RelativePathBuf>>,
|
version: this.version,
|
||||||
|
target: this.target,
|
||||||
/// The graph of dependencies
|
overrides: this.overrides,
|
||||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
workspace: this.workspace,
|
||||||
pub graph: BTreeMap<PackageNames, BTreeMap<VersionId, DownloadedDependencyGraphNodeOld>>,
|
graph: this
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
.graph
|
.graph
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|(name, versions)| {
|
.flat_map(|(name, versions)| {
|
||||||
|
@ -141,7 +87,94 @@ pub mod old {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect(),
|
.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 {
|
let lockfile = match lockfile {
|
||||||
Some(l) => match toml::from_str::<crate::Lockfile>(&l) {
|
Some(l) => match crate::lockfile::parse_lockfile(&l) {
|
||||||
Ok(l) => l,
|
Ok(l) => l,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(errors::ResolveError::DeserLockfile(
|
return Err(errors::ResolveError::ParseLockfile(
|
||||||
Box::new(repo_url.clone()),
|
Box::new(repo_url.clone()),
|
||||||
e,
|
e,
|
||||||
))
|
))
|
||||||
|
@ -603,9 +603,12 @@ pub mod errors {
|
||||||
#[source] crate::source::git_index::errors::ReadFile,
|
#[source] crate::source::git_index::errors::ReadFile,
|
||||||
),
|
),
|
||||||
|
|
||||||
/// An error occurred while deserializing the lockfile
|
/// An error occurred while parsing the lockfile
|
||||||
#[error("error deserializing lockfile for repository {0}")]
|
#[error("error parsing lockfile for repository {0}")]
|
||||||
DeserLockfile(Box<gix::Url>, #[source] toml::de::Error),
|
ParseLockfile(
|
||||||
|
Box<gix::Url>,
|
||||||
|
#[source] crate::lockfile::errors::ParseLockfileError,
|
||||||
|
),
|
||||||
|
|
||||||
/// The repository is missing a lockfile
|
/// The repository is missing a lockfile
|
||||||
#[error("no lockfile found in repository {0}")]
|
#[error("no lockfile found in repository {0}")]
|
||||||
|
|
Loading…
Add table
Reference in a new issue