use aiken_lang::ast::Span; use miette::NamedSource; use serde::{Deserialize, Serialize}; use std::{ collections::BTreeMap, fs, path::Path, time::{Duration, SystemTime}, }; use crate::{ config::{Config, Dependency, Platform}, error::Error, package_name::PackageName, paths, telemetry::{Event, EventListener}, }; #[derive(Deserialize, Serialize, Debug)] pub struct Manifest { pub requirements: Vec, pub packages: Vec, #[serde(default)] pub etags: BTreeMap, } impl Manifest { pub fn load( event_listener: &T, config: &Config, root_path: &Path, ) -> Result<(Self, bool), Error> where T: EventListener, { let manifest_path = root_path.join(paths::manifest()); // If there's no manifest (or we have been asked not to use it) then resolve // the versions anew let should_resolve = !manifest_path.exists(); if should_resolve { let manifest = resolve_versions(config, event_listener)?; return Ok((manifest, true)); } let toml = fs::read_to_string(&manifest_path)?; let manifest: Self = toml::from_str(&toml).map_err(|e| Error::TomlLoading { path: manifest_path.clone(), src: toml.clone(), named: NamedSource::new(manifest_path.display().to_string(), toml).into(), // this isn't actually a legit way to get the span location: e.span().map(|range| Span { start: range.start, end: range.end, }), help: e.to_string(), })?; // If the config is unchanged since the manifest was written then it is up // to date so we can return it unmodified. if manifest.requirements == config.dependencies { Ok((manifest, false)) } else { let manifest = resolve_versions(config, event_listener)?; Ok((manifest, true)) } } pub fn save(&self, root_path: &Path) -> Result<(), Error> { let manifest_path = root_path.join(paths::manifest()); let mut toml = toml::to_string(&self).expect("aiken.lock serialization"); toml.insert_str( 0, "# This file was generated by Aiken\n# You typically do not need to edit this file\n\n", ); fs::write(manifest_path, toml)?; Ok(()) } pub fn lookup_etag(&self, package: &Package) -> Option { match self.etags.get(&etag_key(package)) { None => None, Some((last_fetched, etag)) => { let elapsed = SystemTime::now().duration_since(*last_fetched).unwrap(); // Discard any etag older than an hour. So that we throttle call to the package // registry but we ensure a relatively good synchonization of local packages. if elapsed > Duration::from_secs(3600) { None } else { Some(etag.clone()) } } } } pub fn insert_etag(&mut self, package: &Package, etag: String) { self.etags .insert(etag_key(package), (SystemTime::now(), etag)); } } fn etag_key(package: &Package) -> String { format!( "{}/{}@{}", package.name.owner, package.name.repo, package.version ) } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct Package { pub name: PackageName, pub version: String, pub requirements: Vec, pub source: Platform, } fn resolve_versions(config: &Config, event_listener: &T) -> Result where T: EventListener, { event_listener.handle_event(Event::ResolvingVersions); let manifest = Manifest { packages: config .dependencies .iter() .map(|dep| Package { name: dep.name.clone(), version: dep.version.clone(), requirements: vec![], source: dep.source, }) .collect(), requirements: config.dependencies.clone(), etags: BTreeMap::new(), }; Ok(manifest) }