1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
#![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(),
// }
// }
// }