From e2fa7d7226961a628423108dc4d449d15d23eb4c Mon Sep 17 00:00:00 2001 From: Petr Gadorek Date: Wed, 26 Jun 2024 16:36:48 +0200 Subject: [PATCH] switched from print! to logging and added verbose flag --- .cargo/config.toml | 5 +- Cargo.lock | 37 ++++++++++++++ Cargo.toml | 2 + src/cli_args/mod.rs | 94 +++++++++-------------------------- src/main.rs | 9 ++-- src/wizard/mod.rs | 116 ++++++++++++++++---------------------------- 6 files changed, 116 insertions(+), 147 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 8255e5b..999f495 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -6,4 +6,7 @@ linker = "/usr/bin/i686-w64-mingw32-gcc" ar = "/usr/i686-w64-mingw32/bin/ar" [target.aarch64-pc-windows-msvc] -linker = "x86_64-w64-mingw32-gcc" \ No newline at end of file +linker = "x86_64-w64-mingw32-gcc" + +[target.'cfg(all(windows, target_env = "msvc"))'] +rustflags = ["-C", "target-feature=+crt-static"] \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index e8fc42f..5b93a6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1687,10 +1687,12 @@ dependencies = [ "git2", "idf-im-lib", "indicatif", + "log", "ratatui", "ratatui-splash-screen", "rfd", "serde", + "simple_logger", "tokio", "walkdir", ] @@ -2407,6 +2409,15 @@ dependencies = [ "syn 2.0.65", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "number_prefix" version = "0.4.0" @@ -3878,6 +3889,18 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +[[package]] +name = "simple_logger" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c5dfa5e08767553704aa0ffd9d9794d527103c736aba9854773851fd7497eb" +dependencies = [ + "colored", + "log", + "time", + "windows-sys 0.48.0", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -4145,10 +4168,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", + "itoa", + "libc", "num-conv", + "num_threads", "powerfmt", "serde", "time-core", + "time-macros", ] [[package]] @@ -4157,6 +4184,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "timsort" version = "0.1.3" diff --git a/Cargo.toml b/Cargo.toml index 888d74c..9784b08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,5 @@ console = "0.15.8" config = "0.14.0" serde = { version = "1.0", features = ["derive"] } git2 = "0.19.0" +log = "0.4.21" +simple_logger = "5.0.0" diff --git a/src/cli_args/mod.rs b/src/cli_args/mod.rs index f89584b..766a03e 100644 --- a/src/cli_args/mod.rs +++ b/src/cli_args/mod.rs @@ -1,10 +1,11 @@ use clap::Parser; +use clap::{arg, ValueEnum}; use config::{Config, ConfigError, File}; +use log::error; use serde::Deserialize; +use simple_logger::SimpleLogger; use std::path::PathBuf; use std::{fmt, str::FromStr}; -// use clap::{command, Parser, ValueEnum}; -use clap::{arg, value_parser, Command, ValueEnum}; const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -54,6 +55,14 @@ pub struct Cli { tools_json_file: Option, #[arg(short, long)] non_interactive: Option, + + #[arg( + short, + long, + action = clap::ArgAction::Count, + help = "Increase verbosity level (can be used multiple times)" + )] + verbose: u8, } impl IntoIterator for Cli { @@ -99,6 +108,18 @@ impl IntoIterator for Cli { impl Settings { pub fn new() -> Result { let cli = Cli::parse(); + let log_level = match cli.verbose { + 0 => log::LevelFilter::Warn, + 1 => log::LevelFilter::Info, + 2 => log::LevelFilter::Debug, + _ => log::LevelFilter::Trace, + }; + match SimpleLogger::new().with_level(log_level).init() { + Ok(_) => {} + Err(e) => { + error!("Failed to initialize logger: {}", e); + } + } let mut builder = Config::builder() // Start off by merging in the "default" configuration file @@ -114,7 +135,7 @@ impl Settings { } // Add in settings from the environment (with a prefix of ESP) - // Eg.. `ESP_DEBUG=1 ./target/app` would set the `debug` key + // Eg.. `ESP_TARGET=esp32` would set the `target` key builder = builder.add_source(config::Environment::with_prefix("ESP").separator("_")); // Now that we've gathered all our config sources, let's merge them @@ -214,70 +235,3 @@ impl ValueEnum for ChipId { }) } } - -pub fn get_cli() -> clap::Command { - Command::new("ESP-IDF Installation Manager") - .version(VERSION) - .about("All you need to manage your ESP-IDF installations") - .arg( - arg!( - -p --path "base instalation path" - ) - .required(false) - .value_parser(value_parser!(PathBuf)), - ) - .arg( - arg!( - -t --target "which chip you are using" - ) - .required(false) - .value_parser(value_parser!(ChipId)), - ) - .arg( - arg!( - --"idf-version" "which version of idf we want to install" - ) - .required(false) - .value_parser(value_parser!(String)), - ) - .arg( - arg!( - -c --"config-file" "path to file with instalator configuration" - ) - .required(false) - .value_parser(value_parser!(PathBuf)), - ) - .arg( - arg!( - -n --"non-interactive" "show the wizard" - ) - .required(false) - .value_parser(value_parser!(bool)) - .default_value(std::ffi::OsStr::new("false")), - ) -} - -pub fn parse_cli(arg_matches: &clap::ArgMatches) -> Settings { - let mut config = Settings::default(); - - // we need to parse config file first, because cli params have higher priority - // TODO: we shoud parse env even before - if let Some(config_file) = arg_matches.get_one::("config-file") { - println!("Value for config_file: {:?}", config_file); - println!("Parsing config file not implemented yet"); - } - - if let Some(path) = arg_matches.get_one::("path") { - config.path = Some(path.to_owned()); - } - if let Some(target) = arg_matches.get_one::("target") { - config.target = Some(target.to_string().to_lowercase()); - } - if let Some(idf_version) = arg_matches.get_one::("idf-version") { - config.idf_version = Some(idf_version.to_owned()); - } - if let Some(non_interactive) = arg_matches.get_one::("non-interactive") { - config.non_interactive = Some(*non_interactive); - } - config -} diff --git a/src/main.rs b/src/main.rs index 5aa7068..ebf7445 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +use log::{error, info}; +use simple_logger::SimpleLogger; extern crate idf_im_lib; mod cli_args; @@ -11,13 +13,14 @@ async fn main() { let result = wizard::run_wizzard_run(settings).await; match result { Ok(r) => { - println!("Successfully installed IDF {:?}", r); + info!("Wizard result: {:?}", r); + println!("Successfully installed IDF"); println!("Now you can start using IDF tools"); } - Err(err) => println!("Error: {}", err), + Err(err) => error!("Error: {}", err), } } - Err(err) => println!("Error: {}", err), + Err(err) => error!("Error: {}", err), } // next step is source the env vars diff --git a/src/wizard/mod.rs b/src/wizard/mod.rs index 49e48df..b2230f7 100644 --- a/src/wizard/mod.rs +++ b/src/wizard/mod.rs @@ -3,39 +3,15 @@ use console::Style; use dialoguer::theme::ColorfulTheme; use idf_im_lib::idf_tools::ToolsFile; use indicatif::{ProgressBar, ProgressState, ProgressStyle}; -use ratatui::prelude::*; -use ratatui_splash_screen::{SplashConfig, SplashScreen}; -use std::error::Error; +use log::{debug, error, info, trace, warn}; use std::fmt::Write; -use std::io::Stdout; use std::path::{Path, PathBuf}; use std::time::{Duration, Instant}; -use std::{env, fs, thread}; +use std::{env, fs}; // folder select use dialoguer::{Confirm, Input, Select}; use walkdir::{DirEntry, WalkDir}; -fn show_splash_screen( - terminal: &mut Terminal>, -) -> Result<(), Box> { - static SPLASH_CONFIG: SplashConfig = SplashConfig { - image_data: include_bytes!("../../assets/logo.png"), - sha256sum: Some("405a91875ca5c1247140c73cd80dbde1962b0f747330058c0989a324bb311d5f"), - render_steps: 10, - use_colors: true, - }; - - let mut splash_screen = SplashScreen::new(SPLASH_CONFIG)?; - while !splash_screen.is_rendered() { - terminal.draw(|frame| { - frame.render_widget(&mut splash_screen, frame.size()); - })?; - std::thread::sleep(Duration::from_millis(120)); - } - - Ok(()) -} - fn run_with_spinner(func: F) -> T where F: FnOnce() -> T, @@ -65,7 +41,7 @@ where let duration = start_time.elapsed(); // Print the duration - // println!("Function completed in: {:?}", duration); //TODO: move to debug + debug!("Function completed in: {:?}", duration); // Return the result result @@ -76,11 +52,11 @@ fn check_prerequisites() -> Result, String> { match unsatisfied_prerequisities { Ok(prerequisities) => { if prerequisities.is_empty() { - // println!("All prerequisities are satisfied!"); //TODO: move to interactive wizard + debug!("All prerequisities are satisfied!"); Ok(vec![]) } else { - // println!("The following prerequisities are not satisfied:"); - // println!("{:?}", prerequisities); + debug!("The following prerequisities are not satisfied:"); + debug!("{:?}", prerequisities); Ok(prerequisities.into_iter().map(|p| p.to_string()).collect()) } } @@ -108,7 +84,6 @@ async fn select_idf_version(target: &str, theme: &ColorfulTheme) -> Result Result Result { let _: Result = match idf_im_lib::ensure_path(&path.to_string()) { - Ok(_) => { - // println!("Path is ok"); - Ok("ok".to_string()) - } + Ok(_) => Ok("ok".to_string()), Err(err) => return Err(err.to_string()), // probably panic }; let progress_bar = ProgressBar::new(100); @@ -141,7 +113,6 @@ fn download_idf(path: &str, tag: &str) -> Result { let output = idf_im_lib::get_esp_idf_by_tag_name(&path.to_string(), &tag.to_string(), |stats| { - // println!("{}/{}", stats.received_objects(), stats.total_objects()); let current_progress = ((stats.received_objects() as f64) / (stats.total_objects() as f64)) * 100.0; progress_bar.set_position(current_progress as u64); @@ -168,12 +139,12 @@ async fn download_tools( selected_chip: String, destination_path: &str, ) -> Vec { - let pepa: Vec = tools_file + let tool_name_list: Vec = tools_file .tools .iter() .map(|tool| tool.name.clone()) .collect(); - print!("Downloading tools: {:?}", pepa); + info!("Downloading tools: {:?}", tool_name_list); let list = idf_im_lib::idf_tools::filter_tools_by_target( tools_file.tools, &selected_chip.to_lowercase(), @@ -185,7 +156,7 @@ async fn download_tools( panic!("Can not identify platfor for tools instalation. {:?}", err); } }; - // println!("Platform: {}", platform); + debug!("Python platform: {}", platform); let download_links = idf_im_lib::idf_tools::change_links_donwanload_mirror( idf_im_lib::idf_tools::get_download_link_by_platform(list, &platform), // Some("https://dl.espressif.com/github_assets"), // this switches mirror, should be parametrized @@ -193,18 +164,17 @@ async fn download_tools( ); let mut downloaded_tools: Vec = vec![]; for (tool_name, download_link) in download_links.iter() { - println!("Downloading tool: {}", tool_name); + info!("Downloading tool: {}", tool_name); let progress_bar = ProgressBar::new(download_link.size); progress_bar.set_style(ProgressStyle::with_template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({eta})").unwrap() .with_key("eta", |state: &ProgressState, w: &mut dyn Write| write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap()) .progress_chars("#>-")); - let update_progress = |amount_downloaded: u64, total_size: u64| { - // let current_progress = ((amount_downloaded as f64) / (total_size as f64)) * 100.0; + let update_progress = |amount_downloaded: u64, _total_size: u64| { progress_bar.set_position(amount_downloaded); }; - println!("Download link: {}", download_link.url); - println!("destination: {}", destination_path); + debug!("Download link: {}", download_link.url); + debug!("destination: {}", destination_path); let file_path = Path::new(&download_link.url); let filename: &str = file_path.file_name().unwrap().to_str().unwrap(); @@ -216,13 +186,12 @@ async fn download_tools( ) { Ok(true) => { downloaded_tools.push(filename.to_string()); // add it to the list for extraction even if it's already downloaded - println!("The file is already downloaded and the checksum matches."); + info!("The file is already downloaded and the checksum matches."); progress_bar.finish(); continue; } _ => { - println!("The checksum does not match or file was not avalible."); - // TODO: move to debug + debug!("The checksum does not match or file was not avalible."); } } @@ -232,11 +201,11 @@ async fn download_tools( Ok(_) => { downloaded_tools.push(filename.to_string()); progress_bar.finish(); - println!("Downloaded {}", tool_name); + info!("Downloaded {}", tool_name); } Err(err) => { - println!("Failed to download tool: {}", tool_name); - println!("Error: {:?}", err); + error!("Failed to download tool: {}", tool_name); + error!("Error: {:?}", err); panic!(); } } @@ -251,9 +220,9 @@ fn extract_tools(tools: Vec, source_path: &str, destination_path: &str) let out = idf_im_lib::decompress_archive(archive_path.to_str().unwrap(), destination_path); match out { Ok(_) => { - println!("extracted tool: {}", tool); + info!("extracted tool: {}", tool); } - Err(err) => println!("{:?}", err), // TODO: return error + Err(err) => warn!("{:?}", err), } } } @@ -271,7 +240,7 @@ fn python_sanity_check() -> Result<(), String> { } } if all_ok { - // println!("All good!") + debug!("Python sanity check passed."); Ok(()) } else { Err(" Your python does not meets the requirements!".to_string()) @@ -329,7 +298,7 @@ fn folder_select(path: &str) -> String { } pub async fn run_wizzard_run(mut config: Settings) -> Result<(), String> { - // println!("Config: {:?}", config); // TODO remove + debug!("Config entering wizard: {:?}", config); if let Some(non_interactive) = config.non_interactive { if non_interactive { @@ -347,24 +316,24 @@ pub async fn run_wizzard_run(mut config: Settings) -> Result<(), String> { match run_with_spinner::<_, Result, String>>(|| check_prerequisites()) { Ok(list) => { if list.is_empty() { - println!("All prerequisities are satisfied!"); + info!("All prerequisities are satisfied!"); } else { unimplemented!("Please install the following prerequisities: {:?}", list); //TODO: offer to install prerequisities } } Err(err) => { - println!("{:?}", err); + error!("{:?}", err); return Err(err); } } match run_with_spinner::<_, Result<(), String>>(|| python_sanity_check()) { Ok(_) => { - println!("Your python meets the requirements") + info!("Your python meets the requirements") } Err(err) => { - println!("{:?}", err); // python does not meets requirements: TODO: on windows proceeed with instalation of our python + error!("{:?}", err); // python does not meets requirements: TODO: on windows proceeed with instalation of our python return Err(err); } } @@ -373,20 +342,21 @@ pub async fn run_wizzard_run(mut config: Settings) -> Result<(), String> { let chip_id = match select_target(&theme).await { Ok(target) => target, Err(err) => { - println!("{:?}", err); + error!("{:?}", err); return Err(err); } }; config.target = Some(chip_id); } let target = config.target.clone().unwrap().to_string(); + debug!("Selected target: {}", target); // select version // TODO: verify the version support target if config.idf_version.is_none() { config.idf_version = Some(select_idf_version(&target, &theme).await.unwrap()); } let idf_versions = config.idf_version.unwrap(); - // let version: String = select_idf_version(&config.target).await.unwrap(); + debug!("Selected idf version: {}", idf_versions); // select folder // instalation path consist from base path and idf version let mut instalation_path: PathBuf = PathBuf::new(); @@ -414,10 +384,12 @@ pub async fn run_wizzard_run(mut config: Settings) -> Result<(), String> { // download idf match download_idf(&idf_path.to_str().unwrap(), &idf_versions) { - Ok(_) => {} + Ok(_) => { + debug!("idf downloaded sucessfully"); + } Err(err) => { // TODO: offer purging directory and retry - println!("Please choose valid instalation directory {:?}", err); + error!("Please choose valid instalation directory {:?}", err); return Err(err); } } @@ -443,7 +415,7 @@ pub async fn run_wizzard_run(mut config: Settings) -> Result<(), String> { match idf_im_lib::ensure_path(&tool_download_directory.display().to_string()) { Ok(_) => {} Err(err) => { - println!("{:?}", err); + error!("{:?}", err); return Err(err.to_string()); } } @@ -470,7 +442,7 @@ pub async fn run_wizzard_run(mut config: Settings) -> Result<(), String> { match idf_im_lib::ensure_path(&tool_install_directory.display().to_string()) { Ok(_) => {} Err(err) => { - println!("{:?}", err); + error!("{:?}", err); return Err(err.to_string()); } } @@ -500,10 +472,10 @@ pub async fn run_wizzard_run(mut config: Settings) -> Result<(), String> { // tools_json_file.push("tools"); // tools_json_file.push("tools.json"); - // println!("Tools json file: {}", tools_json_file.display()); + debug!("Tools json file: {}", tools_json_file.display()); if !fs::metadata(&tools_json_file).is_ok() { - println!("Tools.json file does not exist. Please select valied tools.json file"); + warn!("Tools.json file does not exist. Please select valied tools.json file"); unimplemented!(); // TODO: select tools.json file using file picker // TODO: implement the retry logic -> in interactive mode the user should not be able to proceed until the files is found } @@ -558,7 +530,7 @@ pub async fn run_wizzard_run(mut config: Settings) -> Result<(), String> { config.idf_tools_path = Some(name); } if !fs::metadata(&idf_tools_path).is_ok() { - println!("idf_tools.py file not found. Please select valid idf_tools.py file"); + warn!("idf_tools.py file not found. Please select valid idf_tools.py file"); unimplemented!(); // TODO: select idf_tools.py file using file picker } let out = run_with_spinner::<_, Result>(|| { @@ -570,8 +542,8 @@ pub async fn run_wizzard_run(mut config: Settings) -> Result<(), String> { ) }); match out { - Ok(_output) => { - // println!("{}", output) // if it's success we should onlyp rint the output to the debug + Ok(output) => { + trace!("idf_tools.py install output:\r\n{}", output) // if it's success we should onlyp rint the output to the debug } Err(err) => panic!("Failed to run idf tools: {:?}", err), } @@ -582,8 +554,8 @@ pub async fn run_wizzard_run(mut config: Settings) -> Result<(), String> { Some(&env_vars), ); match output { - Ok(_output) => { - // println!("{}", output) // if it's success we should onlyp rint the output to the debug + Ok(output) => { + trace!("idf_tools.py install-python-env output:\r\n{}", output) // if it's success we should onlyp rint the output to the debug } Err(err) => panic!("Failed to run idf tools: {:?}", err), } @@ -597,12 +569,10 @@ pub async fn run_wizzard_run(mut config: Settings) -> Result<(), String> { ); match output2 { Ok(output) => { - // println!("TADY: {}", output); let exports = env_vars .into_iter() .map(|(k, v)| format!("export {}=\"{}\"; ", k, v)) .collect::>(); - // println!("exportujeme: {}", exports.join("")); println!( "please copy and paste the following lines to your terminal:\r\n\r\n {}", format!("{} {}; ", exports.join(""), output)