From a60ce013d7e353354a38c959662238e992c68f6b Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Tue, 21 Apr 2026 09:00:06 -0700 Subject: [PATCH 1/9] Various enhancements to the build process --- README.md | 13 +++--- src/args.rs | 3 ++ src/backends/mod.rs | 12 ++++++ src/main.rs | 34 +++++++++++++++- src/package/lock.rs | 26 ++++++++++++ src/package/management.rs | 70 ++++++++++++++++++++++++++++---- src/package/target_properties.rs | 2 +- src/util/mod.rs | 6 ++- 8 files changed, 148 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index c0e97c9..92e41c5 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,13 @@ 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 Update the dependencies and potentially build tools + run Build and run binaries + clean Remove build artifacts + cleanall Remove build artifacts, dependencies, and lfc 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/args.rs b/src/args.rs index 65ac4c4..6b6d3be 100644 --- a/src/args.rs +++ b/src/args.rs @@ -122,6 +122,9 @@ pub enum Command { /// removes build artifacts Clean, + + /// removes build artifacts, installed dependencies, lock file, and lfc build artifacts + Cleanall, } #[derive(Parser)] diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 5c7c790..cab03c4 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) => { + println!( + "{} starting dependency resolution ({} declared dependencies)", + "Build step:".to_string(), + 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 + println!("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 { + println!( + "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..2c05112 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,6 +91,27 @@ 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() { + println!("Deleted {}", path.display()); + fs::remove_dir_all(path)?; + } else if path.is_file() { + println!("Deleted {}", path.display()); + fs::remove_file(path)?; + } + Ok(()) +} + +fn clean_all(project_root: &Path) -> BuildResult { + remove_if_exists(&project_root.join("build"))?; + remove_if_exists(&project_root.join("Lingo.lock"))?; + remove_if_exists(&project_root.join("src-gen"))?; + remove_if_exists(&project_root.join("bin"))?; + remove_if_exists(&project_root.join("fed-gen"))?; + remove_if_exists(&project_root.join("include"))?; + Ok(()) +} + fn main() { print_logger::new().init().unwrap(); // parses command line arguments @@ -187,6 +208,11 @@ fn execute_command<'a>( (Some(config), ConsoleCommand::Clean) => { CommandResult::Batch(run_command(CommandSpec::Clean, config, true)) } + (_, ConsoleCommand::Cleanall) => { + let cwd = env::current_dir() + .map_err(|e| -> Box { Box::new(e) }); + CommandResult::Single(cwd.and_then(|path| clean_all(&path))) + } _ => todo!(), } } @@ -216,7 +242,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..7e4857a 100644 --- a/src/package/lock.rs +++ b/src/package/lock.rs @@ -12,6 +12,7 @@ use std::fmt::Display; use std::fs; use std::path::{Path, PathBuf}; use std::str::FromStr; +use std::time::Instant; use crate::GitCloneAndCheckoutCap; @@ -213,18 +214,43 @@ impl DependencyLock { lfc_include_folder: &Path, git_clone_and_checkout_cap: &GitCloneAndCheckoutCap, ) -> anyhow::Result<()> { + println!( + "{} checking lock entries in {}", + "Build step:".green().bold(), + 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)?; + println!( + "{} {} from {}+{}", + "Fetching".green().bold(), + lock.name, + lock.source.source_type, + lock.source.uri + ); details .fetch(&temp, git_clone_and_checkout_cap) .expect("cannot pull package"); } + println!( + "{} computing checksum for locked dependency {} at {}", + "Build step:".green().bold(), + lock.name, + temp.display() + ); + let checksum_started_at = Instant::now(); let hash = sha1dir::checksum_current_dir(&temp, false); + println!( + "{} checksum complete for {} in {:?}", + "Build step:".green().bold(), + lock.name, + checksum_started_at.elapsed() + ); if hash.to_string() != lock.checksum { error!("checksum does not match aborting!"); diff --git a/src/package/management.rs b/src/package/management.rs index 3e04b8f..0845371 100644 --- a/src/package/management.rs +++ b/src/package/management.rs @@ -1,3 +1,4 @@ +use anyhow::Context; use colored::Colorize; use log::error; use versions::{Requirement, Versioning}; @@ -10,6 +11,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 +77,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 +104,19 @@ impl DependencyManager { target_path: &Path, git_clone_and_checkout_cap: &GitCloneAndCheckoutCap, ) -> anyhow::Result { + println!( + "{} resolving dependencies in {}", + "Build step:".green().bold(), + 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 +124,25 @@ impl DependencyManager { // checks if a Lingo.lock file exists if lock_file.exists() { + println!( + "{} loading lock file {}", + "Build step:".green().bold(), + 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 + println!( + "{} validating lock dependencies in {}", + "Build step:".green().bold(), + 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,7 +169,9 @@ impl DependencyManager { lock = DependencyLock::create(selection); // writes the lock file down - let mut lock_file = File::create(target_path.join("../Lingo.lock"))?; + 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()))?; println!("{:?}", lock.dependencies); let serialized_toml = toml::to_string(&lock).expect("cannot generate toml"); @@ -168,7 +199,12 @@ 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() { @@ -216,12 +252,32 @@ impl DependencyManager { fs::create_dir_all(&temporary_path)?; // cloning the specified package + println!( + "{} fetching dependency {} into {}", + "Build step:".green().bold(), + name, + temporary_path.display() + ); package.fetch(&temporary_path, git_clone_and_checkout_cap)?; + println!( + "{} computing checksum for {}", + "Build step:".green().bold(), + temporary_path.display() + ); + let checksum_started_at = Instant::now(); let hash = sha1dir::checksum_current_dir(&temporary_path, false); + println!( + "{} checksum complete for {} in {:?}", + "Build step:".green().bold(), + 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); 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..1431427 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -47,8 +47,10 @@ pub fn delete_subdirs(path_root: &Path, subdirs: &[&str]) -> io::Result<()> { 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(_) => println!("Deleted {}", buf.display()), + Err(err) => eprintln!("Failed to delete {}: {}", buf.display(), err), + } } buf.pop(); } From 6a4700ceb48282c29a74132188667be8b821dbec Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Tue, 21 Apr 2026 09:22:43 -0700 Subject: [PATCH 2/9] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2c05112..8ca97c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -93,11 +93,21 @@ fn do_read_to_string(p: &Path) -> io::Result { fn remove_if_exists(path: &Path) -> io::Result<()> { if path.is_dir() { - println!("Deleted {}", path.display()); - fs::remove_dir_all(path)?; + match fs::remove_dir_all(path) { + Ok(()) => println!("Deleted {}", path.display()), + Err(err) => { + eprintln!("Failed to delete {}: {}", path.display(), err); + return Err(err); + } + } } else if path.is_file() { - println!("Deleted {}", path.display()); - fs::remove_file(path)?; + match fs::remove_file(path) { + Ok(()) => println!("Deleted {}", path.display()), + Err(err) => { + eprintln!("Failed to delete {}: {}", path.display(), err); + return Err(err); + } + } } Ok(()) } From 026c551846d13bdfbe833a12c063872e98ad0c6f Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Tue, 21 Apr 2026 09:24:32 -0700 Subject: [PATCH 3/9] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8ca97c7..7029db6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -115,10 +115,6 @@ fn remove_if_exists(path: &Path) -> io::Result<()> { fn clean_all(project_root: &Path) -> BuildResult { remove_if_exists(&project_root.join("build"))?; remove_if_exists(&project_root.join("Lingo.lock"))?; - remove_if_exists(&project_root.join("src-gen"))?; - remove_if_exists(&project_root.join("bin"))?; - remove_if_exists(&project_root.join("fed-gen"))?; - remove_if_exists(&project_root.join("include"))?; Ok(()) } From 233fc1aef737c562a5567c80fbab435d8824b7d8 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Tue, 21 Apr 2026 09:29:46 -0700 Subject: [PATCH 4/9] Respond to Copilot review --- README.md | 2 +- src/backends/mod.rs | 8 ++++---- src/main.rs | 35 +++++++++++++++++++++++-------- src/package/lock.rs | 34 ++++++++++--------------------- src/package/management.rs | 43 ++++++++++++++++----------------------- src/util/mod.rs | 6 ++++-- 6 files changed, 65 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 92e41c5..d3c9617 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Commands: update Update the dependencies and potentially build tools run Build and run binaries clean Remove build artifacts - cleanall Remove build artifacts, dependencies, and lfc build artifacts + cleanall Remove build artifacts and dependencies help Print this message or the help of the given subcommand(s) Options: diff --git a/src/backends/mod.rs b/src/backends/mod.rs index cab03c4..25cfa28 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -31,9 +31,9 @@ pub fn execute_command<'a>( match command { CommandSpec::Build(_build) => { - println!( + log::info!( "{} starting dependency resolution ({} declared dependencies)", - "Build step:".to_string(), + "Build step:", dependencies.len() ); let manager = match DependencyManager::from_dependencies( @@ -49,7 +49,7 @@ pub fn execute_command<'a>( }; // enriching the apps with the target properties from the libraries - println!("Build step: merging dependency target properties"); + log::info!("Build step: merging dependency target properties"); let library_properties = manager.get_target_properties().expect("lib properties"); // merging app with library target properties @@ -73,7 +73,7 @@ pub fn execute_command<'a>( } for (build_system, apps) in by_build_system { - println!( + log::info!( "Build step: dispatching {} app(s) to {:?}/{:?}", apps.len(), build_system.0, diff --git a/src/main.rs b/src/main.rs index 7029db6..d03c58e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -94,17 +94,17 @@ fn do_read_to_string(p: &Path) -> io::Result { fn remove_if_exists(path: &Path) -> io::Result<()> { if path.is_dir() { match fs::remove_dir_all(path) { - Ok(()) => println!("Deleted {}", path.display()), + Ok(()) => log::info!("Deleted {}", path.display()), Err(err) => { - eprintln!("Failed to delete {}: {}", path.display(), err); + log::error!("Failed to delete {}: {}", path.display(), err); return Err(err); } } } else if path.is_file() { match fs::remove_file(path) { - Ok(()) => println!("Deleted {}", path.display()), + Ok(()) => log::info!("Deleted {}", path.display()), Err(err) => { - eprintln!("Failed to delete {}: {}", path.display(), err); + log::error!("Failed to delete {}: {}", path.display(), err); return Err(err); } } @@ -119,9 +119,19 @@ fn clean_all(project_root: &Path) -> BuildResult { } 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. @@ -143,6 +153,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), ); @@ -188,6 +199,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> { @@ -215,9 +227,16 @@ fn execute_command<'a>( CommandResult::Batch(run_command(CommandSpec::Clean, config, true)) } (_, ConsoleCommand::Cleanall) => { - let cwd = env::current_dir() - .map_err(|e| -> Box { Box::new(e) }); - CommandResult::Single(cwd.and_then(|path| clean_all(&path))) + let 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(clean_all); + CommandResult::Single(result) } _ => todo!(), } diff --git a/src/package/lock.rs b/src/package/lock.rs index 7e4857a..1850d54 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; @@ -214,9 +213,8 @@ impl DependencyLock { lfc_include_folder: &Path, git_clone_and_checkout_cap: &GitCloneAndCheckoutCap, ) -> anyhow::Result<()> { - println!( - "{} checking lock entries in {}", - "Build step:".green().bold(), + info!( + "Build step: checking lock entries in {}", lfc_include_folder.display() ); for (_, lock) in self.dependencies.iter() { @@ -224,12 +222,9 @@ impl DependencyLock { // 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)?; - println!( - "{} {} from {}+{}", - "Fetching".green().bold(), - lock.name, - lock.source.source_type, - lock.source.uri + info!( + "Fetching {} from {}+{}", + lock.name, lock.source.source_type, lock.source.uri ); details @@ -237,17 +232,15 @@ impl DependencyLock { .expect("cannot pull package"); } - println!( - "{} computing checksum for locked dependency {} at {}", - "Build step:".green().bold(), + info!( + "Build step: computing checksum for locked dependency {} at {}", lock.name, temp.display() ); let checksum_started_at = Instant::now(); let hash = sha1dir::checksum_current_dir(&temp, false); - println!( - "{} checksum complete for {} in {:?}", - "Build step:".green().bold(), + info!( + "Build step: checksum complete for {} in {:?}", lock.name, checksum_started_at.elapsed() ); @@ -259,12 +252,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 0845371..b54a144 100644 --- a/src/package/management.rs +++ b/src/package/management.rs @@ -1,6 +1,5 @@ use anyhow::Context; -use colored::Colorize; -use log::error; +use log::{error, info}; use versions::{Requirement, Versioning}; use crate::util::sha1dir; @@ -104,9 +103,8 @@ impl DependencyManager { target_path: &Path, git_clone_and_checkout_cap: &GitCloneAndCheckoutCap, ) -> anyhow::Result { - println!( - "{} resolving dependencies in {}", - "Build step:".green().bold(), + info!( + "Build step: resolving dependencies in {}", target_path.display() ); // create library folder @@ -124,11 +122,7 @@ impl DependencyManager { // checks if a Lingo.lock file exists if lock_file.exists() { - println!( - "{} loading lock file {}", - "Build step:".green().bold(), - lock_file.display() - ); + info!("Build step: loading lock file {}", lock_file.display()); // reads and parses Lockfile lock = toml::from_str::(&fs::read_to_string(&lock_file).with_context( @@ -138,9 +132,8 @@ impl DependencyManager { // if a lock file is present it will load the dependencies from it and checks // integrity of the build directory - println!( - "{} validating lock dependencies in {}", - "Build step:".green().bold(), + 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) @@ -173,7 +166,10 @@ impl DependencyManager { let mut lock_file = File::create(&lock_file_path) .with_context(|| format!("failed to create lock file: {}", lock_file_path.display()))?; - println!("{:?}", lock.dependencies); + 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())?; @@ -208,7 +204,7 @@ impl DependencyManager { 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, @@ -252,24 +248,21 @@ impl DependencyManager { fs::create_dir_all(&temporary_path)?; // cloning the specified package - println!( - "{} fetching dependency {} into {}", - "Build step:".green().bold(), + info!( + "Build step: fetching dependency {} into {}", name, temporary_path.display() ); package.fetch(&temporary_path, git_clone_and_checkout_cap)?; - println!( - "{} computing checksum for {}", - "Build step:".green().bold(), + info!( + "Build step: computing checksum for {}", temporary_path.display() ); let checksum_started_at = Instant::now(); let hash = sha1dir::checksum_current_dir(&temporary_path, false); - println!( - "{} checksum complete for {} in {:?}", - "Build step:".green().bold(), + info!( + "Build step: checksum complete for {} in {:?}", temporary_path.display(), checksum_started_at.elapsed() ); @@ -280,7 +273,7 @@ impl DependencyManager { .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/util/mod.rs b/src/util/mod.rs index 1431427..ea0e98d 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -42,14 +42,16 @@ 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() { match fs::remove_dir_all(&buf) { - Ok(_) => println!("Deleted {}", buf.display()), - Err(err) => eprintln!("Failed to delete {}: {}", buf.display(), err), + Ok(_) => log::info!("Deleted {}", buf.display()), + Err(err) => log::error!("Failed to delete {}: {}", buf.display(), err), } } buf.pop(); From 720d5e781c86997503f00241151cc5ed04edb705 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Tue, 21 Apr 2026 09:31:29 -0700 Subject: [PATCH 5/9] Tuned README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d3c9617..2e0ebde 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Commands: update Update the dependencies and potentially build tools run Build and run binaries clean Remove build artifacts - cleanall Remove build artifacts and dependencies + cleanall Remove build artifacts, dependencies, and lock file help Print this message or the help of the given subcommand(s) Options: From 76b88fde05fdcd322911a67de8177052f99a30af Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Tue, 21 Apr 2026 22:38:53 -0700 Subject: [PATCH 6/9] Removing timing of checksum --- src/package/lock.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/package/lock.rs b/src/package/lock.rs index 1850d54..2296475 100644 --- a/src/package/lock.rs +++ b/src/package/lock.rs @@ -237,12 +237,10 @@ impl DependencyLock { lock.name, temp.display() ); - let checksum_started_at = Instant::now(); let hash = sha1dir::checksum_current_dir(&temp, false); info!( - "Build step: checksum complete for {} in {:?}", - lock.name, - checksum_started_at.elapsed() + "Build step: checksum complete for {}", + lock.name ); if hash.to_string() != lock.checksum { From 5f27298825390a8e8433673fed179f9f12242984 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 4 May 2026 10:55:12 -0700 Subject: [PATCH 7/9] Remove cleanall and replace with update --- README.md | 3 +-- src/README.md | 2 +- src/args.rs | 5 +---- src/main.rs | 36 ++++++++++++++++++++++++++++++------ src/package/lock.rs | 1 - 5 files changed, 33 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 2e0ebde..0e7c798 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,9 @@ 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 + update Remove build artifacts, update the dependencies, and build run Build and run binaries clean Remove build artifacts - cleanall Remove build artifacts, dependencies, and lock file help Print this message or the help of the given subcommand(s) Options: 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/args.rs b/src/args.rs index 6b6d3be..6047ba1 100644 --- a/src/args.rs +++ b/src/args.rs @@ -121,10 +121,7 @@ pub enum Command { Run(BuildArgs), /// removes build artifacts - Clean, - - /// removes build artifacts, installed dependencies, lock file, and lfc build artifacts - Cleanall, + Clean } #[derive(Parser)] diff --git a/src/main.rs b/src/main.rs index d03c58e..0ab2159 100644 --- a/src/main.rs +++ b/src/main.rs @@ -112,9 +112,17 @@ fn remove_if_exists(path: &Path) -> io::Result<()> { Ok(()) } -fn clean_all(project_root: &Path) -> BuildResult { +fn update(project_root: &Path) -> BuildResult { remove_if_exists(&project_root.join("build"))?; - remove_if_exists(&project_root.join("Lingo.lock"))?; + 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(()) } @@ -226,8 +234,8 @@ fn execute_command<'a>( (Some(config), ConsoleCommand::Clean) => { CommandResult::Batch(run_command(CommandSpec::Clean, config, true)) } - (_, ConsoleCommand::Cleanall) => { - let result = lingo_path + (Some(config), ConsoleCommand::Update) => { + let update_result = lingo_path .and_then(Path::parent) .ok_or_else(|| { Box::new(io::Error::new( @@ -235,8 +243,24 @@ fn execute_command<'a>( "Error: Missing Lingo.toml file", )) as Box }) - .and_then(clean_all); - CommandResult::Single(result) + .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)) + } + } } _ => todo!(), } diff --git a/src/package/lock.rs b/src/package/lock.rs index 2296475..f6051b1 100644 --- a/src/package/lock.rs +++ b/src/package/lock.rs @@ -11,7 +11,6 @@ use std::fmt::Display; use std::fs; use std::path::{Path, PathBuf}; use std::str::FromStr; -use std::time::Instant; use crate::GitCloneAndCheckoutCap; From c38092e99c28530a8e140dfda093d21215fca68c Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 4 May 2026 10:56:49 -0700 Subject: [PATCH 8/9] Removed todo which triggers a warning --- src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 0ab2159..8668224 100644 --- a/src/main.rs +++ b/src/main.rs @@ -262,7 +262,6 @@ fn execute_command<'a>( } } } - _ => todo!(), } } From 7b8352451b00f9b46a59b91b8d021eb125bcf946 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 4 May 2026 11:05:17 -0700 Subject: [PATCH 9/9] Format --- src/args.rs | 2 +- src/package/lock.rs | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/args.rs b/src/args.rs index 6047ba1..65ac4c4 100644 --- a/src/args.rs +++ b/src/args.rs @@ -121,7 +121,7 @@ pub enum Command { Run(BuildArgs), /// removes build artifacts - Clean + Clean, } #[derive(Parser)] diff --git a/src/package/lock.rs b/src/package/lock.rs index f6051b1..4a2ff28 100644 --- a/src/package/lock.rs +++ b/src/package/lock.rs @@ -237,10 +237,7 @@ impl DependencyLock { temp.display() ); let hash = sha1dir::checksum_current_dir(&temp, false); - info!( - "Build step: checksum complete for {}", - lock.name - ); + info!("Build step: checksum complete for {}", lock.name); if hash.to_string() != lock.checksum { error!("checksum does not match aborting!");