#![allow(clippy::use_self)]
use crate::package::OrganizationToResolveFor;
use anyhow::bail;
use camino::{Utf8Path, Utf8PathBuf};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::{
package::{PackageSet, PackageVersion},
registry::Registry,
resolve::Resolver,
};
/// Common name between all lock files for ontologies.
pub const LOCK_FILE_NAME: &str = "Plow.lock";
/// A runtime representation of a lock file.
#[derive(Debug, Clone, Default)]
pub struct LockFile {
// Maybe necessary later.
_path: Option<PathBuf>,
pub locked_dependencies: PackageSet,
}
impl LockFile {
/// Writes the conceptual lock file to the given path.
///
/// This mimics Cargo.lock and produces a similar structure, like below.
///
/// ```toml
/// [[package]]
/// name = "@namespace/name"
/// # A complete bare version.
/// version = "0.2.15"
/// # Currently left empty
/// source = ""
/// cksum = "24606928a235e73cdef55a0c909719cadd72fce573e5713d58cb2952d8f5794c"
/// # Set of dependencies with corresponding versions.
/// dependencies = [
/// "@x/y 0.24.0",
/// "@a/b 1.15.0",
/// ]
/// ```
pub fn write(
workspace_root: Option<Utf8PathBuf>,
package_set: &[PackageInLockFile],
) -> Result<Utf8PathBuf, anyhow::Error> {
let packages = PackagesInLockFile {
version: FileVersion::V1,
packages: package_set.to_vec(),
};
let serialized = toml::to_string_pretty(&packages)?;
if let Some(workspace_root) = workspace_root {
let lock_file_path = workspace_root.join(LOCK_FILE_NAME);
let mut lock_file_contents = "# This file is automatically generated by the Plow package manager.\n# It is not intended for manual editing.\n\n".to_owned();
lock_file_contents += &serialized;
std::fs::write(&lock_file_path, lock_file_contents)?;
return Ok(lock_file_path);
}
bail!("There is no workspace root to write the lock file.");
}
pub fn deserialize_lock_file(
lock_file_path: &Utf8Path,
) -> Result<PackagesInLockFile, anyhow::Error> {
let lock_file_contents = std::fs::read_to_string(lock_file_path)?;
Ok(toml::from_str::<PackagesInLockFile>(&lock_file_contents)?)
}
pub fn previous_lock_file_exists(workspace_root: Option<Utf8PathBuf>) -> Option<Utf8PathBuf> {
workspace_root.and_then(|workspace_root| {
let lock_file_path_in_workspace_root = workspace_root.join(LOCK_FILE_NAME);
if lock_file_path_in_workspace_root.exists() {
return Some(lock_file_path_in_workspace_root);
}
None
})
}
/// Starts locking operation, resolves dependencies and write the lock file.
pub fn lock_with_registry(
package_to_resolve: OrganizationToResolveFor,
registry: &dyn Registry,
workspace_root: Option<Utf8PathBuf>,
respect_existing_lock_file: bool,
) -> Result<Self, anyhow::Error> {
// TODO: Either this or another entry point will be expanded to support db based locks in the future.
let (resolved_dependencies, _previously_locked_path) =
if let Some(lock_file_path) = Self::previous_lock_file_exists(workspace_root) {
if respect_existing_lock_file {
// With existing lock file input
let packages = Self::deserialize_lock_file(&lock_file_path)?
.packages
.iter()
.cloned()
// TODO:
// Currently filter the local resolutions out.
// Will be addressed soon.
.filter(|p| !p.root)
.collect::<Vec<_>>();
(
Into::<crate::resolve::VersionRequestResolver>::into(registry)
.resolve_dependencies(package_to_resolve, Some(&packages))?,
Some(lock_file_path),
)
} else {
(
Into::<crate::resolve::VersionRequestResolver>::into(registry)
.resolve_dependencies(package_to_resolve, None)?,
None,
)
}
} else {
// No lockfile input
(
Into::<crate::resolve::VersionRequestResolver>::into(registry)
.resolve_dependencies(package_to_resolve, None)?,
None,
)
};
// TODO: Do not write in this function!
// let resolved_dependencies_with_metadata: Vec<PackageVersionWithRegistryMetadata> =
// resolved_dependencies
// .packages
// .iter()
// .map(|package_version| registry.get_package_version_metadata(package_version))
// .collect::<Result<Vec<_>, _>>()?;
// if !resolved_dependencies_with_metadata.is_empty() {
// // Do not write a lock file if there are no dependencies.
// let path = Self::write(selected_file, &resolved_dependencies_with_metadata)?;
// return Ok(Self {
// _path: Some(path),
// locked_dependencies: resolved_dependencies,
// });
// }
// if let Some(existing_lock_file_path) = previously_locked_path {
// if resolved_dependencies_with_metadata.is_empty() {
// // If there are no dependencies, but there is an existing lock file, remove it.
// std::fs::remove_file(existing_lock_file_path)?;
// }
// }
// Always re-writing the lock file even if it is the same,
// I think it is harmless since we're not interested on the creation time.
Ok(Self {
_path: None,
locked_dependencies: resolved_dependencies,
})
}
}
#[derive(Serialize, Deserialize)]
pub enum FileVersion {
#[serde(rename(serialize = "1", deserialize = "1"))]
V1,
}
/// Set of packages in the form to be serialized to or to be deserialized to or from the lock file.
#[derive(Serialize, Deserialize)]
#[serde(rename(serialize = "package", deserialize = "package"))]
pub struct PackagesInLockFile {
version: FileVersion,
#[serde(rename(serialize = "package", deserialize = "package"))]
packages: Vec<PackageInLockFile>,
}
impl PackagesInLockFile {
pub fn packages(&self) -> &[PackageInLockFile] {
&self.packages
}
}
/// A package in the form to be serialized to or to be deserialized to or from the lock file.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct PackageInLockFile {
pub root: bool,
pub name: String,
pub version: String,
pub source: Option<String>,
pub ontology_iri: Option<String>,
pub cksum: Option<String>,
pub dependencies: Vec<String>,
}
// Only one way conversion is allowed, for convenience.
#[allow(clippy::from_over_into)]
impl Into<PackageVersion> for &PackageInLockFile {
fn into(self) -> PackageVersion {
PackageVersion {
package_name: self.name.clone(),
version: self.version.clone(),
}
}
}
// impl From<PackageVersionWithRegistryMetadata> for PackageInLockFile {
// fn from(package: PackageVersionWithRegistryMetadata) -> Self {
// Self {
// name: package.package_name,
// version: package.version.to_string(),
// // TODO: For example, source = "registry+https://github.com/rust-lang/crates.io-index" ??
// source: None,
// ontology_iri: package.ontology_iri,
// cksum: package.cksum,
// dependencies: package
// .dependencies
// .iter()
// .map(std::string::ToString::to_string)
// .collect_vec(),
// }
// }
// }