From 5f0f0a4674914ca63260e58494088e8731c044ec Mon Sep 17 00:00:00 2001 From: Petr Gadorek Date: Wed, 6 Aug 2025 10:11:36 +0200 Subject: [PATCH 1/2] Added fix command for reinstalling the IDF --- .github/workflows/build.yaml | 4 +- docs/src/cli_commands.md | 10 ++++ src-tauri/src/cli/cli_args.rs | 6 ++ src-tauri/src/cli/mod.rs | 90 +++++++++++++++++++++++++++- src-tauri/src/lib/version_manager.rs | 19 ++++-- 5 files changed, 118 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 18e35ca4..53e36574 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -191,7 +191,7 @@ jobs: perl -MText::Template -e "print 'Text::Template loaded successfully\n'" perl -MParams::Check -e "print 'Params::Check loaded successfully\n'" perl -MIPC::Cmd -e "print 'IPC::Cmd loaded successfully\n'" - $perl_lib_path = "C:\hostedtoolcache\windows\perl\5.38.4-thr\x64\site\lib" + $perl_lib_path = "C:\hostedtoolcache\windows\perl\5.38.5-thr\x64\site\lib" echo "PERL5LIB=$perl_lib_path" >> $env:GITHUB_ENV - name: Cache cargo @@ -211,7 +211,7 @@ jobs: OPENSSL_LIB_DIR: 'C:\vcpkg\installed\x64-windows-static-md\lib' OPENSSL_INCLUDE_DIR: 'C:\vcpkg\installed\x64-windows-static-md\include' OPENSSL_STATIC: "1" - PERL: 'C:\\hostedtoolcache\\windows\\perl\\5.38.4-thr\\x64\\bin\\perl.exe' + PERL: 'C:\\hostedtoolcache\\windows\\perl\\5.38.5-thr\\x64\\bin\\perl.exe' run: | cd src-tauri cargo build --release --no-default-features --features cli ${{ matrix.target && format('--target {0}', matrix.target) || '' }} diff --git a/docs/src/cli_commands.md b/docs/src/cli_commands.md index 73e671c5..204c0bb9 100644 --- a/docs/src/cli_commands.md +++ b/docs/src/cli_commands.md @@ -144,6 +144,16 @@ eim discover This command is planned to discover ESP-IDF installations on your system but is not yet implemented. +### Fix Command + +Fix the ESP-IDF installation by reinstalling the tools and dependencies + +```bash +eim fix [PATH] +``` + +If no `PATH` is provided, the user will be presented with selection of all known IDF installation to select from. + ## Examples ```bash diff --git a/src-tauri/src/cli/cli_args.rs b/src-tauri/src/cli/cli_args.rs index fff8b974..a872853f 100644 --- a/src-tauri/src/cli/cli_args.rs +++ b/src-tauri/src/cli/cli_args.rs @@ -96,6 +96,12 @@ pub enum Commands { /// Run the ESP-IDF Installer GUI with arguments passed through command line #[cfg(feature = "gui")] Gui(InstallArgs), + + /// Fix the ESP-IDF installation by reinstalling the tools and dependencies + Fix { + #[arg(help = "Fix IDF on a specific path")] + path: Option, + }, } #[derive(Parser, Debug, Clone, Default)] diff --git a/src-tauri/src/cli/mod.rs b/src-tauri/src/cli/mod.rs index cf82fcfe..e177df01 100644 --- a/src-tauri/src/cli/mod.rs +++ b/src-tauri/src/cli/mod.rs @@ -10,6 +10,7 @@ use helpers::generic_input; use helpers::generic_select; use idf_im_lib::get_log_directory; use idf_im_lib::settings::Settings; +use idf_im_lib::utils::is_valid_idf_directory; use idf_im_lib::version_manager::remove_single_idf_version; use idf_im_lib::version_manager::select_idf_version; use log::debug; @@ -353,7 +354,7 @@ pub async fn run_cli(cli: Cli) -> anyhow::Result<()> { println!("Available versions:"); let options = versions.iter().map(|v| v.name.clone()).collect(); match generic_select("Which version do you want to remove?", &options) { - Ok(selected) => match remove_single_idf_version(&selected) { + Ok(selected) => match remove_single_idf_version(&selected, false) { Ok(_) => { info!("Removed version: {}", selected); Ok(()) @@ -367,7 +368,7 @@ pub async fn run_cli(cli: Cli) -> anyhow::Result<()> { Err(err) => Err(anyhow::anyhow!(err)), } } else { - match remove_single_idf_version(&version.clone().unwrap()) { + match remove_single_idf_version(&version.clone().unwrap(), false) { Ok(_) => { println!("Removed version: {}", version.clone().unwrap()); Ok(()) @@ -388,7 +389,7 @@ pub async fn run_cli(cli: Cli) -> anyhow::Result<()> { let mut failed = false; for version in versions { info!("Removing version: {}", version.name); - match remove_single_idf_version(&version.name) { + match remove_single_idf_version(&version.name, false) { Ok(_) => { info!("Removed version: {}", version.name); } @@ -432,6 +433,89 @@ pub async fn run_cli(cli: Cli) -> anyhow::Result<()> { Err(err) => Err(anyhow::anyhow!(err)), } } + Commands::Fix { path } => { + let path_to_fix = if path.is_some() { + // If a path is provided, fix the IDF installation at that path + let path = path.unwrap(); + if is_valid_idf_directory(&path) { + PathBuf::from(path) + } else { + error!("Invalid IDF directory: {}", path); + return Err(anyhow::anyhow!("Invalid IDF directory: {}", path)); + } + } else { + match idf_im_lib::version_manager::list_installed_versions() { + Ok(versions) => { + if versions.is_empty() { + warn!("No versions installed"); + return Ok(()); + } else { + let options = versions.iter().map(|v| v.path.clone()).collect(); + let version_path = match helpers::generic_select( + "Which version do you want to fix?", + &options, + ) { + Ok(selected) => selected, + Err(err) => { + error!("Error: {}", err); + return Err(anyhow::anyhow!(err)); + } + }; + PathBuf::from(version_path) + } + } + Err(err) => { + debug!("Error: {}", err); + return Err(anyhow::anyhow!("No versions found. Use eim install to install a new ESP-IDF version.")); + } + } + }; + info!("Fixing IDF installation at path: {}", path_to_fix.display()); + // The fix logic is just instalation with use of existing repository + let mut version_name = None; + match idf_im_lib::version_manager::list_installed_versions() { + Ok(versions) => { + for v in versions { + if v.path == path_to_fix.to_str().unwrap() { + info!("Found existing IDF version: {}", v.name); + // Remove the existing activation script and eim_idf.json entry + match remove_single_idf_version(&v.name, true) { + Ok(_) => { + info!("Removed existing IDF version from eim_idf.json: {}", v.name); + version_name = Some(v.name.clone()); + } + Err(err) => { + error!("Failed to remove existing IDF version {}: {}", v.name, err); + } + } + } + } + } + Err(_) => { + info!("Failed to list installed versions. Using default naming."); + } + } + + let mut settings = Settings::default(); + settings.path = Some(path_to_fix.clone()); + settings.non_interactive = Some(true); + settings.version_name = version_name; + settings.install_all_prerequisites = Some(true); + settings.config_file_save_path = None; + let result = wizard::run_wizzard_run(settings).await; + match result { + Ok(r) => { + info!("Fix result: {:?}", r); + info!("Successfully fixed IDF installation at {}", path_to_fix.display()); + } + Err(err) => { + error!("Failed to fix IDF installation: {}", err); + return Err(anyhow::anyhow!(err)); + } + } + info!("Now you can start using IDF tools"); + Ok(()) + } #[cfg(feature = "gui")] Commands::Gui(_install_args) => { #[cfg(not(feature = "gui"))] diff --git a/src-tauri/src/lib/version_manager.rs b/src-tauri/src/lib/version_manager.rs index befc944f..f47affb1 100644 --- a/src-tauri/src/lib/version_manager.rs +++ b/src-tauri/src/lib/version_manager.rs @@ -173,7 +173,7 @@ pub fn rename_idf_version(identifier: &str, new_name: String) -> Result /// /// * `Result` - On success, returns a `Result` containing a string message indicating /// that the version has been removed. On error, returns an `anyhow::Error` with a description of the error. -pub fn remove_single_idf_version(identifier: &str) -> Result { +pub fn remove_single_idf_version(identifier: &str, keep_idf_folder: bool) -> Result { //TODO: remove also from path let config_path = get_default_config_path(); let mut ide_config = IdfConfig::from_file(&config_path)?; @@ -183,11 +183,18 @@ pub fn remove_single_idf_version(identifier: &str) -> Result { .find(|install| install.id == identifier || install.name == identifier) { let installation_folder_path = PathBuf::from(installation.path.clone()); - let installation_folder = installation_folder_path.parent().unwrap(); - match remove_directory_all(installation_folder) { - Ok(_) => {} - Err(e) => { - return Err(anyhow!("Failed to remove installation folder: {}", e)); + let installation_folder = installation_folder_path.parent().ok_or_else(|| { + anyhow!( + "Installation path '{}' has no parent directory", + installation_folder_path.display() + ) + })?; + if !keep_idf_folder { + match remove_directory_all(installation_folder) { + Ok(_) => {} + Err(e) => { + return Err(anyhow!("Failed to remove installation folder: {}", e)); + } } } match remove_directory_all(installation.clone().activation_script) { From f9d20bb7eb203b7019ba2fc29983b3c2666f7795 Mon Sep 17 00:00:00 2001 From: Petr Gadorek Date: Mon, 5 May 2025 13:28:42 +0200 Subject: [PATCH 2/2] find all esp-idf instances and evaluate which one to process IDF installed using eim or eclipse can be discovered parsing function for esp_idf.json file added POSIX discovery mvp updated activation script creation improved export paths in old version discovery added tool_set.json file parsing to the discovery returned the parse_tool_set_config back to working state updated according to changes in the scripts location Documentation update to add the discovery command reworked the vscode discovery according to new approach to isntallation removed unused code updated the discovery command help Fixed discovery tools paths improved windows debugging part of discovery and venv creation creating venv using proper python discovery disabled for vscode installed IDF on windows --- docs/src/cli_commands.md | 7 +- src-tauri/src/cli/cli_args.rs | 7 +- src-tauri/src/cli/mod.rs | 298 +++++++++++++++++++-------- src-tauri/src/gui/mod.rs | 31 --- src-tauri/src/lib/idf_config.rs | 19 +- src-tauri/src/lib/python_utils.rs | 2 +- src-tauri/src/lib/utils.rs | 208 ++++++++++++++++++- src-tauri/src/lib/version_manager.rs | 3 + 8 files changed, 437 insertions(+), 138 deletions(-) diff --git a/docs/src/cli_commands.md b/docs/src/cli_commands.md index 204c0bb9..6a8ff5b5 100644 --- a/docs/src/cli_commands.md +++ b/docs/src/cli_commands.md @@ -30,7 +30,8 @@ These options can be used with any command: | `remove` | Remove a specific ESP-IDF version | | `purge` | Purge all ESP-IDF installations | | `import` | Import existing ESP-IDF installation using tools_set_config.json | -| `discover` | Discover available ESP-IDF versions (not implemented yet) | +| `discover` | Discover available ESP-IDF versions | +| `gui` | Run the ESP-IDF Installer GUI with arguments passed through command line | ## Command Details @@ -139,10 +140,10 @@ If `PATH` is not provided, the command will inform you that no config file was s Discover available ESP-IDF versions (not implemented yet). ```bash -eim discover +eim discover [PATH] ``` -This command is planned to discover ESP-IDF installations on your system but is not yet implemented. +This command searches for previously installed ESP-IDF versions on your system. If PATH is not provided, the command will search from the root of the filesystem. For any found versions where automatic import is possible, they will be imported into EIM's management. For other found versions that cannot be automatically imported, the command will suggest the appropriate `eim install` command to allow the user to manually reinstall them. ### Fix Command diff --git a/src-tauri/src/cli/cli_args.rs b/src-tauri/src/cli/cli_args.rs index a872853f..3aacd130 100644 --- a/src-tauri/src/cli/cli_args.rs +++ b/src-tauri/src/cli/cli_args.rs @@ -65,8 +65,11 @@ pub enum Commands { version: Option, }, - /// Discover available ESP-IDF versions (not implemented yet) - Discover, + /// Discover available ESP-IDF versions + Discover { + #[arg(help = "Discover available ESP-IDF versions and imports them")] + path: Option, + }, /// Remove specific ESP-IDF version Remove { diff --git a/src-tauri/src/cli/mod.rs b/src-tauri/src/cli/mod.rs index e177df01..ab7effa4 100644 --- a/src-tauri/src/cli/mod.rs +++ b/src-tauri/src/cli/mod.rs @@ -1,3 +1,4 @@ +use std::path::Path; use std::path::PathBuf; use anyhow::Context; @@ -9,8 +10,12 @@ use config::ConfigError; use helpers::generic_input; use helpers::generic_select; use idf_im_lib::get_log_directory; +use idf_im_lib::idf_config::IdfConfig; use idf_im_lib::settings::Settings; use idf_im_lib::utils::is_valid_idf_directory; +use idf_im_lib::utils::find_by_name_and_extension; +use idf_im_lib::utils::parse_esp_idf_json; +use idf_im_lib::utils::EspIdfConfig; use idf_im_lib::version_manager::remove_single_idf_version; use idf_im_lib::version_manager::select_idf_version; use log::debug; @@ -316,13 +321,128 @@ pub async fn run_cli(cli: Cli) -> anyhow::Result<()> { } } } - Commands::Discover => { - // TODO:Implement version discovery - unimplemented!("Version discovery not implemented yet"); - println!("Discovering available versions... (This can take couple of minutes)"); - let idf_dirs = idf_im_lib::version_manager::find_esp_idf_folders("/"); + Commands::Discover {path } => { + info!("Discovering available versions... (This can take couple of minutes)"); + let path = path.unwrap_or_else(|| { + let default_path = match std::env::consts::OS { + "windows" => { + "C:\\".to_string() + } + _ => { + "/".to_string() + } + }; + + + debug!("No path provided, using default: {}", default_path); + default_path + }); + // first parse existing esp_idf.json (using parse_esp_idf_json) || previous VSCode installations + info!("Searching for esp_idf.json files..."); + let search_patch = Path::new(&path); + let esp_idf_json_path = find_by_name_and_extension( + search_patch, + "esp_idf", + "json", + ); + if esp_idf_json_path.is_empty() { + info!("No esp_idf.json found"); + } else { + info!("Found {} esp_idf.json files:", esp_idf_json_path.len()); + } + for path in esp_idf_json_path { + info!("- {} ", &path); + match std::env::consts::OS { + "windows" => { + // On Windows, we need to fix every installation from VSCode + info!("Parsing esp_idf.json at: {}", path); + let idf_json_path = Path::new(&path); + let json_str = std::fs::read_to_string(idf_json_path).unwrap(); + let config: EspIdfConfig = match serde_json::from_str(&json_str) { + Ok(config) => config, + Err(e) => { + error!("Failed to parse config file: {}", e); + continue; + } + }; + for (_key, value) in config.idf_installed { + let idf_path = value.path; + fix_command(Some(idf_path)).await?; + } + } + _ => { + match parse_esp_idf_json(&path) { + Ok(_) => { + info!("Parsed config: {:?}", path); + } + Err(err) => { + info!("Failed to parse esp_idf.json: {}", err); + } + } + } + } + } + // second try to find tool_set_config.json (using parse_tool_set_config) || previous Eclipse installations + info!("Searching for tool_set_config.json files..."); + let tool_set_config_path = find_by_name_and_extension( + search_patch, + "tool_set_config", + "json", + ); + if tool_set_config_path.is_empty() { + info!("No tool_set_config.json found"); + } else { + info!("Found {} tool_set_config.json files:", tool_set_config_path.len()); + } + for path in tool_set_config_path { + info!("- {} ", &path); + match idf_im_lib::utils::parse_tool_set_config(&path) { + Ok(_) => { + info!("Parsed config: {:?}", path); + } + Err(err) => { + info!("Failed to parse tool_set_config.json: {}", err); + } + } + } + // third try to find IDF directories (using find_esp_idf_folders) || previous instalation from cli + info!("Searching for any other IDF directories..."); + let idf_dirs = idf_im_lib::version_manager::find_esp_idf_folders(&path); + if idf_dirs.is_empty() { + info!("No IDF directories found"); + } else { + info!("Found {} IDF directories:", idf_dirs.len()); + } + let config = match idf_im_lib::version_manager::get_esp_ide_config() { + Ok(config) => { + if config.idf_installed.is_empty() { + debug!( + "No versions found. Every discovered version can be imported." + ); + } + config + } + Err(_err) => { + debug!("No ide config found. New will be created."); + IdfConfig::default() + } + }; + let mut paths_to_add = vec![]; for dir in idf_dirs { - println!("Found IDF directory: {}", dir); + info!("- {} ", &dir); + if config.clone().is_path_in_config(dir.clone()) { + info!("Already present!"); + } else { + info!("Can be added..."); + paths_to_add.push(dir); + } + } + if paths_to_add.is_empty() { + info!("No new IDF directories found to add."); + return Ok(()); + } else { + info!("Found {} new IDF directories available to add:", paths_to_add.len()); + info!("You can add them using `eim install` command with the `--path` option."); } Ok(()) } @@ -434,87 +554,7 @@ pub async fn run_cli(cli: Cli) -> anyhow::Result<()> { } } Commands::Fix { path } => { - let path_to_fix = if path.is_some() { - // If a path is provided, fix the IDF installation at that path - let path = path.unwrap(); - if is_valid_idf_directory(&path) { - PathBuf::from(path) - } else { - error!("Invalid IDF directory: {}", path); - return Err(anyhow::anyhow!("Invalid IDF directory: {}", path)); - } - } else { - match idf_im_lib::version_manager::list_installed_versions() { - Ok(versions) => { - if versions.is_empty() { - warn!("No versions installed"); - return Ok(()); - } else { - let options = versions.iter().map(|v| v.path.clone()).collect(); - let version_path = match helpers::generic_select( - "Which version do you want to fix?", - &options, - ) { - Ok(selected) => selected, - Err(err) => { - error!("Error: {}", err); - return Err(anyhow::anyhow!(err)); - } - }; - PathBuf::from(version_path) - } - } - Err(err) => { - debug!("Error: {}", err); - return Err(anyhow::anyhow!("No versions found. Use eim install to install a new ESP-IDF version.")); - } - } - }; - info!("Fixing IDF installation at path: {}", path_to_fix.display()); - // The fix logic is just instalation with use of existing repository - let mut version_name = None; - match idf_im_lib::version_manager::list_installed_versions() { - Ok(versions) => { - for v in versions { - if v.path == path_to_fix.to_str().unwrap() { - info!("Found existing IDF version: {}", v.name); - // Remove the existing activation script and eim_idf.json entry - match remove_single_idf_version(&v.name, true) { - Ok(_) => { - info!("Removed existing IDF version from eim_idf.json: {}", v.name); - version_name = Some(v.name.clone()); - } - Err(err) => { - error!("Failed to remove existing IDF version {}: {}", v.name, err); - } - } - } - } - } - Err(_) => { - info!("Failed to list installed versions. Using default naming."); - } - } - - let mut settings = Settings::default(); - settings.path = Some(path_to_fix.clone()); - settings.non_interactive = Some(true); - settings.version_name = version_name; - settings.install_all_prerequisites = Some(true); - settings.config_file_save_path = None; - let result = wizard::run_wizzard_run(settings).await; - match result { - Ok(r) => { - info!("Fix result: {:?}", r); - info!("Successfully fixed IDF installation at {}", path_to_fix.display()); - } - Err(err) => { - error!("Failed to fix IDF installation: {}", err); - return Err(anyhow::anyhow!(err)); - } - } - info!("Now you can start using IDF tools"); - Ok(()) + fix_command(path).await } #[cfg(feature = "gui")] Commands::Gui(_install_args) => { @@ -525,3 +565,87 @@ pub async fn run_cli(cli: Cli) -> anyhow::Result<()> { } } } + +async fn fix_command(path:Option) -> anyhow::Result<()> { + let path_to_fix = if path.is_some() { + // If a path is provided, fix the IDF installation at that path + let path = path.unwrap(); + if is_valid_idf_directory(&path) { + PathBuf::from(path) + } else { + error!("Invalid IDF directory: {}", path); + return Err(anyhow::anyhow!("Invalid IDF directory: {}", path)); + } + } else { + match idf_im_lib::version_manager::list_installed_versions() { + Ok(versions) => { + if versions.is_empty() { + warn!("No versions installed"); + return Ok(()); + } else { + let options = versions.iter().map(|v| v.path.clone()).collect(); + let version_path = match helpers::generic_select( + "Which version do you want to fix?", + &options, + ) { + Ok(selected) => selected, + Err(err) => { + error!("Error: {}", err); + return Err(anyhow::anyhow!(err)); + } + }; + PathBuf::from(version_path) + } + } + Err(err) => { + debug!("Error: {}", err); + return Err(anyhow::anyhow!("No versions found. Use eim install to install a new ESP-IDF version.")); + } + } + }; + info!("Fixing IDF installation at path: {}", path_to_fix.display()); + // The fix logic is just instalation with use of existing repository + let mut version_name = None; + match idf_im_lib::version_manager::list_installed_versions() { + Ok(versions) => { + for v in versions { + if v.path == path_to_fix.to_str().unwrap() { + info!("Found existing IDF version: {}", v.name); + // Remove the existing activation script and eim_idf.json entry + match remove_single_idf_version(&v.name, true) { + Ok(_) => { + info!("Removed existing IDF version from eim_idf.json: {}", v.name); + version_name = Some(v.name.clone()); + } + Err(err) => { + error!("Failed to remove existing IDF version {}: {}", v.name, err); + } + } + } + } + } + Err(_) => { + info!("Failed to list installed versions. Using default naming."); + } + } + + let mut settings = Settings::default(); + settings.path = Some(path_to_fix.clone()); + settings.non_interactive = Some(true); + settings.version_name = version_name; + settings.install_all_prerequisites = Some(true); + settings.config_file_save_path = None; // Do not save config file in fix mode + let result = wizard::run_wizzard_run(settings).await; + match result { + Ok(r) => { + info!("Fix result: {:?}", r); + info!("Successfully fixed IDF installation at {}", path_to_fix.display()); + } + Err(err) => { + error!("Failed to fix IDF installation: {}", err); + return Err(anyhow::anyhow!(err)); + } + } + info!("Now you can start using IDF tools"); + Ok(()) +} diff --git a/src-tauri/src/gui/mod.rs b/src-tauri/src/gui/mod.rs index a301676f..0e8d8d07 100644 --- a/src-tauri/src/gui/mod.rs +++ b/src-tauri/src/gui/mod.rs @@ -169,37 +169,6 @@ impl ToolSetup { } } -// async fn install_single_version( -// app_handle: AppHandle, -// settings: &Settings, -// version: String, -// ) -> Result<(), Box> { -// info!("Installing IDF version: {}", version); - -// let version_path = prepare_installation_directories(app_handle.clone(), settings, &version)?; -// let idf_path = version_path.clone().join("esp-idf"); -// download_idf(&app_handle, settings, &version, &idf_path).await?; -// let export_vars = setup_tools(&app_handle, settings, &idf_path, &version).await?; -// let tools_install_path = version_path.clone().join( -// settings -// .tool_install_folder_name -// .clone() -// .unwrap_or_default(), -// ); -// let idf_python_env_path = tools_install_path.clone().join("python").join(&version).join("venv"); -// let activation_script_path = settings.esp_idf_json_path.clone().unwrap_or_default(); -// idf_im_lib::single_version_post_install( -// &activation_script_path, -// idf_path.to_str().unwrap(), -// &version, -// tools_install_path.to_str().unwrap(), -// export_vars, -// Some(idf_python_env_path.to_str().unwrap()), -// ); - -// Ok(()) -// } - // Helper function to check if a process is running on Windows #[cfg(target_os = "windows")] fn is_process_running(pid: u32) -> bool { diff --git a/src-tauri/src/lib/idf_config.rs b/src-tauri/src/lib/idf_config.rs index 6989f3ec..bcfe587f 100644 --- a/src-tauri/src/lib/idf_config.rs +++ b/src-tauri/src/lib/idf_config.rs @@ -37,6 +37,18 @@ pub struct IdfConfig { pub version: Option, } +impl Default for IdfConfig { + fn default() -> Self { + IdfConfig { + git_path: crate::utils::get_git_path().unwrap_or_default(), + idf_installed: [].to_vec(), + idf_selected_id: "".to_string(), + eim_path: None, + version: Some(IDF_CONFIG_FILE_VERSION.to_string()), + } + } +} + impl IdfConfig { /// Saves the configuration to a file. /// @@ -226,6 +238,10 @@ impl IdfConfig { } } + pub fn is_path_in_config(self, path:String) -> bool { + self.idf_installed.iter().find(|i| i.path == path).is_some() + } + /// Removes an IDF installation from the configuration. /// /// This function searches for an installation matching the given identifier @@ -261,9 +277,6 @@ impl IdfConfig { } } - pub fn is_path_in_config(self, path:String) -> bool { - self.idf_installed.iter().find(|i| i.path == path).is_some() - } } pub fn parse_idf_config>(path: P) -> Result { diff --git a/src-tauri/src/lib/python_utils.rs b/src-tauri/src/lib/python_utils.rs index 60a2cf3e..4a6d0aec 100644 --- a/src-tauri/src/lib/python_utils.rs +++ b/src-tauri/src/lib/python_utils.rs @@ -201,7 +201,7 @@ async fn download_constraints_file(idf_tools_path: &Path, idf_version: &str) -> /// - Other system-level errors prevent the command from executing. /// - The `python -m venv` command itself encounters an error (e.g., invalid path). pub fn create_python_venv(venv_path: &str) -> Result { - info!("Creating Python virtual environment at: {}", venv_path); + info!("Creating Python virtual environment at: {}", venv_path); let output = match std::env::consts::OS { "windows" => command_executor::execute_command( diff --git a/src-tauri/src/lib/utils.rs b/src-tauri/src/lib/utils.rs index b7f3c999..85036e4f 100644 --- a/src-tauri/src/lib/utils.rs +++ b/src-tauri/src/lib/utils.rs @@ -1,22 +1,16 @@ use crate::{ - command_executor::execute_command, - idf_config::{IdfConfig, IdfInstallation}, - idf_tools::read_and_parse_tools_file, - single_version_post_install, - version_manager::get_default_config_path, + command_executor::execute_command, ensure_path, idf_config::{IdfConfig, IdfInstallation}, idf_tools::read_and_parse_tools_file, python_utils::create_python_venv, single_version_post_install, version_manager::get_default_config_path }; -use anyhow::{anyhow, Result, Error}; +use anyhow::{anyhow, Result}; use git2::Repository; use log::{debug, error, info, warn}; use rust_search::SearchBuilder; use serde::{Deserialize, Serialize}; +use uuid::Uuid; #[cfg(not(windows))] use std::os::unix::fs::MetadataExt; use std::{ - collections::{HashMap, HashSet}, - fs::{self}, - io, - path::{Path, PathBuf}, + collections::{HashMap, HashSet}, fs::{self}, io, path::{Path, PathBuf} }; use regex::Regex; @@ -149,14 +143,18 @@ pub fn is_valid_idf_directory(path: &str) -> bool { let path = PathBuf::from(path); let tools_path = path.join("tools"); let tools_json_path = tools_path.join("tools.json"); + debug!("Checking for tools.json at: {}", tools_json_path.display()); if !tools_json_path.exists() { return false; } + debug!("Found tools.json at: {}", tools_json_path.display()); match read_and_parse_tools_file(tools_json_path.to_str().unwrap()) { Ok(_) => { + debug!("Valid IDF directory: {}", path.display()); true } Err(_) => { + debug!("Invalid IDF directory: {}", path.display()); false } } @@ -373,7 +371,11 @@ fn extract_tools_path_from_python_env_path(path: &str) -> Option { /// It also logs errors if the IDF installation configuration cannot be updated. pub fn parse_tool_set_config(config_path: &str) -> Result<()> { let config_path = Path::new(config_path); - let json_str = std::fs::read_to_string(config_path).unwrap(); + let json_str = match std::fs::read_to_string(config_path) { + Ok(content) => content, + Err(e) => return Err(anyhow!("Failed to read config file: {}", e)), + }; + debug!("Parsing tool set config from: {}", config_path.display()); let config: Vec = match serde_json::from_str(&json_str) { Ok(config) => config, Err(e) => return Err(anyhow!("Failed to parse config file: {}", e)), @@ -454,6 +456,190 @@ pub fn parse_tool_set_config(config_path: &str) -> Result<()> { } } +#[derive(Deserialize, Debug)] +pub struct EspIdfConfig { + #[serde(rename = "$schema")] + pub schema: String, + #[serde(rename = "$id")] + pub id: String, + #[serde(rename = "_comment")] + pub comment: String, + #[serde(rename = "_warning")] + pub warning: String, + #[serde(rename = "gitPath")] + pub git_path: String, + #[serde(rename = "idfToolsPath")] + pub idf_tools_path: String, + #[serde(rename = "idfSelectedId")] + pub idf_selected_id: String, + #[serde(rename = "idfInstalled")] + pub idf_installed: HashMap, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct EspIdfVersion { + pub version: String, + pub python: String, + pub path: String, +} + +pub fn parse_esp_idf_json(idf_json_path: &str) -> Result<()> { + if std::env::consts::OS == "windows" { + info!("Discovery of vscode installed IDF is not supported on windows."); + } + let idf_json_path = Path::new(idf_json_path); + let json_str = std::fs::read_to_string(idf_json_path).unwrap(); + let config: EspIdfConfig = match serde_json::from_str(&json_str) { + Ok(config) => config, + Err(e) => return Err(anyhow!("Failed to parse config file: {}", e)), + }; + let config_path = get_default_config_path(); + let mut current_config = match IdfConfig::from_file(&config_path) { + Ok(config) => config, + Err(_e) => IdfConfig::default(), + }; + for (_key, value) in config.idf_installed { + let value_clone = value.clone(); + let idf_version = value.version; + let idf_path = value.path; + let python = value.python; + let settings = crate::settings::Settings::default(); + let paths = settings.get_version_paths(&idf_version)?; + let old_tool_path = PathBuf::from(&config.idf_tools_path.clone()); + let tools_path = old_tool_path.join(settings.tool_install_folder_name.unwrap_or_default()); + ensure_path(tools_path.to_str().unwrap()); + + for entry in fs::read_dir(old_tool_path)? { + let entry = entry?; + let path = entry.path(); + if path.is_file() { + if let Some(file_name) = path.file_name() { + if let Some(name_str) = file_name.to_str() { + if name_str.contains("constraints") { + let target_file = tools_path.join(file_name); + fs::copy(&path, &target_file)?; + debug!("Copied: {} -> {}", path.display(), target_file.display()); + } + } + } + } + } + + debug!("IDF tools path: {}", tools_path.display()); + debug!("IDF version: {}", idf_version); + debug!("activation script path: {}", paths.activation_script_path.display()); + debug!("Python path: {}", python); + // export paths + let tools_json_file = find_by_name_and_extension(Path::new(&idf_path), "tools", "json"); + if tools_json_file.is_empty() { + return Err(anyhow!("tools.json file not found")); + } + + debug!("Tools json file: {:?}", tools_json_file); + + let tools = match crate::idf_tools::read_and_parse_tools_file(&tools_json_file.first().unwrap()){ + Ok(tools) => tools, + Err(e) => { + return Err(anyhow!("Failed to read tools.json file: {}", e)); + } + }; + let list= crate::idf_tools::get_list_of_tools_to_download(tools.clone(), vec!["all".to_string()],None); + let mut export_paths:Vec = crate::idf_tools::get_tools_export_paths_from_list( + tools, + list, + tools_path.to_str().unwrap(), + ) + .into_iter() + .map(|p| { + if std::env::consts::OS == "windows" { + crate::replace_unescaped_spaces_win(&p) + } else { + p + } + }) + .collect(); + export_paths.push(config.git_path.clone()); + + if !PathBuf::from(idf_path.clone()).exists() { + warn!("Path {} does not exists, skipping", &idf_path); + continue; + }; + if current_config.clone().is_path_in_config(idf_path.to_string()) { + info!("Path {} already in config, skipping", &idf_path); + continue; + }; + let python_env = match std::env::consts::OS { + "windows" => python.clone().find("Scripts").map(|index| python[..=index-1].to_string()), + _ => python.clone().find("bin").map(|index| python[..=index-1].to_string()), + }; + let penv = match python_env.clone() { + Some(env) => { + debug!("Python environment found: {}", env); + env + } + None => { + warn!("Python environment not found for version: {:?}", value_clone); + continue; + } + }; + //create_python_venv + single_version_post_install( + &paths.activation_script_path.to_string_lossy().into_owned(), + &idf_path, + &idf_version, + tools_path.to_str().unwrap(), + export_paths, + python_env.as_deref(), + None, + ); + + let python = match std::env::consts::OS { + "windows" => PathBuf::from(&penv).join("Scripts").join("python.exe"), + _ => PathBuf::from(&penv).join("bin").join("python"), + }; + let venv_activation_script = match std::env::consts::OS { + "windows" => PathBuf::from(&penv).join("Scripts").join("activate.bat"), + _ => PathBuf::from(&penv).join("bin").join("activate"), + }; + if venv_activation_script.try_exists().unwrap_or(false) { + debug!("Virtual environment activation script found: {}", venv_activation_script.display()); + } else { + debug!("Virtual environment activation script not found: {} ... Creating new one...", venv_activation_script.display()); + match create_python_venv(&penv) { + Ok(_) => debug!("Virtual environment created successfully"), + Err(e) => { + error!("Failed to create virtual environment: {}", e); + continue; + } + } + } + let id = format!("esp-idf-{}", Uuid::new_v4().to_string().replace("-", "")); + let installation = IdfInstallation { + id, + activation_script: paths.activation_script.to_string_lossy().into_owned(), + path: idf_path.clone(), + name: idf_version, + python: python.to_str() + .unwrap() + .to_string(), + idf_tools_path: tools_path.to_str() + .unwrap() + .to_string(), + }; + + current_config.idf_installed.push(installation); + } + match current_config.to_file(config_path, true, true) { + Ok(_) => { + debug!("Updated config file with new tool set"); + return Ok(()) + } + Err(e) => { + return Err(anyhow!("Failed to update config file: {}", e)) + } + } +} + /// Converts a path to a long path compatible with Windows. /// /// This function takes a string representing a path and returns a new string. diff --git a/src-tauri/src/lib/version_manager.rs b/src-tauri/src/lib/version_manager.rs index f47affb1..8b5772d0 100644 --- a/src-tauri/src/lib/version_manager.rs +++ b/src-tauri/src/lib/version_manager.rs @@ -231,9 +231,12 @@ pub fn remove_single_idf_version(identifier: &str, keep_idf_folder: bool) -> Res pub fn find_esp_idf_folders(path: &str) -> Vec { let path = Path::new(path); let mut dirs = crate::utils::find_directories_by_name(path, "esp-idf"); + debug!("Found {} esp-idf folders", dirs.len()); + debug!("Found folders: {:?}", dirs); dirs.sort(); dirs.reverse(); let filtered_dirs = crate::utils::filter_duplicate_paths(dirs.clone()); + debug!("Filtered folders: {:?}", filtered_dirs); filtered_dirs .iter() .filter(|p| crate::utils::is_valid_idf_directory(p))