diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 7fdd6301..fd412df7 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use std::{env, path::PathBuf, process}; -use aiken_project::{config::Config, pretty, script::EvalInfo, telemetry, Project}; +use aiken_project::{pretty, script::EvalInfo, telemetry, Project}; use miette::IntoDiagnostic; use owo_colors::OwoColorize; use uplc::machine::cost_model::ExBudget; @@ -18,9 +18,13 @@ where env::current_dir().into_diagnostic()? }; - let config = Config::load(project_path.clone()).into_diagnostic()?; - - let mut project = Project::new(config, project_path, Terminal::default()); + let mut project = match Project::new(project_path, Terminal::default()) { + Ok(p) => p, + Err(e) => { + e.report(); + process::exit(1); + } + }; let build_result = action(&mut project); @@ -153,10 +157,10 @@ impl telemetry::EventListener for Terminal { ); } } - telemetry::Event::DownloadingPackage { name: String } => { + telemetry::Event::DownloadingPackage { name } => { println!( "{} {}", - "...Downloading {}".bold().purple(), + "...Downloading".bold().purple(), name.bright_blue() ) } @@ -168,11 +172,10 @@ impl telemetry::EventListener for Terminal { _ => format!("{} packages in {}", count, elapsed), }; - println!( - "{} {}", - "...Downloaded {}".bold().purple(), - msg.bright_blue() - ) + println!("{} {}", "...Downloaded".bold().purple(), msg.bright_blue()) + } + telemetry::Event::ResolvingVersions => { + println!("{}", "...Resolving versions".bold().purple(),) } } } diff --git a/crates/project/src/config.rs b/crates/project/src/config.rs index f86296a4..7ea850b8 100644 --- a/crates/project/src/config.rs +++ b/crates/project/src/config.rs @@ -1,7 +1,11 @@ -use std::{fmt::Display, fs, io, path::PathBuf}; +use std::{fmt::Display, fs, path::PathBuf}; +use aiken_lang::ast::Span; +use miette::NamedSource; use serde::{de::Visitor, Deserialize, Serialize}; +use crate::error::Error; + #[derive(Deserialize)] pub struct Config { pub name: PackageName, @@ -19,7 +23,7 @@ pub struct Repository { pub platform: Platform, } -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Copy)] #[serde(rename_all = "lowercase")] pub enum Platform { Github, @@ -27,7 +31,7 @@ pub enum Platform { Bitbucket, } -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone)] pub struct Dependency { pub name: PackageName, pub version: String, @@ -106,11 +110,22 @@ impl Display for Platform { } impl Config { - pub fn load(dir: PathBuf) -> io::Result { - let raw_config = fs::read_to_string(dir.join("aiken.toml"))?; + pub fn load(dir: PathBuf) -> Result { + let config_path = dir.join("aiken.toml"); + let raw_config = fs::read_to_string(&config_path)?; - let config = toml::from_str(&raw_config).unwrap(); + let result: Self = toml::from_str(&raw_config).map_err(|e| Error::TomlLoading { + path: config_path.clone(), + src: raw_config.clone(), + named: NamedSource::new(config_path.display().to_string(), raw_config), + // this isn't actually a legit way to get the span + location: e.line_col().map(|(line, col)| Span { + start: line, + end: col, + }), + help: e.to_string(), + })?; - Ok(config) + Ok(result) } } diff --git a/crates/project/src/deps.rs b/crates/project/src/deps.rs index 97deeaeb..dfc82a45 100644 --- a/crates/project/src/deps.rs +++ b/crates/project/src/deps.rs @@ -51,17 +51,23 @@ impl LocalPackages { start: line, end: col, }), + help: e.to_string(), })?; Ok(result) } pub fn save(&self, root_path: &Path) -> Result<(), Error> { + let packages_path = root_path.join(paths::packages()); let path = root_path.join(paths::packages_toml()); + if !packages_path.exists() { + fs::create_dir_all(&packages_path)?; + } + let toml = toml::to_string(&self).expect("packages.toml serialization"); - fs::write(&path, &toml)?; + fs::write(path, toml)?; Ok(()) } @@ -119,7 +125,7 @@ impl From<&Manifest> for LocalPackages { .map(|p| Dependency { name: p.name.clone(), version: p.version.clone(), - source: p.source.into(), + source: p.source, }) .collect(), } @@ -138,7 +144,12 @@ where { let build_path = root_path.join(paths::build()); - let mut build_lock = fslock::LockFile::open(&build_path).expect("Build Lock Creation"); + if !build_path.is_dir() { + fs::create_dir_all(&build_path)?; + } + + let mut build_lock = fslock::LockFile::open(&build_path.join("aiken-compile.lock")) + .expect("Build Lock Creation"); if !build_lock .try_lock_with_pid() @@ -163,7 +174,13 @@ where let runtime = tokio::runtime::Runtime::new().expect("Unable to start Tokio"); - let (manifest, changed) = Manifest::load(event_listener)?; + let (manifest, changed) = Manifest::load( + runtime.handle().clone(), + event_listener, + config, + use_manifest, + root_path, + )?; let local = LocalPackages::load(root_path)?; @@ -178,7 +195,7 @@ where ))?; if changed { - manifest.save()?; + manifest.save(root_path)?; } LocalPackages::from(&manifest).save(root_path)?; diff --git a/crates/project/src/deps/downloader.rs b/crates/project/src/deps/downloader.rs index 937a5b67..653aadc7 100644 --- a/crates/project/src/deps/downloader.rs +++ b/crates/project/src/deps/downloader.rs @@ -5,7 +5,7 @@ use reqwest::Client; use crate::{config::PackageName, error::Error, paths}; -use super::manifest::{Package, PackageSource}; +use super::manifest::Package; pub struct Downloader<'a> { http: Client, @@ -32,7 +32,7 @@ impl<'a> Downloader<'a> { .filter(|package| project_name != &package.name) .map(|package| self.ensure_package_in_build_directory(package)); - let results = future::try_join_all(tasks).await?; + let _results = future::try_join_all(tasks).await?; Ok(()) } @@ -41,8 +41,8 @@ impl<'a> Downloader<'a> { &self, package: &Package, ) -> Result { - self.ensure_package_downloaded(package).await?; - self.extract_package_from_cache(&package.name, &package.version) + self.ensure_package_downloaded(package).await + // self.extract_package_from_cache(&package.name, &package.version) } pub async fn ensure_package_downloaded(&self, package: &Package) -> Result { @@ -60,18 +60,11 @@ impl<'a> Downloader<'a> { let response = self.http.get(url).send().await?; - let PackageSource::Github { url } = &package.source; + dbg!(response); - let zipball = - hexpm::get_package_tarball_response(response, &outer_checksum.0).map_err(|error| { - Error::DownloadPackageError { - package_name: package.name.to_string(), - package_version: package.version.to_string(), - error: error.to_string(), - } - })?; + // let PackageSource::Github { url } = &package.source; - tokio::fs::write(&zipball_path, zipball).await?; + // tokio::fs::write(&zipball_path, zipball).await?; Ok(true) } diff --git a/crates/project/src/deps/manifest.rs b/crates/project/src/deps/manifest.rs index 730ff699..a68b1bec 100644 --- a/crates/project/src/deps/manifest.rs +++ b/crates/project/src/deps/manifest.rs @@ -1,44 +1,139 @@ -use std::collections::HashMap; +use std::{fs, path::Path}; + +use aiken_lang::ast::Span; +use miette::NamedSource; +use serde::{Deserialize, Serialize}; use crate::{ - config::{PackageName, Platform}, + config::{Config, Dependency, PackageName, Platform}, error::Error, - telemetry::EventListener, + paths, + telemetry::{Event, EventListener}, }; +use super::UseManifest; + +#[derive(Deserialize, Serialize)] pub struct Manifest { - pub requirements: HashMap, + pub requirements: Vec, pub packages: Vec, } impl Manifest { - pub fn load(event_listener: &T) -> Result<(Self, bool), Error> + pub fn load( + runtime: tokio::runtime::Handle, + event_listener: &T, + config: &Config, + use_manifest: UseManifest, + root_path: &Path, + ) -> Result<(Self, bool), Error> where T: EventListener, { - todo!() + 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 = match use_manifest { + _ if !manifest_path.exists() => true, + UseManifest::No => true, + UseManifest::Yes => false, + }; + + if should_resolve { + let manifest = resolve_versions(runtime, config, None, 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), + // this isn't actually a legit way to get the span + location: e.line_col().map(|(line, col)| Span { + start: line, + end: col, + }), + help: e.to_string(), + })?; + + // If the config has 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(runtime, config, Some(&manifest), event_listener)?; + + Ok((manifest, true)) + } } - pub fn save(&self) -> Result<(), Error> { - todo!() + 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", + ); + + dbg!(&manifest_path.display()); + + fs::write(manifest_path, toml)?; + + Ok(()) } } +#[derive(Deserialize, Serialize)] pub struct Package { pub name: PackageName, pub version: String, pub requirements: Vec, - pub source: PackageSource, + pub source: Platform, } -pub enum PackageSource { - Github { url: String }, -} +fn resolve_versions( + _runtime: tokio::runtime::Handle, + config: &Config, + _manifest: Option<&Manifest>, + event_listener: &T, +) -> Result +where + T: EventListener, +{ + event_listener.handle_event(Event::ResolvingVersions); -impl From for Platform { - fn from(value: PackageSource) -> Self { - match value { - PackageSource::Github { .. } => Self::Github, - } - } + // let resolved = hex::resolve_versions( + // PackageFetcher::boxed(runtime.clone()), + // mode, + // config, + // manifest, + // )?; + + // let packages = runtime.block_on(future::try_join_all( + // resolved + // .into_iter() + // .map(|(name, version)| lookup_package(name, version)), + // ))?; + + 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(), + }; + + Ok(manifest) } diff --git a/crates/project/src/error.rs b/crates/project/src/error.rs index e01472aa..8264514b 100644 --- a/crates/project/src/error.rs +++ b/crates/project/src/error.rs @@ -36,12 +36,13 @@ pub enum Error { #[error(transparent)] Http(#[from] reqwest::Error), - #[error("Loading toml")] + #[error("{help}")] TomlLoading { path: PathBuf, src: String, named: NamedSource, location: Option, + help: String, }, #[error("Cyclical module imports")] diff --git a/crates/project/src/lib.rs b/crates/project/src/lib.rs index 676aeb1e..7be53c11 100644 --- a/crates/project/src/lib.rs +++ b/crates/project/src/lib.rs @@ -73,7 +73,7 @@ impl Project where T: EventListener, { - pub fn new(config: Config, root: PathBuf, event_listener: T) -> Project { + pub fn new(root: PathBuf, event_listener: T) -> Result, Error> { let id_gen = IdGenerator::new(); let mut module_types = HashMap::new(); @@ -81,7 +81,9 @@ where module_types.insert("aiken".to_string(), builtins::prelude(&id_gen)); module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen)); - Project { + let config = Config::load(root.clone())?; + + Ok(Project { config, defined_modules: HashMap::new(), id_gen, @@ -90,7 +92,7 @@ where sources: vec![], warnings: vec![], event_listener, - } + }) } pub fn build(&mut self, uplc: bool) -> Result<(), Error> { diff --git a/crates/project/src/paths.rs b/crates/project/src/paths.rs index f4414795..0fd21d07 100644 --- a/crates/project/src/paths.rs +++ b/crates/project/src/paths.rs @@ -2,6 +2,10 @@ use std::path::PathBuf; use crate::config::PackageName; +pub fn manifest() -> PathBuf { + PathBuf::from("aiken.lock") +} + pub fn build() -> PathBuf { PathBuf::from("build") } diff --git a/crates/project/src/telemetry.rs b/crates/project/src/telemetry.rs index 2792c827..baab2f65 100644 --- a/crates/project/src/telemetry.rs +++ b/crates/project/src/telemetry.rs @@ -43,4 +43,5 @@ pub enum Event { start: tokio::time::Instant, count: usize, }, + ResolvingVersions, } diff --git a/examples/sample/.gitignore b/examples/sample/.gitignore new file mode 100644 index 00000000..567609b1 --- /dev/null +++ b/examples/sample/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/examples/sample/aiken.lock b/examples/sample/aiken.lock new file mode 100644 index 00000000..0423f31b --- /dev/null +++ b/examples/sample/aiken.lock @@ -0,0 +1,13 @@ +# This file was generated by Aiken +# You typically do not need to edit this file + +[[requirements]] +name = "aiken-lang/stdlib" +version = "main" +source = "github" + +[[packages]] +name = "aiken-lang/stdlib" +version = "main" +requirements = [] +source = "github" diff --git a/examples/sample/aiken.toml b/examples/sample/aiken.toml index 4feccc0a..74580cc9 100644 --- a/examples/sample/aiken.toml +++ b/examples/sample/aiken.toml @@ -1,6 +1,6 @@ -name = "sample" +name = "aiken/sample" version = "0.0.1" dependencies = [ - { name = "aiken-lang/stdlib", version = "main", source = "Github" }, + { name = "aiken-lang/stdlib", version = "main", source = "github" }, ]