diff --git a/README.md b/README.md index c0e97c9..0e7c798 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,12 @@ Build system for the Lingua Franca coordination language Usage: lingo [OPTIONS] Commands: - init Initialize a Lingua Franca package - build Compile one or multiple binaries in a Lingua Franca package - update Update the dependencies and potentially build tools - run Build and run binaries - clean Remove build artifacts - help Print this message or the help of the given subcommand(s) + init Initialize a Lingua Franca package + build Compile one or multiple binaries in a Lingua Franca package + update Remove build artifacts, update the dependencies, and build + run Build and run binaries + clean Remove build artifacts + help Print this message or the help of the given subcommand(s) Options: -q, --quiet Do not produce any output diff --git a/src/README.md b/src/README.md index 6fe41e6..f4dfec7 100644 --- a/src/README.md +++ b/src/README.md @@ -6,7 +6,7 @@ General workflow that is happening. ```mermaid graph TD - A[Start] --> B(Handeling User Input) + A[Start] --> B(Handling User Input) B --> C(Parsing Lingo.toml) C --> D(Configuring LFC) D --> E(Invoking LFC) diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 5c7c790..25cfa28 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -31,6 +31,11 @@ pub fn execute_command<'a>( match command { CommandSpec::Build(_build) => { + log::info!( + "{} starting dependency resolution ({} declared dependencies)", + "Build step:", + dependencies.len() + ); let manager = match DependencyManager::from_dependencies( dependencies.clone(), &PathBuf::from(OUTPUT_DIRECTORY), @@ -44,6 +49,7 @@ pub fn execute_command<'a>( }; // enriching the apps with the target properties from the libraries + log::info!("Build step: merging dependency target properties"); let library_properties = manager.get_target_properties().expect("lib properties"); // merging app with library target properties @@ -67,6 +73,12 @@ pub fn execute_command<'a>( } for (build_system, apps) in by_build_system { + log::info!( + "Build step: dispatching {} app(s) to {:?}/{:?}", + apps.len(), + build_system.0, + build_system.1 + ); let mut sub_res = BatchBuildResults::for_apps(&apps); sub_res.map(|app| { diff --git a/src/main.rs b/src/main.rs index 7541c24..8668224 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use liblingo::args::TargetLanguage; use std::io::ErrorKind; use std::path::{Path, PathBuf}; use std::process::Command; -use std::{env, io}; +use std::{env, fs, io}; use clap::Parser; use git2::BranchType::{Local, Remote}; @@ -91,10 +91,55 @@ fn do_read_to_string(p: &Path) -> io::Result { std::fs::read_to_string(p) } +fn remove_if_exists(path: &Path) -> io::Result<()> { + if path.is_dir() { + match fs::remove_dir_all(path) { + Ok(()) => log::info!("Deleted {}", path.display()), + Err(err) => { + log::error!("Failed to delete {}: {}", path.display(), err); + return Err(err); + } + } + } else if path.is_file() { + match fs::remove_file(path) { + Ok(()) => log::info!("Deleted {}", path.display()), + Err(err) => { + log::error!("Failed to delete {}: {}", path.display(), err); + return Err(err); + } + } + } + Ok(()) +} + +fn update(project_root: &Path) -> BuildResult { + remove_if_exists(&project_root.join("build"))?; + let lock_path = project_root.join("Lingo.lock"); + if lock_path.is_file() { + let backup_path = (0..) + .map(|n| project_root.join(format!("Lingo.lock.bak{}", n))) + .find(|p| !p.exists()) + .unwrap(); + fs::rename(&lock_path, &backup_path)?; + log::info!("Backed up Lingo.lock to {}", backup_path.display()); + } + Ok(()) +} + fn main() { - print_logger::new().init().unwrap(); // parses command line arguments let args = CommandLineArgs::parse(); + let level_filter = if args.quiet { + print_logger::LevelFilter::Error + } else if args.verbose { + print_logger::LevelFilter::Debug + } else { + print_logger::LevelFilter::Info + }; + print_logger::new() + .level_filter(level_filter) + .init() + .unwrap(); // Finds Lingo.toml recursively inside the parent directories. // If it exists the returned path is absolute. @@ -116,6 +161,7 @@ fn main() { let result = execute_command( &mut wrapped_config, args.command, + lingo_path.as_deref(), Box::new(do_which), Box::new(do_clone_and_checkout), ); @@ -161,6 +207,7 @@ fn validate(config: &mut Option, command: &ConsoleCommand) -> BuildResul fn execute_command<'a>( config: &'a mut Option, command: ConsoleCommand, + lingo_path: Option<&Path>, _which_capability: WhichCapability, git_clone_capability: GitCloneAndCheckoutCap, ) -> CommandResult<'a> { @@ -187,7 +234,34 @@ fn execute_command<'a>( (Some(config), ConsoleCommand::Clean) => { CommandResult::Batch(run_command(CommandSpec::Clean, config, true)) } - _ => todo!(), + (Some(config), ConsoleCommand::Update) => { + let update_result = lingo_path + .and_then(Path::parent) + .ok_or_else(|| { + Box::new(io::Error::new( + ErrorKind::NotFound, + "Error: Missing Lingo.toml file", + )) as Box + }) + .and_then(update); + match update_result { + Err(e) => CommandResult::Single(Err(e)), + Ok(()) => { + let default_args = BuildArgs { + build_system: None, + language: None, + platform: None, + lfc: None, + no_compile: false, + keep_going: false, + release: false, + apps: vec![], + threads: 0, + }; + CommandResult::Batch(build(&default_args, config)) + } + } + } } } @@ -216,7 +290,11 @@ fn build<'a>(args: &BuildArgs, config: &'a mut Config) -> BatchBuildResults<'a> ) } -fn run_command(task: CommandSpec, config: &mut Config, _fail_at_end: bool) -> BatchBuildResults { +fn run_command( + task: CommandSpec, + config: &mut Config, + _fail_at_end: bool, +) -> BatchBuildResults<'_> { let _apps = config.apps.iter().collect::>(); liblingo::backends::execute_command( &task, diff --git a/src/package/lock.rs b/src/package/lock.rs index 2c3bff1..4a2ff28 100644 --- a/src/package/lock.rs +++ b/src/package/lock.rs @@ -1,9 +1,8 @@ use crate::util::sha1dir; -use colored::Colorize; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use versions::Versioning; -use log::error; +use log::{error, info}; use serde::de::Error as DeserializationError; use serde::ser::Error as SerializationError; use std::cmp::PartialEq; @@ -213,18 +212,32 @@ impl DependencyLock { lfc_include_folder: &Path, git_clone_and_checkout_cap: &GitCloneAndCheckoutCap, ) -> anyhow::Result<()> { + info!( + "Build step: checking lock entries in {}", + lfc_include_folder.display() + ); for (_, lock) in self.dependencies.iter() { let temp = lfc_include_folder.join(&lock.name); // the Lingo.toml for this dependency doesnt exists, hence we need to fetch this package if !temp.join("Lingo.toml").exists() { let mut details = PackageDetails::try_from(&lock.source)?; + info!( + "Fetching {} from {}+{}", + lock.name, lock.source.source_type, lock.source.uri + ); details .fetch(&temp, git_clone_and_checkout_cap) .expect("cannot pull package"); } + info!( + "Build step: computing checksum for locked dependency {} at {}", + lock.name, + temp.display() + ); let hash = sha1dir::checksum_current_dir(&temp, false); + info!("Build step: checksum complete for {}", lock.name); if hash.to_string() != lock.checksum { error!("checksum does not match aborting!"); @@ -233,12 +246,7 @@ impl DependencyLock { let lingo_toml_text = fs::read_to_string(temp.join("Lingo.toml"))?; let read_toml = toml::from_str::(&lingo_toml_text)?.to_config(&temp); - println!( - "{} {} ... {}", - "Reading".green().bold(), - lock.name, - read_toml.package.version - ); + info!("Reading {} ... {}", lock.name, read_toml.package.version); let lib = match read_toml.library { Some(value) => value, diff --git a/src/package/management.rs b/src/package/management.rs index 3e04b8f..b54a144 100644 --- a/src/package/management.rs +++ b/src/package/management.rs @@ -1,5 +1,5 @@ -use colored::Colorize; -use log::error; +use anyhow::Context; +use log::{error, info}; use versions::{Requirement, Versioning}; use crate::util::sha1dir; @@ -10,6 +10,7 @@ use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; use std::str::FromStr; +use std::time::Instant; use url::{ParseError, Url}; use crate::package::lock::{PackageLockSource, PackageLockSourceType}; @@ -75,8 +76,12 @@ impl PackageDetails { ) -> anyhow::Result<()> { match &self.mutual_exclusive { ProjectSource::Path(path_buf) => { - let src = fs::canonicalize(path_buf)?; - let dst = fs::canonicalize(library_path)?; + let src = fs::canonicalize(path_buf).with_context(|| { + format!("dependency path not found: {}", path_buf.display()) + })?; + let dst = fs::canonicalize(library_path).with_context(|| { + format!("library path not found: {}", library_path.display()) + })?; Ok(copy_dir_all(src, dst)?) } ProjectSource::Git(git_url) => { @@ -98,9 +103,18 @@ impl DependencyManager { target_path: &Path, git_clone_and_checkout_cap: &GitCloneAndCheckoutCap, ) -> anyhow::Result { + info!( + "Build step: resolving dependencies in {}", + target_path.display() + ); // create library folder let library_path = target_path.join(LIBRARY_DIRECTORY); - fs::create_dir_all(&library_path)?; + fs::create_dir_all(&library_path).with_context(|| { + format!( + "failed to create library directory: {}", + library_path.display() + ) + })?; let mut manager; let mut lock: DependencyLock; @@ -108,12 +122,20 @@ impl DependencyManager { // checks if a Lingo.lock file exists if lock_file.exists() { + info!("Build step: loading lock file {}", lock_file.display()); // reads and parses Lockfile - lock = toml::from_str::(&fs::read_to_string(lock_file)?) + lock = + toml::from_str::(&fs::read_to_string(&lock_file).with_context( + || format!("failed to read lock file: {}", lock_file.display()), + )?) .expect("cannot parse lock"); // if a lock file is present it will load the dependencies from it and checks // integrity of the build directory + info!( + "Build step: validating lock dependencies in {}", + target_path.join("lfc_include").display() + ); if let Ok(()) = lock.init(&target_path.join("lfc_include"), git_clone_and_checkout_cap) { return Ok(DependencyManager { @@ -140,9 +162,14 @@ impl DependencyManager { lock = DependencyLock::create(selection); // writes the lock file down - let mut lock_file = File::create(target_path.join("../Lingo.lock"))?; - - println!("{:?}", lock.dependencies); + let lock_file_path = target_path.join("../Lingo.lock"); + let mut lock_file = File::create(&lock_file_path) + .with_context(|| format!("failed to create lock file: {}", lock_file_path.display()))?; + + info!( + "Build step: selected lock dependencies: {:?}", + lock.dependencies + ); let serialized_toml = toml::to_string(&lock).expect("cannot generate toml"); lock_file.write_all(serialized_toml.as_ref())?; @@ -168,11 +195,16 @@ impl DependencyManager { self.pulling_queue.append(&mut dependencies); let sub_dependency_path = root_path.join("libraries"); //fs::remove_dir_all(&sub_dependency_path)?; - fs::create_dir_all(&sub_dependency_path)?; + fs::create_dir_all(&sub_dependency_path).with_context(|| { + format!( + "failed to create directory: {}", + sub_dependency_path.display() + ) + })?; while !self.pulling_queue.is_empty() { if let Some((package_name, package_details)) = self.pulling_queue.pop() { - print!("{} {} ...", "Cloning".green().bold(), package_name); + info!("Cloning {} ...", package_name); let node = match self.non_recursive_fetching( &package_name, package_details, @@ -216,15 +248,32 @@ impl DependencyManager { fs::create_dir_all(&temporary_path)?; // cloning the specified package + info!( + "Build step: fetching dependency {} into {}", + name, + temporary_path.display() + ); package.fetch(&temporary_path, git_clone_and_checkout_cap)?; + info!( + "Build step: computing checksum for {}", + temporary_path.display() + ); + let checksum_started_at = Instant::now(); let hash = sha1dir::checksum_current_dir(&temporary_path, false); + info!( + "Build step: checksum complete for {} in {:?}", + temporary_path.display(), + checksum_started_at.elapsed() + ); let include_path = library_path.join(hash.to_string()); - let lingo_toml_text = fs::read_to_string(temporary_path.clone().join("Lingo.toml"))?; + let lingo_toml_path = temporary_path.join("Lingo.toml"); + let lingo_toml_text = fs::read_to_string(&lingo_toml_path) + .with_context(|| format!("failed to read {}", lingo_toml_path.display()))?; let read_toml = toml::from_str::(&lingo_toml_text)?.to_config(&temporary_path); - println!(" {}", read_toml.package.version); + info!("Resolved dependency version {}", read_toml.package.version); let config = match read_toml.library { Some(value) => value, diff --git a/src/package/target_properties.rs b/src/package/target_properties.rs index 5c3d0a4..4b7520c 100644 --- a/src/package/target_properties.rs +++ b/src/package/target_properties.rs @@ -56,7 +56,7 @@ pub struct LibraryTargetPropertiesFile { sources: Vec, /// list of files that should be made available to the user - #[serde(rename = "sources", default)] + #[serde(rename = "artifacts", default)] artifacts: Vec, } diff --git a/src/util/mod.rs b/src/util/mod.rs index 5b41aa0..ea0e98d 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -42,13 +42,17 @@ pub fn copy_recursively(source: impl AsRef, destination: impl AsRef) Ok(()) } +/// Delete subdirectories of a given path recursively. +// Errors are reported but this function always returns Ok. pub fn delete_subdirs(path_root: &Path, subdirs: &[&str]) -> io::Result<()> { let mut buf = path_root.to_owned(); for &sub_dir in subdirs { buf.push(sub_dir); if buf.is_dir() { - // ignore errors - let _ = fs::remove_dir_all(&buf); + match fs::remove_dir_all(&buf) { + Ok(_) => log::info!("Deleted {}", buf.display()), + Err(err) => log::error!("Failed to delete {}: {}", buf.display(), err), + } } buf.pop(); }