diff --git a/Cargo.lock b/Cargo.lock index ac1039ae..7a2c95c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2282,7 +2282,7 @@ dependencies = [ [[package]] name = "kit" -version = "3.1.0" +version = "3.1.1" dependencies = [ "alloy", "alloy-sol-macro", diff --git a/Cargo.toml b/Cargo.toml index 5c97295c..d9d39d7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "kit" authors = ["Sybil Technologies AG"] -version = "3.1.0" +version = "3.1.1" edition = "2021" description = "Development toolkit for Hyperware" homepage = "https://hyperware.ai" diff --git a/src/build/caller_utils_generator.rs b/src/build/caller_utils_generator.rs index a45755cf..d8ecce01 100644 --- a/src/build/caller_utils_generator.rs +++ b/src/build/caller_utils_generator.rs @@ -403,10 +403,20 @@ fn generate_async_function(signature: &SignatureStruct) -> Option { // Create the caller-utils crate with a single lib.rs file #[instrument(level = "trace", skip_all)] fn create_caller_utils_crate(api_dir: &Path, base_dir: &Path) -> Result<()> { + // Extract package name from base directory + let package_name = base_dir + .file_name() + .and_then(|n| n.to_str()) + .ok_or_else(|| eyre!("Could not extract package name from base directory"))?; + + // Create crate name by prepending package name + let crate_name = format!("{}-caller-utils", package_name); + // Path to the new crate - let caller_utils_dir = base_dir.join("target").join("caller-utils"); + let caller_utils_dir = base_dir.join("target").join(&crate_name); debug!( path = %caller_utils_dir.display(), + crate_name = %crate_name, "Creating caller-utils crate" ); @@ -422,7 +432,7 @@ fn create_caller_utils_crate(api_dir: &Path, base_dir: &Path) -> Result<()> { // Create Cargo.toml with updated dependencies let cargo_toml = format!( r#"[package] -name = "caller-utils" +name = "{}" version = "0.1.0" edition = "2021" publish = false @@ -442,13 +452,14 @@ wit-bindgen = "0.41.0" [lib] crate-type = ["cdylib", "lib"] "#, + crate_name.replace("-", "_"), hyperware_dep ); fs::write(caller_utils_dir.join("Cargo.toml"), cargo_toml) - .with_context(|| "Failed to write caller-utils Cargo.toml")?; + .with_context(|| format!("Failed to write {} Cargo.toml", crate_name))?; - debug!("Created Cargo.toml for caller-utils"); + debug!("Created Cargo.toml for {}", crate_name); // Get the world name (preferably the types- version) let world_names = find_world_names(api_dir)?; @@ -787,7 +798,7 @@ fn get_hyperware_process_lib_dependency(base_dir: &Path) -> Result { // Update workspace Cargo.toml to include the caller-utils crate #[instrument(level = "trace", skip_all)] -fn update_workspace_cargo_toml(base_dir: &Path) -> Result<()> { +fn update_workspace_cargo_toml(base_dir: &Path, crate_name: &str) -> Result<()> { let workspace_cargo_toml = base_dir.join("Cargo.toml"); debug!( path = %workspace_cargo_toml.display(), @@ -819,12 +830,15 @@ fn update_workspace_cargo_toml(base_dir: &Path) -> Result<()> { if let Some(members) = workspace.get_mut("members") { if let Some(members_array) = members.as_array_mut() { // Check if caller-utils is already in the members list + // Using a `?` forces cargo to interpret it as optional, which allows building from scratch (i.e. before caller-utils has been generated) + let crate_name_without_s = crate_name.trim_end_matches('s'); + let target_path = format!("target/{}?", crate_name_without_s); let caller_utils_exists = members_array .iter() - .any(|m| m.as_str().map_or(false, |s| s == "target/caller-util?")); + .any(|m| m.as_str().map_or(false, |s| s == target_path)); if !caller_utils_exists { - members_array.push(Value::String("target/caller-util?".to_string())); + members_array.push(Value::String(target_path.clone())); // Write back the updated TOML let updated_content = toml::to_string_pretty(&parsed_toml) @@ -840,7 +854,8 @@ fn update_workspace_cargo_toml(base_dir: &Path) -> Result<()> { debug!("Successfully updated workspace Cargo.toml"); } else { debug!( - "Workspace Cargo.toml already up-to-date regarding caller-util? member." + "Workspace Cargo.toml already up-to-date regarding {} member.", + target_path ); } } @@ -852,7 +867,16 @@ fn update_workspace_cargo_toml(base_dir: &Path) -> Result<()> { // Add caller-utils as a dependency to hyperware:process crates #[instrument(level = "trace", skip_all)] -pub fn add_caller_utils_to_projects(projects: &[PathBuf]) -> Result<()> { +pub fn add_caller_utils_to_projects(projects: &[PathBuf], base_dir: &Path) -> Result<()> { + // Extract package name from base directory + let package_name = base_dir + .file_name() + .and_then(|n| n.to_str()) + .ok_or_else(|| eyre!("Could not extract package name from base directory"))?; + + // Create crate name by prepending package name + let crate_name = format!("{}-caller-utils", package_name); + let crate_name_underscore = crate_name.replace("-", "_"); for project_path in projects { let cargo_toml_path = project_path.join("Cargo.toml"); debug!( @@ -878,42 +902,75 @@ pub fn add_caller_utils_to_projects(projects: &[PathBuf]) -> Result<()> { // Add caller-utils to dependencies if not already present if let Some(dependencies) = parsed_toml.get_mut("dependencies") { if let Some(deps_table) = dependencies.as_table_mut() { - if !deps_table.contains_key("caller-utils") { + if !deps_table.contains_key(&crate_name_underscore) { deps_table.insert( - "caller-utils".to_string(), + crate_name_underscore.clone(), Value::Table({ let mut t = toml::map::Map::new(); t.insert( "path".to_string(), - Value::String("../target/caller-utils".to_string()), + Value::String(format!("../target/{}", crate_name)), ); t.insert("optional".to_string(), Value::Boolean(true)); t }), ); - // Write back the updated TOML - let updated_content = - toml::to_string_pretty(&parsed_toml).with_context(|| { - format!( - "Failed to serialize updated project Cargo.toml: {}", - cargo_toml_path.display() - ) - })?; - - fs::write(&cargo_toml_path, updated_content).with_context(|| { - format!( - "Failed to write updated project Cargo.toml: {}", - cargo_toml_path.display() - ) - })?; + debug!(project = ?project_path.file_name().unwrap_or_default(), "Successfully added {} dependency", crate_name_underscore); + } else { + debug!(project = ?project_path.file_name().unwrap_or_default(), "{} dependency already exists", crate_name_underscore); + } + } + } + + // Add or update the features section to include caller-utils feature + if !parsed_toml.as_table().unwrap().contains_key("features") { + parsed_toml + .as_table_mut() + .unwrap() + .insert("features".to_string(), Value::Table(toml::map::Map::new())); + } - debug!(project = ?project_path.file_name().unwrap_or_default(), "Successfully added caller-utils dependency"); + if let Some(features) = parsed_toml.get_mut("features") { + if let Some(features_table) = features.as_table_mut() { + // Add caller-utils feature that enables the package-specific caller-utils dependency + if !features_table.contains_key("caller-utils") { + features_table.insert( + "caller-utils".to_string(), + Value::Array(vec![Value::String(crate_name_underscore.clone())]), + ); + debug!(project = ?project_path.file_name().unwrap_or_default(), "Added caller-utils feature"); } else { - debug!(project = ?project_path.file_name().unwrap_or_default(), "caller-utils dependency already exists"); + // Update existing caller-utils feature if it doesn't include our dependency + if let Some(caller_utils_feature) = features_table.get_mut("caller-utils") { + if let Some(feature_array) = caller_utils_feature.as_array_mut() { + let dep_exists = feature_array + .iter() + .any(|v| v.as_str().map_or(false, |s| s == crate_name_underscore)); + if !dep_exists { + feature_array.push(Value::String(crate_name_underscore.clone())); + debug!(project = ?project_path.file_name().unwrap_or_default(), "Updated caller-utils feature to include {}", crate_name_underscore); + } + } + } } } } + + // Write back the updated TOML + let updated_content = toml::to_string_pretty(&parsed_toml).with_context(|| { + format!( + "Failed to serialize updated project Cargo.toml: {}", + cargo_toml_path.display() + ) + })?; + + fs::write(&cargo_toml_path, updated_content).with_context(|| { + format!( + "Failed to write updated project Cargo.toml: {}", + cargo_toml_path.display() + ) + })?; } Ok(()) @@ -922,12 +979,21 @@ pub fn add_caller_utils_to_projects(projects: &[PathBuf]) -> Result<()> { // Create caller-utils crate and integrate with the workspace #[instrument(level = "trace", skip_all)] pub fn create_caller_utils(base_dir: &Path, api_dir: &Path) -> Result<()> { + // Extract package name from base directory + let package_name = base_dir + .file_name() + .and_then(|n| n.to_str()) + .ok_or_else(|| eyre!("Could not extract package name from base directory"))?; + + // Create crate name by prepending package name + let crate_name = format!("{}-caller-utils", package_name); + // Step 1: Create the caller-utils crate create_caller_utils_crate(api_dir, base_dir)?; // Step 2: Update workspace Cargo.toml - update_workspace_cargo_toml(base_dir)?; + update_workspace_cargo_toml(base_dir, &crate_name)?; - info!("Successfully created caller-utils and copied the imports"); + info!("Successfully created {} and copied the imports", crate_name); Ok(()) } diff --git a/src/build/mod.rs b/src/build/mod.rs index 6d98ffe1..bd2c0967 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -1649,7 +1649,10 @@ async fn compile_package( if let Some(ref processed_projects) = hyperapp_processed_projects { caller_utils_generator::create_caller_utils(package_dir, &api_dir)?; for processed_project in processed_projects { - caller_utils_generator::add_caller_utils_to_projects(&[processed_project.clone()])?; + caller_utils_generator::add_caller_utils_to_projects( + &[processed_project.clone()], + package_dir, + )?; } } diff --git a/src/clean/mod.rs b/src/clean/mod.rs new file mode 100644 index 00000000..b763de2b --- /dev/null +++ b/src/clean/mod.rs @@ -0,0 +1,64 @@ +use crate::build::run_command; +use color_eyre::eyre::{eyre, Result}; +use fs_err as fs; +use std::path::Path; +use std::process::Command; +use tracing::{info, instrument}; + +#[instrument(level = "trace", skip_all)] +pub fn execute( + package_dir: &Path, + release: bool, + profile: Option<&str>, + targets: Vec, + packages: Vec, + dry_run: bool, + verbose: bool, +) -> Result<()> { + let package_dir = fs::canonicalize(package_dir)?; + + if !package_dir.exists() { + let error = format!("Directory {:?} doesnt exists.", package_dir); + return Err(eyre!(error)); + } + + info!("Removing 'target' folder from {:?}...", package_dir); + + let mut args: Vec = vec!["clean", "--target-dir", "target"] + .iter() + .map(|v| v.to_string()) + .collect(); + + if release { + args.push("--release".to_string()); + } + + if let Some(prof) = profile { + args.push("--profile".to_string()); + args.push(prof.to_string()); + } + + for target in targets { + args.push("--target".to_string()); + args.push(target); + } + + for package in packages { + args.push("--package".to_string()); + args.push(package); + } + + if dry_run { + args.push("--dry-run".to_string()); + } + + run_command( + Command::new("cargo") + .args(&args[..]) + .current_dir(&package_dir), + verbose, + )?; + + info!("Done removing 'target' folder from {:?}.", package_dir); + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 0dc17a8c..14bc8c41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub mod boot_real_node; pub mod build; pub mod build_start_package; pub mod chain; +pub mod clean; pub mod connect; pub mod dev_ui; pub mod inject_message; diff --git a/src/main.rs b/src/main.rs index 50957dd2..371a27bc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,11 @@ use color_eyre::{ Section, }; use fs_err as fs; +use kit::{ + boot_fake_node, boot_real_node, build, build_start_package, chain, clean, connect, dev_ui, + inject_message, new, publish, remove_package, reset_cache, run_tests, setup, start_package, + update, view_api, KIT_LOG_PATH_DEFAULT, +}; use serde::Deserialize; use tracing::{error, instrument, warn, Level}; use tracing_error::ErrorLayer; @@ -16,12 +21,6 @@ use tracing_subscriber::{ filter, fmt, layer::SubscriberExt, prelude::*, util::SubscriberInitExt, EnvFilter, }; -use kit::{ - boot_fake_node, boot_real_node, build, build_start_package, chain, connect, dev_ui, - inject_message, new, publish, remove_package, reset_cache, run_tests, setup, start_package, - update, view_api, KIT_LOG_PATH_DEFAULT, -}; - const MAX_REMOTE_VALUES: usize = 3; const GIT_COMMIT_HASH: &str = env!("GIT_COMMIT_SHA"); const GIT_BRANCH_NAME: &str = env!("GIT_BRANCH_NAME"); @@ -206,6 +205,35 @@ async fn execute( ) .await } + + Some(("clean", matches)) => { + let package_dir = PathBuf::from(matches.get_one::("DIR").unwrap()); + let release = matches.get_one::("RELEASE").unwrap(); + let profile = matches.get_one::("PROFILE"); + let targets: Vec = matches + .get_many::("TARGET") + .unwrap_or_default() + .map(|s| s.to_string()) + .collect(); + let packages: Vec = matches + .get_many::("PACKAGE") + .unwrap_or_default() + .map(|s| s.to_string()) + .collect(); + let dry_run = matches.get_one::("DRY_RUN").unwrap(); + let verbose = matches.get_one::("VERBOSE").unwrap(); + + clean::execute( + &package_dir, + *release, + profile.map(|s| s.as_str()), + targets, + packages, + *dry_run, + *verbose, + ) + } + Some(("build", matches)) => { let package_dir = PathBuf::from(matches.get_one::("DIR").unwrap()); let no_ui = matches.get_one::("NO_UI").unwrap(); @@ -704,6 +732,63 @@ async fn make_app(current_dir: &std::ffi::OsString) -> Result { .required(false) ) ) + .subcommand( + Command::new("clean") + .about("Remove the target directory") + .arg( + Arg::new("DIR") + .action(ArgAction::Set) + .help("The package directory to clean") + .default_value(current_dir) + .required(false), + ) + .arg( + Arg::new("RELEASE") + .action(ArgAction::SetTrue) + .short('r') + .long("release") + .help("Whether or not to clean release artifacts") + .required(false), + ) + .arg( + Arg::new("PROFILE") + .action(ArgAction::Set) + .long("profile") + .help("Clean artifacts of the specified profile") + .required(false), + ) + .arg( + Arg::new("TARGET") + .action(ArgAction::Append) + .long("target") + .help("Target triple to clean output for (can specify multiple times)") + .required(false), + ) + .arg( + Arg::new("PACKAGE") + .action(ArgAction::Append) + .short('p') + .long("package") + .help("Package to clean artifacts for (can specify multiple times)") + .required(false), + ) + .arg( + Arg::new("DRY_RUN") + .action(ArgAction::SetTrue) + .short('n') + .long("dry-run") + .help("Display what would be deleted without deleting anything") + .required(false), + ) + .arg( + Arg::new("VERBOSE") + .action(ArgAction::SetTrue) + .short('v') + .long("verbose") + .help("If set, output stdout and stderr") + .required(false), + ), + ) .subcommand(Command::new("build") .about("Build a Hyperware package") .visible_alias("b")