From d13738646f56b8c916c57415369381bd38e17a12 Mon Sep 17 00:00:00 2001 From: Radu Date: Tue, 9 Sep 2025 12:31:37 +0300 Subject: [PATCH 01/21] refactor: migrate from old format to CMakePresets --- src/config.ts | 3 +- .../ProjectConfigurationManager.ts | 244 ++++- src/project-conf/index.ts | 881 +++++++++++++++--- src/project-conf/projectConfPanel.ts | 13 +- src/project-conf/projectConfiguration.ts | 45 + 5 files changed, 1005 insertions(+), 181 deletions(-) diff --git a/src/config.ts b/src/config.ts index a787e4d88..f0db7df60 100644 --- a/src/config.ts +++ b/src/config.ts @@ -30,8 +30,7 @@ export namespace ESP { export namespace ProjectConfiguration { export let store: ProjectConfigStore; export const SELECTED_CONFIG = "SELECTED_PROJECT_CONFIG"; - export const PROJECT_CONFIGURATION_FILENAME = - "esp_idf_project_configuration.json"; + export const PROJECT_CONFIGURATION_FILENAME = "CMakePresets.json"; } export enum BuildType { diff --git a/src/project-conf/ProjectConfigurationManager.ts b/src/project-conf/ProjectConfigurationManager.ts index 917b3369c..71d69388b 100644 --- a/src/project-conf/ProjectConfigurationManager.ts +++ b/src/project-conf/ProjectConfigurationManager.ts @@ -20,7 +20,8 @@ import { CommandKeys, createCommandDictionary } from "../cmdTreeView/cmdStore"; import { createStatusBarItem } from "../statusBar"; import { getIdfTargetFromSdkconfig } from "../workspaceConfig"; import { Logger } from "../logger/logger"; -import { getProjectConfigurationElements } from "./index"; +import { getProjectConfigurationElements, configurePresetToProjectConfElement, promptLegacyMigration, migrateLegacyConfiguration } from "./index"; +import { pathExists } from "fs-extra"; import { configureClangSettings } from "../clang"; export function clearSelectedProjectConfiguration(): void { @@ -68,32 +69,15 @@ export class ProjectConfigurationManager { false ); - this.initialize(); this.registerEventHandlers(); + // Initialize asynchronously + this.initialize(); } - private initialize(): void { + private async initialize(): Promise { if (!fileExists(this.configFilePath)) { - // File doesn't exist - this is normal for projects without multiple configurations - this.configVersions = []; - - // If configuration status bar item exists, remove it - if (this.statusBarItems["projectConf"]) { - this.statusBarItems["projectConf"].dispose(); - this.statusBarItems["projectConf"] = undefined; - } - - // Clear any potentially stale configuration - const currentSelectedConfig = ESP.ProjectConfiguration.store.get( - ESP.ProjectConfiguration.SELECTED_CONFIG - ); - if (currentSelectedConfig) { - ESP.ProjectConfiguration.store.clear(currentSelectedConfig); - ESP.ProjectConfiguration.store.clear( - ESP.ProjectConfiguration.SELECTED_CONFIG - ); - } - + // CMakePresets.json doesn't exist - check for legacy file + await this.checkForLegacyFile(); return; } @@ -350,7 +334,10 @@ export class ProjectConfigurationManager { this.workspaceUri, true // Resolve paths for building ); - ESP.ProjectConfiguration.store.set(configName, resolvedConfig[configName]); + + // Convert ConfigurePreset to ProjectConfElement for store compatibility + const legacyElement = configurePresetToProjectConfElement(resolvedConfig[configName]); + ESP.ProjectConfiguration.store.set(configName, legacyElement); // Update UI if (this.statusBarItems["projectConf"]) { @@ -392,8 +379,17 @@ export class ProjectConfigurationManager { !projectConfigurations || Object.keys(projectConfigurations).length === 0 ) { + // Check if we have legacy configurations to migrate + const legacyFilePath = Uri.joinPath(this.workspaceUri, "esp_idf_project_configuration.json"); + + if (await pathExists(legacyFilePath.fsPath)) { + // Show migration dialog + await this.handleLegacyMigrationDialog(legacyFilePath); + return; + } + const emptyOption = await window.showInformationMessage( - l10n.t("No project configuration found"), + l10n.t("No CMakePresets configure presets found"), "Open editor" ); @@ -430,6 +426,204 @@ export class ProjectConfigurationManager { } } + /** + * Checks for legacy esp_idf_project_configuration.json file and shows appropriate status + */ + private async checkForLegacyFile(): Promise { + const legacyFilePath = Uri.joinPath(this.workspaceUri, "esp_idf_project_configuration.json").fsPath; + + if (fileExists(legacyFilePath)) { + // Legacy file exists - show status bar with migration option + this.configVersions = []; + + try { + const legacyContent = readFileSync(legacyFilePath); + if (legacyContent && legacyContent.trim() !== "") { + const legacyData = JSON.parse(legacyContent); + const legacyConfigNames = Object.keys(legacyData); + + if (legacyConfigNames.length > 0) { + // Show status bar indicating legacy configurations are available + this.setLegacyConfigurationStatus(legacyConfigNames); + + // Show migration notification + this.showLegacyMigrationNotification(legacyConfigNames); + return; + } + } + } catch (error) { + Logger.warn(`Failed to parse legacy configuration file: ${error.message}`); + } + } + + // No configuration files found - clear everything + this.clearConfigurationState(); + } + + /** + * Sets status bar to indicate legacy configurations are available + */ + private setLegacyConfigurationStatus(legacyConfigNames: string[]): void { + const statusBarItemName = `Legacy Configs (${legacyConfigNames.length})`; + const statusBarItemTooltip = `Found legacy project configurations: ${legacyConfigNames.join(", ")}. Click to migrate to CMakePresets.json format.`; + const commandToUse = "espIdf.projectConf"; + + if (this.statusBarItems["projectConf"]) { + this.statusBarItems["projectConf"].dispose(); + } + + this.statusBarItems["projectConf"] = createStatusBarItem( + `$(${ + this.commandDictionary[CommandKeys.SelectProjectConfiguration].iconId + }) ${statusBarItemName}`, + statusBarItemTooltip, + commandToUse, + 99, + this.commandDictionary[CommandKeys.SelectProjectConfiguration] + .checkboxState + ); + } + + /** + * Shows notification about legacy configurations + */ + private async showLegacyMigrationNotification(legacyConfigNames: string[]): Promise { + const message = l10n.t( + "Found {0} legacy project configuration(s): {1}. Would you like to migrate them to the new CMakePresets.json format? Your original file will remain unchanged.", + legacyConfigNames.length, + legacyConfigNames.join(", ") + ); + + const migrateOption = l10n.t("Migrate Now"); + const laterOption = l10n.t("Later"); + + const choice = await window.showInformationMessage( + message, + migrateOption, + laterOption + ); + + if (choice === migrateOption) { + // Directly perform migration without additional popup + const legacyFilePath = Uri.joinPath(this.workspaceUri, "esp_idf_project_configuration.json"); + await this.performDirectMigration(legacyFilePath); + } + } + + /** + * Handles the legacy migration dialog when user clicks on project configuration + */ + private async handleLegacyMigrationDialog(legacyFilePath: Uri): Promise { + try { + const legacyContent = readFileSync(legacyFilePath.fsPath); + const legacyData = JSON.parse(legacyContent); + const legacyConfigNames = Object.keys(legacyData); + + const message = l10n.t( + "Found {0} legacy project configuration(s): {1}. Would you like to migrate them to the new CMakePresets.json format?", + legacyConfigNames.length, + legacyConfigNames.join(", ") + ); + + const migrateOption = l10n.t("Migrate Now"); + const cancelOption = l10n.t("Cancel"); + + const choice = await window.showInformationMessage( + message, + { modal: true }, + migrateOption, + cancelOption + ); + + if (choice === migrateOption) { + await this.performMigration(legacyFilePath); + } + } catch (error) { + Logger.errorNotify( + "Failed to handle legacy migration", + error, + "handleLegacyMigrationDialog" + ); + window.showErrorMessage( + l10n.t("Failed to process legacy configuration file: {0}", error.message) + ); + } + } + + /** + * Performs the actual migration and updates UI (with confirmation dialog) + */ + private async performMigration(legacyFilePath: Uri): Promise { + try { + await promptLegacyMigration(this.workspaceUri, legacyFilePath); + + // After migration, reinitialize to show the new configurations + await this.initialize(); + + window.showInformationMessage( + l10n.t("Project configurations successfully migrated to CMakePresets.json format!") + ); + } catch (error) { + Logger.errorNotify( + "Failed to perform migration", + error, + "performMigration" + ); + window.showErrorMessage( + l10n.t("Failed to migrate project configuration: {0}", error.message) + ); + } + } + + /** + * Performs direct migration without additional confirmation (for notification) + */ + private async performDirectMigration(legacyFilePath: Uri): Promise { + try { + await migrateLegacyConfiguration(this.workspaceUri, legacyFilePath); + + // After migration, reinitialize to show the new configurations + await this.initialize(); + + window.showInformationMessage( + l10n.t("Project configurations successfully migrated to CMakePresets.json format!") + ); + } catch (error) { + Logger.errorNotify( + "Failed to perform direct migration", + error, + "performDirectMigration" + ); + window.showErrorMessage( + l10n.t("Failed to migrate project configuration: {0}", error.message) + ); + } + } + + /** + * Clears all configuration state + */ + private clearConfigurationState(): void { + this.configVersions = []; + + // If configuration status bar item exists, remove it + if (this.statusBarItems["projectConf"]) { + this.statusBarItems["projectConf"].dispose(); + this.statusBarItems["projectConf"] = undefined; + } + + // Clear any potentially stale configuration + const currentSelectedConfig = ESP.ProjectConfiguration.store.get( + ESP.ProjectConfiguration.SELECTED_CONFIG + ); + if (currentSelectedConfig) { + ESP.ProjectConfiguration.store.clear(currentSelectedConfig); + ESP.ProjectConfiguration.store.clear( + ESP.ProjectConfiguration.SELECTED_CONFIG + ); + } + } + /** * Dispose of the file system watcher */ diff --git a/src/project-conf/index.ts b/src/project-conf/index.ts index 22100759f..70ef4208e 100644 --- a/src/project-conf/index.ts +++ b/src/project-conf/index.ts @@ -17,10 +17,10 @@ */ import * as path from "path"; -import { ExtensionContext, Uri, window } from "vscode"; +import { ExtensionContext, Uri, window, l10n } from "vscode"; import { ESP } from "../config"; import { pathExists, readJson, writeJson } from "fs-extra"; -import { ProjectConfElement } from "./projectConfiguration"; +import { ProjectConfElement, CMakePresets, ConfigurePreset, BuildPreset, ESPIDFSettings, ESPIDFVendorSettings } from "./projectConfiguration"; import { Logger } from "../logger/logger"; import { resolveVariables } from "../idfConfiguration"; @@ -67,7 +67,7 @@ export async function updateCurrentProfileIdfTarget( if (!projectConfJson[selectedConfig]) { const err = new Error( - `Configuration "${selectedConfig}" not found in ${ESP.ProjectConfiguration.PROJECT_CONFIGURATION_FILENAME}.` + `Configuration preset "${selectedConfig}" not found in ${ESP.ProjectConfiguration.PROJECT_CONFIGURATION_FILENAME}. Please check your CMakePresets configurePresets section.` ); Logger.errorNotify( err.message, @@ -76,7 +76,12 @@ export async function updateCurrentProfileIdfTarget( ); return; } - projectConfJson[selectedConfig].idfTarget = idfTarget; + + // Update IDF_TARGET in cacheVariables for ConfigurePreset + if (!projectConfJson[selectedConfig].cacheVariables) { + projectConfJson[selectedConfig].cacheVariables = {}; + } + projectConfJson[selectedConfig].cacheVariables.IDF_TARGET = idfTarget; ESP.ProjectConfiguration.store.set( selectedConfig, @@ -86,6 +91,30 @@ export async function updateCurrentProfileIdfTarget( } export async function saveProjectConfFile( + workspaceFolder: Uri, + projectConfElements: { [key: string]: ConfigurePreset } +) { + const projectConfFilePath = Uri.joinPath( + workspaceFolder, + ESP.ProjectConfiguration.PROJECT_CONFIGURATION_FILENAME + ); + + // Use ConfigurePreset objects directly + const configurePresets: ConfigurePreset[] = Object.values(projectConfElements); + + const cmakePresets: CMakePresets = { + version: 1, + cmakeMinimumRequired: { major: 3, minor: 23, patch: 0 }, + configurePresets, + }; + + await writeJson(projectConfFilePath.fsPath, cmakePresets, { + spaces: 2, + }); +} + +// Legacy compatibility function +export async function saveProjectConfFileLegacy( workspaceFolder: Uri, projectConfElements: { [key: string]: ProjectConfElement } ) { @@ -93,7 +122,19 @@ export async function saveProjectConfFile( workspaceFolder, ESP.ProjectConfiguration.PROJECT_CONFIGURATION_FILENAME ); - await writeJson(projectConfFilePath.fsPath, projectConfElements, { + + // Convert to CMakePresets format + const configurePresets: ConfigurePreset[] = Object.keys(projectConfElements).map(name => + convertProjectConfElementToConfigurePreset(name, projectConfElements[name]) + ); + + const cmakePresets: CMakePresets = { + version: 1, + cmakeMinimumRequired: { major: 3, minor: 23, patch: 0 }, + configurePresets, + }; + + await writeJson(projectConfFilePath.fsPath, cmakePresets, { spaces: 2, }); } @@ -311,16 +352,16 @@ function resolveConfigPaths( // --- Main Function --- /** - * Reads the project configuration JSON file, performs variable substitution + * Reads the CMakePresets.json file, performs variable substitution * on relevant fields, resolves paths, and returns the structured configuration. * @param workspaceFolder The Uri of the current workspace folder. * @param resolvePaths Whether to resolve paths to absolute paths (true for building, false for display) - * @returns An object mapping configuration names to their processed ProjectConfElement. + * @returns An object mapping configuration names to their processed ConfigurePreset. */ export async function getProjectConfigurationElements( workspaceFolder: Uri, resolvePaths: boolean = false -): Promise<{ [key: string]: ProjectConfElement }> { +): Promise<{ [key: string]: ConfigurePreset }> { const projectConfFilePath = Uri.joinPath( workspaceFolder, ESP.ProjectConfiguration.PROJECT_CONFIGURATION_FILENAME @@ -328,7 +369,8 @@ export async function getProjectConfigurationElements( const doesPathExists = await pathExists(projectConfFilePath.fsPath); if (!doesPathExists) { - // File not existing is normal, return empty object + // Check if legacy file exists and prompt for migration + await checkAndPromptLegacyMigration(workspaceFolder); return {}; } @@ -345,161 +387,599 @@ export async function getProjectConfigurationElements( "getProjectConfigurationElements" ); window.showErrorMessage( - `Error reading or parsing project configuration file (${projectConfFilePath.fsPath}): ${error.message}` + `Error reading or parsing CMakePresets.json file (${projectConfFilePath.fsPath}): ${error.message}` ); return {}; // Return empty if JSON is invalid or unreadable } - const projectConfElements: { [key: string]: ProjectConfElement } = {}; + const projectConfElements: { [key: string]: ConfigurePreset } = {}; + + // Only support CMakePresets format + if (projectConfJson.version !== undefined && projectConfJson.configurePresets) { + // CMakePresets format + const cmakePresets = projectConfJson as CMakePresets; + + if (!cmakePresets.configurePresets || cmakePresets.configurePresets.length === 0) { + return {}; + } + + // Process each configure preset + for (const preset of cmakePresets.configurePresets) { + try { + // Apply variable substitution and path resolution directly to ConfigurePreset + const processedPreset = await processConfigurePresetVariables( + preset, + workspaceFolder, + resolvePaths + ); + + projectConfElements[preset.name] = processedPreset; + } catch (error) { + Logger.warn( + `Failed to process configure preset "${preset.name}": ${error.message}`, + error + ); + } + } + } else { + // This might be a legacy file that wasn't migrated + Logger.warn( + `Invalid CMakePresets.json format detected. Expected 'version' and 'configurePresets' fields.`, + new Error("Invalid CMakePresets format") + ); + window.showErrorMessage( + `Invalid CMakePresets.json format. Please ensure the file follows the CMakePresets specification.` + ); + return {}; + } + + return projectConfElements; +} + +/** + * Checks for legacy project configuration file and prompts user for migration + */ +async function checkAndPromptLegacyMigration(workspaceFolder: Uri): Promise { + const legacyFilePath = Uri.joinPath(workspaceFolder, "esp_idf_project_configuration.json"); + + if (await pathExists(legacyFilePath.fsPath)) { + await promptLegacyMigration(workspaceFolder, legacyFilePath); + } +} + +/** + * Prompts user to migrate legacy configuration file + */ +export async function promptLegacyMigration(workspaceFolder: Uri, legacyFilePath: Uri): Promise { + const message = l10n.t( + "A legacy project configuration file (esp_idf_project_configuration.json) was found. " + + "Would you like to migrate it to the new CMakePresets.json format? " + + "Your original file will remain unchanged." + ); + + const migrateOption = l10n.t("Migrate"); + const cancelOption = l10n.t("Cancel"); + + const choice = await window.showInformationMessage( + message, + { modal: true }, + migrateOption, + cancelOption + ); + + if (choice === migrateOption) { + await migrateLegacyConfiguration(workspaceFolder, legacyFilePath); + } +} - // Process each configuration defined in the JSON - await Promise.all( - Object.keys(projectConfJson).map(async (confName) => { - const rawConfig = projectConfJson[confName]; - if (typeof rawConfig !== "object" || rawConfig === null) { +/** + * Migrates legacy configuration to CMakePresets format + */ +export async function migrateLegacyConfiguration(workspaceFolder: Uri, legacyFilePath: Uri): Promise { + // Read legacy configuration + const legacyConfig = await readJson(legacyFilePath.fsPath); + + // Convert to new format + const projectConfElements: { [key: string]: ProjectConfElement } = {}; + + // Process legacy configurations + for (const [confName, rawConfig] of Object.entries(legacyConfig)) { + if (typeof rawConfig === "object" && rawConfig !== null) { + try { + const processedElement = await processLegacyProjectConfig( + rawConfig, + workspaceFolder, + false // Don't resolve paths for migration + ); + projectConfElements[confName] = processedElement; + } catch (error) { Logger.warn( - `Configuration entry "${confName}" is not a valid object. Skipping.`, - new Error("Invalid config entry") + `Failed to migrate configuration "${confName}": ${error.message}`, + error ); - return; // Skip invalid entries } + } + } + + // Save in new format using legacy compatibility function + await saveProjectConfFileLegacy(workspaceFolder, projectConfElements); + + Logger.info(`Successfully migrated ${Object.keys(projectConfElements).length} configurations to CMakePresets.json`); +} - const buildConfig = rawConfig.build; - const openOCDConfig = rawConfig.openOCD; - const tasksConfig = rawConfig.tasks; - const envConfig = rawConfig.env; - - // --- Process Build Configuration --- - const buildDirectoryPath = resolvePaths - ? resolveConfigPaths( - workspaceFolder, - rawConfig, - buildConfig?.buildDirectoryPath, - resolvePaths - ) - : buildConfig?.buildDirectoryPath; - const sdkconfigDefaults = resolvePaths - ? resolveConfigPaths( - workspaceFolder, - rawConfig, - buildConfig?.sdkconfigDefaults, - resolvePaths - ) - : buildConfig?.sdkconfigDefaults; - const sdkconfigFilePath = resolvePaths - ? resolveConfigPaths( - workspaceFolder, - rawConfig, - buildConfig?.sdkconfigFilePath, - resolvePaths - ) - : buildConfig?.sdkconfigFilePath; - const compileArgs = buildConfig?.compileArgs - ?.map((arg: string) => - resolvePaths - ? substituteVariablesInString(arg, workspaceFolder, rawConfig) - : arg - ) - .filter(isDefined); - const ninjaArgs = buildConfig?.ninjaArgs - ?.map((arg: string) => - resolvePaths - ? substituteVariablesInString(arg, workspaceFolder, rawConfig) - : arg - ) - .filter(isDefined); - - // --- Process Environment Variables --- - let processedEnv: { [key: string]: string } | undefined; - if (typeof envConfig === "object" && envConfig !== null) { - processedEnv = {}; - for (const key in envConfig) { - if (Object.prototype.hasOwnProperty.call(envConfig, key)) { - const rawValue = envConfig[key]; - if (typeof rawValue === "string") { - processedEnv[key] = resolvePaths - ? substituteVariablesInString( - rawValue, - workspaceFolder, - rawConfig - ) ?? "" - : rawValue; - } else { - processedEnv[key] = String(rawValue); - } - } + + +/** + * Processes legacy project configuration format + */ +async function processLegacyProjectConfig( + rawConfig: any, + workspaceFolder: Uri, + resolvePaths: boolean +): Promise { + const buildConfig = rawConfig.build; + const openOCDConfig = rawConfig.openOCD; + const tasksConfig = rawConfig.tasks; + const envConfig = rawConfig.env; + + // --- Process Build Configuration --- + const buildDirectoryPath = resolvePaths + ? resolveConfigPaths( + workspaceFolder, + rawConfig, + buildConfig?.buildDirectoryPath, + resolvePaths + ) + : buildConfig?.buildDirectoryPath; + const sdkconfigDefaults = resolvePaths + ? resolveConfigPaths( + workspaceFolder, + rawConfig, + buildConfig?.sdkconfigDefaults, + resolvePaths + ) + : buildConfig?.sdkconfigDefaults; + const sdkconfigFilePath = resolvePaths + ? resolveConfigPaths( + workspaceFolder, + rawConfig, + buildConfig?.sdkconfigFilePath, + resolvePaths + ) + : buildConfig?.sdkconfigFilePath; + const compileArgs = buildConfig?.compileArgs + ?.map((arg: string) => + resolvePaths + ? substituteVariablesInString(arg, workspaceFolder, rawConfig) + : arg + ) + .filter(isDefined); + const ninjaArgs = buildConfig?.ninjaArgs + ?.map((arg: string) => + resolvePaths + ? substituteVariablesInString(arg, workspaceFolder, rawConfig) + : arg + ) + .filter(isDefined); + + // --- Process Environment Variables --- + let processedEnv: { [key: string]: string } | undefined; + if (typeof envConfig === "object" && envConfig !== null) { + processedEnv = {}; + for (const key in envConfig) { + if (Object.prototype.hasOwnProperty.call(envConfig, key)) { + const rawValue = envConfig[key]; + if (typeof rawValue === "string") { + processedEnv[key] = resolvePaths + ? substituteVariablesInString( + rawValue, + workspaceFolder, + rawConfig + ) ?? "" + : rawValue; + } else { + processedEnv[key] = String(rawValue); } } + } + } - // --- Process OpenOCD Configuration --- - const openOCDConfigs = openOCDConfig?.configs; - const openOCDArgs = openOCDConfig?.args - ?.map((arg: string) => - resolvePaths - ? substituteVariablesInString(arg, workspaceFolder, rawConfig) - : arg - ) - .filter(isDefined); - - // --- Process Tasks --- - const preBuild = resolvePaths - ? substituteVariablesInString( - tasksConfig?.preBuild, - workspaceFolder, - rawConfig - ) - : tasksConfig?.preBuild; - const preFlash = resolvePaths - ? substituteVariablesInString( - tasksConfig?.preFlash, - workspaceFolder, - rawConfig - ) - : tasksConfig?.preFlash; - const postBuild = resolvePaths - ? substituteVariablesInString( - tasksConfig?.postBuild, - workspaceFolder, - rawConfig - ) - : tasksConfig?.postBuild; - const postFlash = resolvePaths - ? substituteVariablesInString( - tasksConfig?.postFlash, - workspaceFolder, - rawConfig - ) - : tasksConfig?.postFlash; - - // --- Assemble the Processed Configuration --- - projectConfElements[confName] = { - build: { - compileArgs: compileArgs ?? [], - ninjaArgs: ninjaArgs ?? [], - buildDirectoryPath: buildDirectoryPath, - sdkconfigDefaults: sdkconfigDefaults ?? [], - sdkconfigFilePath: sdkconfigFilePath, - }, - env: processedEnv ?? {}, - idfTarget: rawConfig.idfTarget, - flashBaudRate: rawConfig.flashBaudRate, - monitorBaudRate: rawConfig.monitorBaudRate, - openOCD: { - debugLevel: openOCDConfig?.debugLevel, - configs: openOCDConfigs ?? [], - args: openOCDArgs ?? [], - }, - tasks: { - preBuild: preBuild, - preFlash: preFlash, - postBuild: postBuild, - postFlash: postFlash, - }, - }; - }) - ); + // --- Process OpenOCD Configuration --- + const openOCDConfigs = openOCDConfig?.configs; + const openOCDArgs = openOCDConfig?.args + ?.map((arg: string) => + resolvePaths + ? substituteVariablesInString(arg, workspaceFolder, rawConfig) + : arg + ) + .filter(isDefined); - return projectConfElements; + // --- Process Tasks --- + const preBuild = resolvePaths + ? substituteVariablesInString( + tasksConfig?.preBuild, + workspaceFolder, + rawConfig + ) + : tasksConfig?.preBuild; + const preFlash = resolvePaths + ? substituteVariablesInString( + tasksConfig?.preFlash, + workspaceFolder, + rawConfig + ) + : tasksConfig?.preFlash; + const postBuild = resolvePaths + ? substituteVariablesInString( + tasksConfig?.postBuild, + workspaceFolder, + rawConfig + ) + : tasksConfig?.postBuild; + const postFlash = resolvePaths + ? substituteVariablesInString( + tasksConfig?.postFlash, + workspaceFolder, + rawConfig + ) + : tasksConfig?.postFlash; + + // --- Assemble the Processed Configuration --- + return { + build: { + compileArgs: compileArgs ?? [], + ninjaArgs: ninjaArgs ?? [], + buildDirectoryPath: buildDirectoryPath, + sdkconfigDefaults: sdkconfigDefaults ?? [], + sdkconfigFilePath: sdkconfigFilePath, + }, + env: processedEnv ?? {}, + idfTarget: rawConfig.idfTarget, + flashBaudRate: rawConfig.flashBaudRate, + monitorBaudRate: rawConfig.monitorBaudRate, + openOCD: { + debugLevel: openOCDConfig?.debugLevel, + configs: openOCDConfigs ?? [], + args: openOCDArgs ?? [], + }, + tasks: { + preBuild: preBuild, + preFlash: preFlash, + postBuild: postBuild, + postFlash: postFlash, + }, + }; +} + +/** + * Processes variable substitution and path resolution for ConfigurePreset + */ +async function processConfigurePresetVariables( + preset: ConfigurePreset, + workspaceFolder: Uri, + resolvePaths: boolean +): Promise { + const processedPreset: ConfigurePreset = { + ...preset, + binaryDir: preset.binaryDir ? await processConfigurePresetPath(preset.binaryDir, workspaceFolder, preset, resolvePaths) : undefined, + cacheVariables: preset.cacheVariables ? await processConfigurePresetCacheVariables(preset.cacheVariables, workspaceFolder, preset, resolvePaths) : undefined, + environment: preset.environment ? await processConfigurePresetEnvironment(preset.environment, workspaceFolder, preset, resolvePaths) : undefined, + vendor: preset.vendor ? await processConfigurePresetVendor(preset.vendor, workspaceFolder, preset, resolvePaths) : undefined, + }; + + return processedPreset; +} + +/** + * Processes paths in ConfigurePreset + */ +async function processConfigurePresetPath( + pathValue: string, + workspaceFolder: Uri, + preset: ConfigurePreset, + resolvePaths: boolean +): Promise { + // Apply variable substitution + let processedPath = substituteVariablesInConfigurePreset(pathValue, workspaceFolder, preset); + + if (resolvePaths && processedPath) { + // Resolve relative paths to absolute paths + if (!path.isAbsolute(processedPath)) { + processedPath = path.join(workspaceFolder.fsPath, processedPath); + } + } + + return processedPath || pathValue; +} + +/** + * Processes cache variables in ConfigurePreset + */ +async function processConfigurePresetCacheVariables( + cacheVariables: { [key: string]: any }, + workspaceFolder: Uri, + preset: ConfigurePreset, + resolvePaths: boolean +): Promise<{ [key: string]: any }> { + const processedCacheVariables: { [key: string]: any } = {}; + + for (const [key, value] of Object.entries(cacheVariables)) { + if (typeof value === "string") { + processedCacheVariables[key] = substituteVariablesInConfigurePreset(value, workspaceFolder, preset); + + // Special handling for path-related cache variables + if (resolvePaths && (key === "SDKCONFIG" || key.includes("PATH"))) { + const processedValue = processedCacheVariables[key]; + if (processedValue && !path.isAbsolute(processedValue)) { + processedCacheVariables[key] = path.join(workspaceFolder.fsPath, processedValue); + } + } + } else { + processedCacheVariables[key] = value; + } + } + + return processedCacheVariables; +} + +/** + * Processes environment variables in ConfigurePreset + */ +async function processConfigurePresetEnvironment( + environment: { [key: string]: string }, + workspaceFolder: Uri, + preset: ConfigurePreset, + resolvePaths: boolean +): Promise<{ [key: string]: string }> { + const processedEnvironment: { [key: string]: string } = {}; + + for (const [key, value] of Object.entries(environment)) { + processedEnvironment[key] = substituteVariablesInConfigurePreset(value, workspaceFolder, preset) || value; + } + + return processedEnvironment; +} + +/** + * Processes vendor-specific settings in ConfigurePreset + */ +async function processConfigurePresetVendor( + vendor: ESPIDFVendorSettings, + workspaceFolder: Uri, + preset: ConfigurePreset, + resolvePaths: boolean +): Promise { + const processedVendor: ESPIDFVendorSettings = { + "espressif/vscode-esp-idf": { + settings: [] + } + }; + + const espIdfSettings = vendor["espressif/vscode-esp-idf"]?.settings || []; + + for (const setting of espIdfSettings) { + const processedSetting: ESPIDFSettings = { ...setting }; + + // Process string values in settings + if (typeof setting.value === "string") { + processedSetting.value = substituteVariablesInConfigurePreset(setting.value, workspaceFolder, preset) || setting.value; + } else if (Array.isArray(setting.value)) { + // Process arrays of strings + processedSetting.value = setting.value.map(item => + typeof item === "string" + ? substituteVariablesInConfigurePreset(item, workspaceFolder, preset) || item + : item + ); + } else if (typeof setting.value === "object" && setting.value !== null) { + // Process objects (like openOCD settings) + processedSetting.value = await processConfigurePresetSettingObject(setting.value, workspaceFolder, preset, resolvePaths); + } + + processedVendor["espressif/vscode-esp-idf"].settings.push(processedSetting); + } + + return processedVendor; +} + +/** + * Processes object values in vendor settings + */ +async function processConfigurePresetSettingObject( + obj: any, + workspaceFolder: Uri, + preset: ConfigurePreset, + resolvePaths: boolean +): Promise { + const processedObj: any = {}; + + for (const [key, value] of Object.entries(obj)) { + if (typeof value === "string") { + processedObj[key] = substituteVariablesInConfigurePreset(value, workspaceFolder, preset) || value; + } else if (Array.isArray(value)) { + processedObj[key] = value.map(item => + typeof item === "string" + ? substituteVariablesInConfigurePreset(item, workspaceFolder, preset) || item + : item + ); + } else { + processedObj[key] = value; + } + } + + return processedObj; +} + +/** + * Processes variable substitution and path resolution for ProjectConfElement (legacy compatibility) + */ +async function processProjectConfElementVariables( + element: ProjectConfElement, + workspaceFolder: Uri, + resolvePaths: boolean +): Promise { + // Create a temporary raw config object for variable substitution + const rawConfig = { + build: element.build, + env: element.env, + idfTarget: element.idfTarget, + flashBaudRate: element.flashBaudRate, + monitorBaudRate: element.monitorBaudRate, + openOCD: element.openOCD, + tasks: element.tasks, + }; + + return processLegacyProjectConfig(rawConfig, workspaceFolder, resolvePaths); +} + +/** + * Substitutes variables like ${workspaceFolder} and ${env:VARNAME} in a string for ConfigurePreset. + * @param text The input string potentially containing variables. + * @param workspaceFolder The workspace folder Uri to resolve ${workspaceFolder}. + * @param preset The ConfigurePreset to resolve ${config:VARNAME} variables. + * @returns The string with variables substituted, or undefined if input was undefined/null. + */ +function substituteVariablesInConfigurePreset( + text: string | undefined, + workspaceFolder: Uri, + preset: ConfigurePreset +): string | undefined { + if (text === undefined || text === null) { + return undefined; + } + + let result = text; + + const regexp = /\$\{(.*?)\}/g; // Find ${anything} + result = result.replace(regexp, (match: string, name: string) => { + if (match.indexOf("config:") > 0) { + const configVar = name.substring( + name.indexOf("config:") + "config:".length + ); + + const delimiterIndex = configVar.indexOf(","); + let configVarName = configVar; + let prefix = ""; + + // Check if a delimiter (e.g., ",") is present + if (delimiterIndex > -1) { + configVarName = configVar.substring(0, delimiterIndex); + prefix = configVar.substring(delimiterIndex + 1).trim(); + } + + const configVarValue = getConfigurePresetParameterValue(configVarName, preset); + + if (!configVarValue) { + return match; + } + + if (prefix && Array.isArray(configVarValue)) { + return configVarValue.map((value) => `${prefix}${value}`).join(" "); + } + + if (prefix && typeof configVarValue === "string") { + return `${prefix} ${configVarValue}`; + } + + return configVarValue; + } + if (match.indexOf("env:") > 0) { + const envVarName = name.substring(name.indexOf("env:") + "env:".length); + if (preset.environment && preset.environment[envVarName]) { + return preset.environment[envVarName]; + } + if (process.env[envVarName]) { + return process.env[envVarName]; + } + return match; + } + if (match.indexOf("workspaceRoot") > 0) { + return workspaceFolder.fsPath; + } + if (match.indexOf("workspaceFolder") > 0) { + return workspaceFolder.fsPath; + } + return match; + }); + + // Substitute ${config:VARNAME} + result = resolveVariables(result, workspaceFolder); + + return result; +} + +/** + * Gets parameter value from ConfigurePreset for variable substitution + */ +function getConfigurePresetParameterValue(param: string, preset: ConfigurePreset): any { + switch (param) { + case "idf.cmakeCompilerArgs": + return getESPIDFSettingValue(preset, "compileArgs") || ""; + case "idf.ninjaArgs": + return getESPIDFSettingValue(preset, "ninjaArgs") || ""; + case "idf.buildPath": + return preset.binaryDir || ""; + case "idf.sdkconfigDefaults": + const sdkconfigDefaults = preset.cacheVariables?.SDKCONFIG_DEFAULTS; + return sdkconfigDefaults ? sdkconfigDefaults.split(";") : ""; + case "idf.flashBaudRate": + return getESPIDFSettingValue(preset, "flashBaudRate") || ""; + case "idf.monitorBaudRate": + return getESPIDFSettingValue(preset, "monitorBaudRate") || ""; + case "idf.openOcdDebugLevel": + const openOCDSettings = getESPIDFSettingValue(preset, "openOCD"); + return openOCDSettings?.debugLevel && openOCDSettings.debugLevel > -1 + ? openOCDSettings.debugLevel.toString() + : ""; + case "idf.openOcdConfigs": + const openOCDConfigs = getESPIDFSettingValue(preset, "openOCD"); + return openOCDConfigs?.configs && openOCDConfigs.configs.length + ? openOCDConfigs.configs + : ""; + case "idf.openOcdLaunchArgs": + const openOCDArgs = getESPIDFSettingValue(preset, "openOCD"); + return openOCDArgs?.args && openOCDArgs.args.length + ? openOCDArgs.args + : ""; + case "idf.preBuildTask": + const preBuildTask = getESPIDFSettingValue(preset, "tasks"); + return preBuildTask?.preBuild || ""; + case "idf.postBuildTask": + const postBuildTask = getESPIDFSettingValue(preset, "tasks"); + return postBuildTask?.postBuild || ""; + case "idf.preFlashTask": + const preFlashTask = getESPIDFSettingValue(preset, "tasks"); + return preFlashTask?.preFlash || ""; + case "idf.postFlashTask": + const postFlashTask = getESPIDFSettingValue(preset, "tasks"); + return postFlashTask?.postFlash || ""; + case "idf.sdkconfigFilePath": + return preset.cacheVariables?.SDKCONFIG || ""; + default: + return ""; + } +} + +/** + * Helper function to get ESP-IDF setting value from ConfigurePreset + */ +function getESPIDFSettingValue(preset: ConfigurePreset, settingType: string): any { + const espIdfSettings = preset.vendor?.["espressif/vscode-esp-idf"]?.settings || []; + const setting = espIdfSettings.find(s => s.type === settingType); + return setting ? setting.value : undefined; +} + +/** + * Converts ConfigurePreset to ProjectConfElement for store compatibility + */ +export function configurePresetToProjectConfElement(preset: ConfigurePreset): ProjectConfElement { + return convertConfigurePresetToProjectConfElement(preset, Uri.file(""), false); +} + +/** + * Converts ProjectConfElement to ConfigurePreset for store compatibility + */ +export function projectConfElementToConfigurePreset(name: string, element: ProjectConfElement): ConfigurePreset { + return convertProjectConfElementToConfigurePreset(name, element); } /** @@ -508,3 +988,102 @@ export async function getProjectConfigurationElements( function isDefined(value: T | undefined): value is T { return value !== undefined; } + +/** + * Converts a CMakePresets ConfigurePreset to the legacy ProjectConfElement format + */ +function convertConfigurePresetToProjectConfElement( + preset: ConfigurePreset, + workspaceFolder: Uri, + resolvePaths: boolean = false +): ProjectConfElement { + // Extract ESP-IDF specific settings from vendor section + const espIdfSettings = preset.vendor?.["espressif/vscode-esp-idf"]?.settings || []; + + // Helper function to find setting by type + const findSetting = (type: string): any => { + const setting = espIdfSettings.find(s => s.type === type); + return setting ? setting.value : undefined; + }; + + // Extract values with defaults + const compileArgs = findSetting("compileArgs") || []; + const ninjaArgs = findSetting("ninjaArgs") || []; + const flashBaudRate = findSetting("flashBaudRate") || ""; + const monitorBaudRate = findSetting("monitorBaudRate") || ""; + const openOCDSettings = findSetting("openOCD") || { debugLevel: -1, configs: [], args: [] }; + const taskSettings = findSetting("tasks") || { preBuild: "", preFlash: "", postBuild: "", postFlash: "" }; + + // Process paths based on resolvePaths flag + const binaryDir = preset.binaryDir || ""; + const buildDirectoryPath = resolvePaths && binaryDir + ? (path.isAbsolute(binaryDir) ? binaryDir : path.join(workspaceFolder.fsPath, binaryDir)) + : binaryDir; + + // Process SDKCONFIG_DEFAULTS - convert semicolon-separated string to array + const sdkconfigDefaultsStr = preset.cacheVariables?.SDKCONFIG_DEFAULTS || ""; + const sdkconfigDefaults = sdkconfigDefaultsStr ? sdkconfigDefaultsStr.split(";") : []; + + return { + build: { + compileArgs: Array.isArray(compileArgs) ? compileArgs : [], + ninjaArgs: Array.isArray(ninjaArgs) ? ninjaArgs : [], + buildDirectoryPath, + sdkconfigDefaults, + sdkconfigFilePath: preset.cacheVariables?.SDKCONFIG || "", + }, + env: preset.environment || {}, + idfTarget: preset.cacheVariables?.IDF_TARGET || "", + flashBaudRate, + monitorBaudRate, + openOCD: { + debugLevel: openOCDSettings.debugLevel || -1, + configs: Array.isArray(openOCDSettings.configs) ? openOCDSettings.configs : [], + args: Array.isArray(openOCDSettings.args) ? openOCDSettings.args : [], + }, + tasks: { + preBuild: taskSettings.preBuild || "", + preFlash: taskSettings.preFlash || "", + postBuild: taskSettings.postBuild || "", + postFlash: taskSettings.postFlash || "", + }, + }; +} + +/** + * Converts a ProjectConfElement to CMakePresets ConfigurePreset format + */ +function convertProjectConfElementToConfigurePreset( + name: string, + element: ProjectConfElement +): ConfigurePreset { + // Convert SDKCONFIG_DEFAULTS array to semicolon-separated string + const sdkconfigDefaults = element.build.sdkconfigDefaults.length > 0 + ? element.build.sdkconfigDefaults.join(";") + : undefined; + + const settings: ESPIDFSettings[] = [ + { type: "compileArgs", value: element.build.compileArgs }, + { type: "ninjaArgs", value: element.build.ninjaArgs }, + { type: "flashBaudRate", value: element.flashBaudRate }, + { type: "monitorBaudRate", value: element.monitorBaudRate }, + { type: "openOCD", value: element.openOCD }, + { type: "tasks", value: element.tasks }, + ]; + + return { + name, + binaryDir: element.build.buildDirectoryPath || undefined, + cacheVariables: { + ...(element.idfTarget && { IDF_TARGET: element.idfTarget }), + ...(sdkconfigDefaults && { SDKCONFIG_DEFAULTS: sdkconfigDefaults }), + ...(element.build.sdkconfigFilePath && { SDKCONFIG: element.build.sdkconfigFilePath }), + }, + environment: Object.keys(element.env).length > 0 ? element.env : undefined, + vendor: { + "espressif/vscode-esp-idf": { + settings, + }, + }, + }; +} diff --git a/src/project-conf/projectConfPanel.ts b/src/project-conf/projectConfPanel.ts index 41fdbb1ea..46ac1cd9b 100644 --- a/src/project-conf/projectConfPanel.ts +++ b/src/project-conf/projectConfPanel.ts @@ -28,7 +28,7 @@ import { } from "vscode"; import { join } from "path"; import { ESP } from "../config"; -import { getProjectConfigurationElements, saveProjectConfFile } from "."; +import { getProjectConfigurationElements, saveProjectConfFileLegacy, configurePresetToProjectConfElement, projectConfElementToConfigurePreset } from "."; import { IdfTarget } from "../espIdf/setTarget/getTargets"; export class projectConfigurationPanel { @@ -96,10 +96,17 @@ export class projectConfigurationPanel { this.panel.webview.html = this.createSetupHtml(scriptPath); this.panel.webview.onDidReceiveMessage(async (message) => { - let projectConfObj = await getProjectConfigurationElements( + let projectConfPresets = await getProjectConfigurationElements( this.workspaceFolder, false // Don't resolve paths for display ); + + // Convert ConfigurePresets to legacy format for webview compatibility + let projectConfObj: { [key: string]: ProjectConfElement } = {}; + for (const [name, preset] of Object.entries(projectConfPresets)) { + projectConfObj[name] = configurePresetToProjectConfElement(preset); + } + switch (message.command) { case "command": break; @@ -185,7 +192,7 @@ export class projectConfigurationPanel { }) { const projectConfKeys = Object.keys(projectConfDict); this.clearSelectedProject(projectConfKeys); - await saveProjectConfFile(this.workspaceFolder, projectConfDict); + await saveProjectConfFileLegacy(this.workspaceFolder, projectConfDict); window.showInformationMessage( "Project Configuration changes has been saved" ); diff --git a/src/project-conf/projectConfiguration.ts b/src/project-conf/projectConfiguration.ts index ed33d8eac..cf541e845 100644 --- a/src/project-conf/projectConfiguration.ts +++ b/src/project-conf/projectConfiguration.ts @@ -16,6 +16,7 @@ * limitations under the License. */ +// Legacy interface for backward compatibility export interface ProjectConfElement { build: { compileArgs: string[]; @@ -40,3 +41,47 @@ export interface ProjectConfElement { postFlash: string; }; } + +// New CMakePresets interfaces +export interface CMakeVersion { + major: number; + minor: number; + patch: number; +} + +export interface ESPIDFSettings { + type: "compileArgs" | "ninjaArgs" | "flashBaudRate" | "monitorBaudRate" | "openOCD" | "tasks"; + value: any; +} + +export interface ESPIDFVendorSettings { + "espressif/vscode-esp-idf": { + settings: ESPIDFSettings[]; + }; +} + +export interface ConfigurePreset { + name: string; + binaryDir?: string; + cacheVariables?: { + IDF_TARGET?: string; + SDKCONFIG_DEFAULTS?: string; + SDKCONFIG?: string; + [key: string]: any; + }; + environment?: { [key: string]: string }; + vendor?: ESPIDFVendorSettings; +} + +export interface BuildPreset { + name: string; + configurePreset: string; +} + +export interface CMakePresets { + $schema?: string; + version: number; + cmakeMinimumRequired?: CMakeVersion; + configurePresets?: ConfigurePreset[]; + buildPresets?: BuildPreset[]; // Optional - not used by ESP-IDF extension +} From 05b318c2f5b26b4aca9208f342ab9db8123303ac Mon Sep 17 00:00:00 2001 From: Radu Date: Tue, 9 Sep 2025 14:59:42 +0300 Subject: [PATCH 02/21] feature: add idf.py --preset --- package.json | 6 +++ src/build/buildTask.ts | 102 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/package.json b/package.json index ca3bbaf04..8ac99459f 100644 --- a/package.json +++ b/package.json @@ -753,6 +753,12 @@ "description": "%param.buildPath%", "scope": "resource" }, + "idf.useCMakePresets": { + "type": "boolean", + "default": false, + "description": "Use idf.py --preset build instead of direct CMake calls. Requires ESP-IDF with CMakePresets support.", + "scope": "resource" + }, "idf.buildPathWin": { "type": "string", "default": "${workspaceFolder}\\build", diff --git a/src/build/buildTask.ts b/src/build/buildTask.ts index fb590effb..360d1df3a 100644 --- a/src/build/buildTask.ts +++ b/src/build/buildTask.ts @@ -78,6 +78,22 @@ export class BuildTask { throw new Error("ALREADY_BUILDING"); } this.building(true); + + // Check if CMakePresets build mode is enabled + const useCMakePresets = idfConf.readParameter( + "idf.useCMakePresets", + this.currentWorkspace + ) as boolean; + + if (useCMakePresets) { + return await this.buildWithPresets(buildType); + } + + // Continue with traditional CMake build + return await this.buildWithCMake(buildType); + } + + private async buildWithCMake(buildType?: ESP.BuildType) { await ensureDir(this.buildDirPath); const modifiedEnv = await appendIdfAndToolsToPath(this.currentWorkspace); const processOptions = { @@ -290,4 +306,90 @@ export class BuildTask { buildPresentationOptions ); } + + private async buildWithPresets(buildType?: ESP.BuildType) { + await ensureDir(this.buildDirPath); + const modifiedEnv = await appendIdfAndToolsToPath(this.currentWorkspace); + + // Get the selected project configuration preset name + const selectedConfig = ESP.ProjectConfiguration.store.get( + ESP.ProjectConfiguration.SELECTED_CONFIG + ); + + if (!selectedConfig) { + throw new Error( + "No project configuration selected. Please select a CMakePresets configuration first." + ); + } + + const currentWorkspaceFolder = vscode.workspace.workspaceFolders.find( + (w) => w.uri === this.currentWorkspace + ); + + const notificationMode = idfConf.readParameter( + "idf.notificationMode", + this.currentWorkspace + ) as string; + const showTaskOutput = + notificationMode === idfConf.NotificationMode.All || + notificationMode === idfConf.NotificationMode.Output + ? vscode.TaskRevealKind.Always + : vscode.TaskRevealKind.Silent; + + // Build idf.py command with preset + const pythonBinPath = await getVirtualEnvPythonPath(this.currentWorkspace); + const idfPy = join(this.idfPathDir, "tools", "idf.py"); + + let args = [idfPy, "--preset", selectedConfig]; + + // Add build type specific arguments if needed + if (buildType) { + switch (buildType) { + case ESP.BuildType.Bootloader: + args.push("bootloader"); + break; + case ESP.BuildType.PartitionTable: + args.push("partition-table"); + break; + default: + args.push("build"); + break; + } + } else { + args.push("build"); + } + + Logger.info(`Building with CMakePresets using: ${pythonBinPath} ${args.join(" ")}`); + + const processOptions = { + cwd: this.currentWorkspace.fsPath, + env: modifiedEnv, + }; + + const buildExecution = new vscode.ProcessExecution( + pythonBinPath, + args, + processOptions + ); + + const buildPresentationOptions = { + reveal: showTaskOutput, + showReuseMessage: false, + clear: false, + panel: vscode.TaskPanelKind.Shared, + } as vscode.TaskPresentationOptions; + + TaskManager.addTask( + { + type: "esp-idf", + command: `ESP-IDF Build (CMakePresets: ${selectedConfig})`, + taskId: "idf-build-presets-task", + }, + currentWorkspaceFolder || vscode.TaskScope.Workspace, + `ESP-IDF Build (CMakePresets: ${selectedConfig})`, + buildExecution, + ["espIdf"], + buildPresentationOptions + ); + } } From 76a0ba54e728a4bec8865a747c19a59c0da6dad5 Mon Sep 17 00:00:00 2001 From: Radu Date: Wed, 17 Sep 2025 16:37:28 +0300 Subject: [PATCH 03/21] feat: add JSON validation for local schema --- .../esp-idf-cmakepresets-schema-v1.json | 122 ++++++++++++++++++ package.json | 9 ++ 2 files changed, 131 insertions(+) create mode 100644 internal/com.espressif.idf.uploads/cmakepresets/esp-idf-cmakepresets-schema-v1.json diff --git a/internal/com.espressif.idf.uploads/cmakepresets/esp-idf-cmakepresets-schema-v1.json b/internal/com.espressif.idf.uploads/cmakepresets/esp-idf-cmakepresets-schema-v1.json new file mode 100644 index 000000000..951d9a354 --- /dev/null +++ b/internal/com.espressif.idf.uploads/cmakepresets/esp-idf-cmakepresets-schema-v1.json @@ -0,0 +1,122 @@ +{ + "$id": "https://dl.espressif.com/schemas/esp-idf-cmakepresets-schema-v1.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "ESP-IDF CMakePresets Extension v1.0", + "description": "Extends the official CMakePresets schema (v3) with strict ESP-IDF vendor fields; requires CMake >= 3.21.", + "allOf": [ + { + "$ref": "https://raw.githubusercontent.com/Kitware/CMake/master/Help/manual/presets/schema.json" + }, + { + "type": "object", + "properties": { + "version": { + "const": 3, + "description": "CMake Presets format version. Must be 3." + }, + "cmakeMinimumRequired": { + "type": "object", + "properties": { + "major": { "type": "integer", "const": 3 }, + "minor": { "type": "integer", "minimum": 21 }, + "patch": { "type": "integer", "minimum": 0 } + }, + "required": ["major", "minor"] + }, + "configurePresets": { + "type": "array", + "items": { + "type": "object", + "properties": { + "vendor": { + "type": "object", + "properties": { + "espressif/vscode-esp-idf": { + "type": "object", + "properties": { + "schemaVersion": { + "type": "integer", + "enum": [1], + "description": "ESP-IDF vendor schema version." + }, + "settings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "compileArgs", + "ninjaArgs", + "flashBaudRate", + "monitorBaudRate", + "openOCD", + "tasks" + ] + }, + "value": {} + }, + "required": ["type", "value"], + "additionalProperties": false, + "allOf": [ + { + "if": { "properties": { "type": { "enum": ["compileArgs", "ninjaArgs"] } } }, + "then": { "properties": { "value": { "type": "array", "items": { "type": "string" } } } } + }, + { + "if": { "properties": { "type": { "enum": ["flashBaudRate", "monitorBaudRate"] } } }, + "then": { "properties": { "value": { "type": "string" } } } + }, + { + "if": { "properties": { "type": { "const": "openOCD" } } }, + "then": { + "properties": { + "value": { + "type": "object", + "properties": { + "debugLevel": { "type": "integer" }, + "configs": { "type": "array", "items": { "type": "string" } }, + "args": { "type": "array", "items": { "type": "string" } } + }, + "required": ["debugLevel", "configs", "args"], + "additionalProperties": false + } + } + } + }, + { + "if": { "properties": { "type": { "const": "tasks" } } }, + "then": { + "properties": { + "value": { + "type": "object", + "properties": { + "preBuild": { "type": "string" }, + "preFlash": { "type": "string" }, + "postBuild": { "type": "string" }, + "postFlash": { "type": "string" } + }, + "additionalProperties": false + } + } + } + } + ] + } + } + }, + "required": ["schemaVersion", "settings"], + "additionalProperties": false + } + }, + "additionalProperties": true + } + } + } + } + }, + "required": ["version", "cmakeMinimumRequired", "configurePresets"] + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 8ac99459f..beece7e5e 100644 --- a/package.json +++ b/package.json @@ -114,6 +114,15 @@ "main": "./dist/extension", "l10n": "./l10n", "contributes": { + "jsonValidation": [ + { + "fileMatch": [ + "CMakePresets.json", + "CMakeUserPresets.json" + ], + "url": "./internal/com.espressif.idf.uploads/cmakepresets/esp-idf-cmakepresets-schema-v1.json" + } + ], "walkthroughs": [ { "id": "espIdf.walkthrough.basic-usage", From 4885b426490a3f2b5f4a5fdcee3f883b3c53d0d2 Mon Sep 17 00:00:00 2001 From: Radu Date: Wed, 17 Sep 2025 16:40:23 +0300 Subject: [PATCH 04/21] feat: add CMakeUserPresets.json support --- src/config.ts | 1 + .../ProjectConfigurationManager.ts | 226 ++++--- src/project-conf/index.ts | 568 ++++++++++++++---- src/project-conf/projectConfiguration.ts | 9 +- src/statusBar/index.ts | 16 +- 5 files changed, 635 insertions(+), 185 deletions(-) diff --git a/src/config.ts b/src/config.ts index f0db7df60..d62a4187e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -31,6 +31,7 @@ export namespace ESP { export let store: ProjectConfigStore; export const SELECTED_CONFIG = "SELECTED_PROJECT_CONFIG"; export const PROJECT_CONFIGURATION_FILENAME = "CMakePresets.json"; + export const USER_CONFIGURATION_FILENAME = "CMakeUserPresets.json"; } export enum BuildType { diff --git a/src/project-conf/ProjectConfigurationManager.ts b/src/project-conf/ProjectConfigurationManager.ts index 71d69388b..312fbaa94 100644 --- a/src/project-conf/ProjectConfigurationManager.ts +++ b/src/project-conf/ProjectConfigurationManager.ts @@ -20,7 +20,12 @@ import { CommandKeys, createCommandDictionary } from "../cmdTreeView/cmdStore"; import { createStatusBarItem } from "../statusBar"; import { getIdfTargetFromSdkconfig } from "../workspaceConfig"; import { Logger } from "../logger/logger"; -import { getProjectConfigurationElements, configurePresetToProjectConfElement, promptLegacyMigration, migrateLegacyConfiguration } from "./index"; +import { + getProjectConfigurationElements, + configurePresetToProjectConfElement, + promptLegacyMigration, + migrateLegacyConfiguration, +} from "./index"; import { pathExists } from "fs-extra"; import { configureClangSettings } from "../clang"; @@ -39,9 +44,11 @@ export function clearSelectedProjectConfiguration(): void { } export class ProjectConfigurationManager { - private readonly configFilePath: string; + private readonly cmakePresetsFilePath: string; + private readonly cmakeUserPresetsFilePath: string; private configVersions: string[] = []; - private configWatcher: FileSystemWatcher; + private cmakePresetsWatcher: FileSystemWatcher; + private cmakeUserPresetsWatcher: FileSystemWatcher; private statusBarItems: { [key: string]: StatusBarItem }; private workspaceUri: Uri; private context: ExtensionContext; @@ -57,13 +64,26 @@ export class ProjectConfigurationManager { this.statusBarItems = statusBarItems; this.commandDictionary = createCommandDictionary(); - this.configFilePath = Uri.joinPath( + this.cmakePresetsFilePath = Uri.joinPath( workspaceUri, ESP.ProjectConfiguration.PROJECT_CONFIGURATION_FILENAME ).fsPath; - this.configWatcher = workspace.createFileSystemWatcher( - this.configFilePath, + this.cmakeUserPresetsFilePath = Uri.joinPath( + workspaceUri, + ESP.ProjectConfiguration.USER_CONFIGURATION_FILENAME + ).fsPath; + + // Watch both CMakePresets.json and CMakeUserPresets.json + this.cmakePresetsWatcher = workspace.createFileSystemWatcher( + this.cmakePresetsFilePath, + false, + false, + false + ); + + this.cmakeUserPresetsWatcher = workspace.createFileSystemWatcher( + this.cmakeUserPresetsFilePath, false, false, false @@ -75,26 +95,23 @@ export class ProjectConfigurationManager { } private async initialize(): Promise { - if (!fileExists(this.configFilePath)) { - // CMakePresets.json doesn't exist - check for legacy file + const cmakePresetsExists = fileExists(this.cmakePresetsFilePath); + const cmakeUserPresetsExists = fileExists(this.cmakeUserPresetsFilePath); + + if (!cmakePresetsExists && !cmakeUserPresetsExists) { + // Neither CMakePresets.json nor CMakeUserPresets.json exists - check for legacy file await this.checkForLegacyFile(); return; } try { - const configContent = readFileSync(this.configFilePath); - - // Handle edge case: File exists but is empty - if (!configContent || configContent.trim() === "") { - Logger.warn( - `Project configuration file is empty: ${this.configFilePath}` - ); - this.configVersions = []; - return; - } + // Use the updated getProjectConfigurationElements function that handles both files + const projectConfElements = await getProjectConfigurationElements( + this.workspaceUri, + false // Don't resolve paths for initialization + ); - const configData = JSON.parse(configContent); - this.configVersions = Object.keys(configData); + this.configVersions = Object.keys(projectConfElements); // Check if the currently selected configuration is valid const currentSelectedConfig = ESP.ProjectConfiguration.store.get( @@ -116,25 +133,31 @@ export class ProjectConfigurationManager { this.setNoConfigurationSelectedStatus(); } else if (this.configVersions.length > 0) { // No current selection but configurations exist + const fileInfo = []; + if (cmakePresetsExists) fileInfo.push("CMakePresets.json"); + if (cmakeUserPresetsExists) fileInfo.push("CMakeUserPresets.json"); + window.showInformationMessage( `Loaded ${ this.configVersions.length - } project configuration(s): ${this.configVersions.join(", ")}` + } project configuration(s) from ${fileInfo.join( + " and " + )}: ${this.configVersions.join(", ")}` ); this.setNoConfigurationSelectedStatus(); } else { - // Empty configuration file + // No configurations found Logger.info( - `Project configuration file loaded but contains no configurations: ${this.configFilePath}` + `Project configuration files loaded but contain no configurations` ); this.setNoConfigurationSelectedStatus(); } } catch (error) { window.showErrorMessage( - `Error reading or parsing project configuration file (${this.configFilePath}): ${error.message}` + `Error reading or parsing project configuration files: ${error.message}` ); Logger.errorNotify( - `Failed to parse project configuration file: ${this.configFilePath}`, + `Failed to parse project configuration files`, error, "ProjectConfigurationManager initialize" ); @@ -144,32 +167,55 @@ export class ProjectConfigurationManager { } private registerEventHandlers(): void { - // Handle file changes - const changeDisposable = this.configWatcher.onDidChange( + // Handle CMakePresets.json file changes + const cmakePresetsChangeDisposable = this.cmakePresetsWatcher.onDidChange( async () => await this.handleConfigFileChange() ); - // Handle file deletion - const deleteDisposable = this.configWatcher.onDidDelete( + // Handle CMakePresets.json file deletion + const cmakePresetsDeleteDisposable = this.cmakePresetsWatcher.onDidDelete( async () => await this.handleConfigFileDelete() ); - // Handle file creation - const createDisposable = this.configWatcher.onDidCreate( + // Handle CMakePresets.json file creation + const cmakePresetsCreateDisposable = this.cmakePresetsWatcher.onDidCreate( + async () => await this.handleConfigFileCreate() + ); + + // Handle CMakeUserPresets.json file changes + const cmakeUserPresetsChangeDisposable = this.cmakeUserPresetsWatcher.onDidChange( + async () => await this.handleConfigFileChange() + ); + + // Handle CMakeUserPresets.json file deletion + const cmakeUserPresetsDeleteDisposable = this.cmakeUserPresetsWatcher.onDidDelete( + async () => await this.handleConfigFileDelete() + ); + + // Handle CMakeUserPresets.json file creation + const cmakeUserPresetsCreateDisposable = this.cmakeUserPresetsWatcher.onDidCreate( async () => await this.handleConfigFileCreate() ); this.context.subscriptions.push( - changeDisposable, - deleteDisposable, - createDisposable + cmakePresetsChangeDisposable, + cmakePresetsDeleteDisposable, + cmakePresetsCreateDisposable, + cmakeUserPresetsChangeDisposable, + cmakeUserPresetsDeleteDisposable, + cmakeUserPresetsCreateDisposable ); } private async handleConfigFileChange(): Promise { try { - const configData = await readJson(this.configFilePath); - const currentVersions = Object.keys(configData); + // Use the updated getProjectConfigurationElements function that handles both files + const projectConfElements = await getProjectConfigurationElements( + this.workspaceUri, + false // Don't resolve paths for change handling + ); + + const currentVersions = Object.keys(projectConfElements); // Find added versions const addedVersions = currentVersions.filter( @@ -183,13 +229,13 @@ export class ProjectConfigurationManager { if (addedVersions.length > 0) { window.showInformationMessage( - `New versions added: ${addedVersions.join(", ")}` + `New configurations added: ${addedVersions.join(", ")}` ); } if (removedVersions.length > 0) { window.showInformationMessage( - `Versions removed: ${removedVersions.join(", ")}` + `Configurations removed: ${removedVersions.join(", ")}` ); } @@ -220,7 +266,9 @@ export class ProjectConfigurationManager { this.setNoConfigurationSelectedStatus(); } } catch (error) { - window.showErrorMessage(`Error parsing config file: ${error.message}`); + window.showErrorMessage( + `Error parsing configuration files: ${error.message}` + ); this.setNoConfigurationSelectedStatus(); } } @@ -255,8 +303,13 @@ export class ProjectConfigurationManager { private async handleConfigFileCreate(): Promise { try { - const configData = await readJson(this.configFilePath); - this.configVersions = Object.keys(configData); + // Use the updated getProjectConfigurationElements function that handles both files + const projectConfElements = await getProjectConfigurationElements( + this.workspaceUri, + false // Don't resolve paths for creation handling + ); + + this.configVersions = Object.keys(projectConfElements); // If we have versions, check if current selection is valid if (this.configVersions.length > 0) { @@ -291,7 +344,7 @@ export class ProjectConfigurationManager { } } catch (error) { window.showErrorMessage( - `Error parsing newly created config file: ${error.message}` + `Error parsing newly created configuration file: ${error.message}` ); this.setNoConfigurationSelectedStatus(); } @@ -334,9 +387,11 @@ export class ProjectConfigurationManager { this.workspaceUri, true // Resolve paths for building ); - + // Convert ConfigurePreset to ProjectConfElement for store compatibility - const legacyElement = configurePresetToProjectConfElement(resolvedConfig[configName]); + const legacyElement = configurePresetToProjectConfElement( + resolvedConfig[configName] + ); ESP.ProjectConfiguration.store.set(configName, legacyElement); // Update UI @@ -380,8 +435,11 @@ export class ProjectConfigurationManager { Object.keys(projectConfigurations).length === 0 ) { // Check if we have legacy configurations to migrate - const legacyFilePath = Uri.joinPath(this.workspaceUri, "esp_idf_project_configuration.json"); - + const legacyFilePath = Uri.joinPath( + this.workspaceUri, + "esp_idf_project_configuration.json" + ); + if (await pathExists(legacyFilePath.fsPath)) { // Show migration dialog await this.handleLegacyMigrationDialog(legacyFilePath); @@ -430,32 +488,37 @@ export class ProjectConfigurationManager { * Checks for legacy esp_idf_project_configuration.json file and shows appropriate status */ private async checkForLegacyFile(): Promise { - const legacyFilePath = Uri.joinPath(this.workspaceUri, "esp_idf_project_configuration.json").fsPath; - + const legacyFilePath = Uri.joinPath( + this.workspaceUri, + "esp_idf_project_configuration.json" + ).fsPath; + if (fileExists(legacyFilePath)) { // Legacy file exists - show status bar with migration option this.configVersions = []; - + try { const legacyContent = readFileSync(legacyFilePath); if (legacyContent && legacyContent.trim() !== "") { const legacyData = JSON.parse(legacyContent); const legacyConfigNames = Object.keys(legacyData); - + if (legacyConfigNames.length > 0) { // Show status bar indicating legacy configurations are available this.setLegacyConfigurationStatus(legacyConfigNames); - + // Show migration notification this.showLegacyMigrationNotification(legacyConfigNames); return; } } } catch (error) { - Logger.warn(`Failed to parse legacy configuration file: ${error.message}`); + Logger.warn( + `Failed to parse legacy configuration file: ${error.message}` + ); } } - + // No configuration files found - clear everything this.clearConfigurationState(); } @@ -465,7 +528,9 @@ export class ProjectConfigurationManager { */ private setLegacyConfigurationStatus(legacyConfigNames: string[]): void { const statusBarItemName = `Legacy Configs (${legacyConfigNames.length})`; - const statusBarItemTooltip = `Found legacy project configurations: ${legacyConfigNames.join(", ")}. Click to migrate to CMakePresets.json format.`; + const statusBarItemTooltip = `Found legacy project configurations: ${legacyConfigNames.join( + ", " + )}. Click to migrate to CMakePresets.json format.`; const commandToUse = "espIdf.projectConf"; if (this.statusBarItems["projectConf"]) { @@ -487,25 +552,30 @@ export class ProjectConfigurationManager { /** * Shows notification about legacy configurations */ - private async showLegacyMigrationNotification(legacyConfigNames: string[]): Promise { + private async showLegacyMigrationNotification( + legacyConfigNames: string[] + ): Promise { const message = l10n.t( "Found {0} legacy project configuration(s): {1}. Would you like to migrate them to the new CMakePresets.json format? Your original file will remain unchanged.", legacyConfigNames.length, legacyConfigNames.join(", ") ); - + const migrateOption = l10n.t("Migrate Now"); const laterOption = l10n.t("Later"); - + const choice = await window.showInformationMessage( message, migrateOption, laterOption ); - + if (choice === migrateOption) { // Directly perform migration without additional popup - const legacyFilePath = Uri.joinPath(this.workspaceUri, "esp_idf_project_configuration.json"); + const legacyFilePath = Uri.joinPath( + this.workspaceUri, + "esp_idf_project_configuration.json" + ); await this.performDirectMigration(legacyFilePath); } } @@ -513,28 +583,30 @@ export class ProjectConfigurationManager { /** * Handles the legacy migration dialog when user clicks on project configuration */ - private async handleLegacyMigrationDialog(legacyFilePath: Uri): Promise { + private async handleLegacyMigrationDialog( + legacyFilePath: Uri + ): Promise { try { const legacyContent = readFileSync(legacyFilePath.fsPath); const legacyData = JSON.parse(legacyContent); const legacyConfigNames = Object.keys(legacyData); - + const message = l10n.t( "Found {0} legacy project configuration(s): {1}. Would you like to migrate them to the new CMakePresets.json format?", legacyConfigNames.length, legacyConfigNames.join(", ") ); - + const migrateOption = l10n.t("Migrate Now"); const cancelOption = l10n.t("Cancel"); - + const choice = await window.showInformationMessage( message, { modal: true }, migrateOption, cancelOption ); - + if (choice === migrateOption) { await this.performMigration(legacyFilePath); } @@ -545,7 +617,10 @@ export class ProjectConfigurationManager { "handleLegacyMigrationDialog" ); window.showErrorMessage( - l10n.t("Failed to process legacy configuration file: {0}", error.message) + l10n.t( + "Failed to process legacy configuration file: {0}", + error.message + ) ); } } @@ -556,12 +631,14 @@ export class ProjectConfigurationManager { private async performMigration(legacyFilePath: Uri): Promise { try { await promptLegacyMigration(this.workspaceUri, legacyFilePath); - + // After migration, reinitialize to show the new configurations await this.initialize(); - + window.showInformationMessage( - l10n.t("Project configurations successfully migrated to CMakePresets.json format!") + l10n.t( + "Project configurations successfully migrated to CMakePresets.json format!" + ) ); } catch (error) { Logger.errorNotify( @@ -581,12 +658,14 @@ export class ProjectConfigurationManager { private async performDirectMigration(legacyFilePath: Uri): Promise { try { await migrateLegacyConfiguration(this.workspaceUri, legacyFilePath); - + // After migration, reinitialize to show the new configurations await this.initialize(); - + window.showInformationMessage( - l10n.t("Project configurations successfully migrated to CMakePresets.json format!") + l10n.t( + "Project configurations successfully migrated to CMakePresets.json format!" + ) ); } catch (error) { Logger.errorNotify( @@ -625,9 +704,10 @@ export class ProjectConfigurationManager { } /** - * Dispose of the file system watcher + * Dispose of the file system watchers */ public dispose(): void { - this.configWatcher.dispose(); + this.cmakePresetsWatcher.dispose(); + this.cmakeUserPresetsWatcher.dispose(); } } diff --git a/src/project-conf/index.ts b/src/project-conf/index.ts index 70ef4208e..bd01f9847 100644 --- a/src/project-conf/index.ts +++ b/src/project-conf/index.ts @@ -20,7 +20,14 @@ import * as path from "path"; import { ExtensionContext, Uri, window, l10n } from "vscode"; import { ESP } from "../config"; import { pathExists, readJson, writeJson } from "fs-extra"; -import { ProjectConfElement, CMakePresets, ConfigurePreset, BuildPreset, ESPIDFSettings, ESPIDFVendorSettings } from "./projectConfiguration"; +import { + ProjectConfElement, + CMakePresets, + ConfigurePreset, + BuildPreset, + ESPIDFSettings, + ESPIDFVendorSettings, +} from "./projectConfiguration"; import { Logger } from "../logger/logger"; import { resolveVariables } from "../idfConfiguration"; @@ -76,7 +83,7 @@ export async function updateCurrentProfileIdfTarget( ); return; } - + // Update IDF_TARGET in cacheVariables for ConfigurePreset if (!projectConfJson[selectedConfig].cacheVariables) { projectConfJson[selectedConfig].cacheVariables = {}; @@ -100,7 +107,9 @@ export async function saveProjectConfFile( ); // Use ConfigurePreset objects directly - const configurePresets: ConfigurePreset[] = Object.values(projectConfElements); + const configurePresets: ConfigurePreset[] = Object.values( + projectConfElements + ); const cmakePresets: CMakePresets = { version: 1, @@ -124,7 +133,9 @@ export async function saveProjectConfFileLegacy( ); // Convert to CMakePresets format - const configurePresets: ConfigurePreset[] = Object.keys(projectConfElements).map(name => + const configurePresets: ConfigurePreset[] = Object.keys( + projectConfElements + ).map((name) => convertProjectConfElementToConfigurePreset(name, projectConfElements[name]) ); @@ -248,7 +259,10 @@ function substituteVariablesInString( configVarName = configVar.substring(0, delimiterIndex); prefix = configVar.substring(delimiterIndex + 1).trim(); } - const configVarValue = parameterToSameProjectConfigMap(configVarName, config); + const configVarValue = parameterToSameProjectConfigMap( + configVarName, + config + ); if (!configVarValue) { return match; @@ -352,8 +366,8 @@ function resolveConfigPaths( // --- Main Function --- /** - * Reads the CMakePresets.json file, performs variable substitution - * on relevant fields, resolves paths, and returns the structured configuration. + * Reads both CMakePresets.json and CMakeUserPresets.json files, performs variable substitution + * on relevant fields, resolves paths, and returns the merged structured configuration. * @param workspaceFolder The Uri of the current workspace folder. * @param resolvePaths Whether to resolve paths to absolute paths (true for building, false for display) * @returns An object mapping configuration names to their processed ConfigurePreset. @@ -362,44 +376,274 @@ export async function getProjectConfigurationElements( workspaceFolder: Uri, resolvePaths: boolean = false ): Promise<{ [key: string]: ConfigurePreset }> { - const projectConfFilePath = Uri.joinPath( + const allRawPresets: { [key: string]: ConfigurePreset } = {}; + + // Read CMakePresets.json + const cmakePresetsFilePath = Uri.joinPath( workspaceFolder, ESP.ProjectConfiguration.PROJECT_CONFIGURATION_FILENAME ); - const doesPathExists = await pathExists(projectConfFilePath.fsPath); - if (!doesPathExists) { - // Check if legacy file exists and prompt for migration + // Read CMakeUserPresets.json + const cmakeUserPresetsFilePath = Uri.joinPath( + workspaceFolder, + ESP.ProjectConfiguration.USER_CONFIGURATION_FILENAME + ); + + const cmakePresetsExists = await pathExists(cmakePresetsFilePath.fsPath); + const cmakeUserPresetsExists = await pathExists( + cmakeUserPresetsFilePath.fsPath + ); + + // If neither file exists, check for legacy file + if (!cmakePresetsExists && !cmakeUserPresetsExists) { await checkAndPromptLegacyMigration(workspaceFolder); return {}; } - let projectConfJson; - try { - projectConfJson = await readJson(projectConfFilePath.fsPath); - if (typeof projectConfJson !== "object" || projectConfJson === null) { - throw new Error("Configuration file content is not a valid JSON object."); + // First pass: Load all raw presets from both files without processing inheritance + if (cmakePresetsExists) { + try { + const cmakePresetsJson = await readJson(cmakePresetsFilePath.fsPath); + if (typeof cmakePresetsJson === "object" && cmakePresetsJson !== null) { + const presets = await loadRawConfigurationFile( + cmakePresetsJson, + "CMakePresets.json" + ); + Object.assign(allRawPresets, presets); + } + } catch (error) { + Logger.errorNotify( + `Failed to read or parse ${ESP.ProjectConfiguration.PROJECT_CONFIGURATION_FILENAME}`, + error, + "getProjectConfigurationElements" + ); + window.showErrorMessage( + `Error reading or parsing CMakePresets.json file (${cmakePresetsFilePath.fsPath}): ${error.message}` + ); } - } catch (error) { - Logger.errorNotify( - `Failed to read or parse ${ESP.ProjectConfiguration.PROJECT_CONFIGURATION_FILENAME}`, - error, - "getProjectConfigurationElements" + } + + if (cmakeUserPresetsExists) { + try { + const cmakeUserPresetsJson = await readJson( + cmakeUserPresetsFilePath.fsPath + ); + if ( + typeof cmakeUserPresetsJson === "object" && + cmakeUserPresetsJson !== null + ) { + const presets = await loadRawConfigurationFile( + cmakeUserPresetsJson, + "CMakeUserPresets.json" + ); + // User presets override project presets with the same name + Object.assign(allRawPresets, presets); + } + } catch (error) { + Logger.errorNotify( + `Failed to read or parse ${ESP.ProjectConfiguration.USER_CONFIGURATION_FILENAME}`, + error, + "getProjectConfigurationElements" + ); + window.showErrorMessage( + `Error reading or parsing CMakeUserPresets.json file (${cmakeUserPresetsFilePath.fsPath}): ${error.message}` + ); + } + } + + // Second pass: Resolve inheritance and process variables + const processedPresets: { [key: string]: ConfigurePreset } = {}; + for (const [name, preset] of Object.entries(allRawPresets)) { + try { + const resolvedPreset = await resolvePresetInheritance( + preset, + allRawPresets + ); + const processedPreset = await processConfigurePresetVariables( + resolvedPreset, + workspaceFolder, + resolvePaths + ); + processedPresets[name] = processedPreset; + } catch (error) { + Logger.warn( + `Failed to process configure preset "${name}": ${error.message}`, + error + ); + } + } + + return processedPresets; +} + +/** + * Loads raw presets from a configuration file without processing inheritance or variables + * @param configJson The parsed JSON content of the configuration file + * @param fileName The name of the file being processed (for error messages) + * @returns An object mapping configuration names to their raw ConfigurePreset + */ +async function loadRawConfigurationFile( + configJson: any, + fileName: string +): Promise<{ [key: string]: ConfigurePreset }> { + const rawPresets: { [key: string]: ConfigurePreset } = {}; + + // Only support CMakePresets format + if (configJson.version !== undefined && configJson.configurePresets) { + const cmakePresets = configJson as CMakePresets; + + if ( + !cmakePresets.configurePresets || + cmakePresets.configurePresets.length === 0 + ) { + return {}; + } + + // Load each configure preset without processing + for (const preset of cmakePresets.configurePresets) { + rawPresets[preset.name] = { ...preset }; + } + } else { + // This might be a legacy file that wasn't migrated + Logger.warn( + `Invalid ${fileName} format detected. Expected 'version' and 'configurePresets' fields.`, + new Error("Invalid CMakePresets format") ); window.showErrorMessage( - `Error reading or parsing CMakePresets.json file (${projectConfFilePath.fsPath}): ${error.message}` + `Invalid ${fileName} format. Please ensure the file follows the CMakePresets specification.` + ); + } + + return rawPresets; +} + +/** + * Resolves inheritance for a preset by merging it with its parent presets + * @param preset The preset to resolve inheritance for + * @param allPresets All available presets (for inheritance lookup) + * @returns The preset with inheritance resolved + */ +async function resolvePresetInheritance( + preset: ConfigurePreset, + allPresets: { [key: string]: ConfigurePreset } +): Promise { + // If no inheritance, return as-is + if (!preset.inherits) { + return { ...preset }; + } + + // Handle both single string and array of strings for inherits + const parentNames = Array.isArray(preset.inherits) + ? preset.inherits + : [preset.inherits]; + + // Start with an empty base preset + let resolvedPreset: ConfigurePreset = { name: preset.name }; + + // Apply each parent preset in order + for (const parentName of parentNames) { + const parentPreset = allPresets[parentName]; + if (!parentPreset) { + Logger.warn( + `Preset "${preset.name}" inherits from "${parentName}" which was not found`, + new Error("Missing parent preset") + ); + continue; + } + + // Recursively resolve parent's inheritance first + const resolvedParent = await resolvePresetInheritance( + parentPreset, + allPresets ); - return {}; // Return empty if JSON is invalid or unreadable + + // Merge parent into resolved preset + resolvedPreset = mergePresets(resolvedPreset, resolvedParent); + } + + // Finally, merge the current preset (child overrides parent) + resolvedPreset = mergePresets(resolvedPreset, preset); + + // Remove the inherits property from the final result + delete resolvedPreset.inherits; + + return resolvedPreset; +} + +/** + * Merges two presets, with the child preset overriding the parent preset + * @param parent The parent preset + * @param child The child preset (takes precedence) + * @returns The merged preset + */ +function mergePresets( + parent: ConfigurePreset, + child: ConfigurePreset +): ConfigurePreset { + const merged: ConfigurePreset = { ...parent }; + + // Merge basic properties + if (child.name !== undefined) merged.name = child.name; + if (child.binaryDir !== undefined) merged.binaryDir = child.binaryDir; + if (child.inherits !== undefined) merged.inherits = child.inherits; + + // Merge cacheVariables (child overrides parent) + if (child.cacheVariables || parent.cacheVariables) { + merged.cacheVariables = { + ...(parent.cacheVariables || {}), + ...(child.cacheVariables || {}), + }; + } + + // Merge environment (child overrides parent) + if (child.environment || parent.environment) { + merged.environment = { + ...(parent.environment || {}), + ...(child.environment || {}), + }; + } + + // Merge vendor settings (child overrides parent) + if (child.vendor || parent.vendor) { + merged.vendor = { + "espressif/vscode-esp-idf": { + settings: [ + ...(parent.vendor?.["espressif/vscode-esp-idf"]?.settings || []), + ...(child.vendor?.["espressif/vscode-esp-idf"]?.settings || []), + ], + }, + }; } + return merged; +} + +/** + * Processes a single configuration file (CMakePresets.json or CMakeUserPresets.json) + * @param configJson The parsed JSON content of the configuration file + * @param workspaceFolder The workspace folder Uri + * @param resolvePaths Whether to resolve paths to absolute paths + * @param fileName The name of the file being processed (for error messages) + * @returns An object mapping configuration names to their processed ConfigurePreset + * @deprecated Use loadRawConfigurationFile and resolvePresetInheritance instead + */ +async function processConfigurationFile( + configJson: any, + workspaceFolder: Uri, + resolvePaths: boolean, + fileName: string +): Promise<{ [key: string]: ConfigurePreset }> { const projectConfElements: { [key: string]: ConfigurePreset } = {}; // Only support CMakePresets format - if (projectConfJson.version !== undefined && projectConfJson.configurePresets) { - // CMakePresets format - const cmakePresets = projectConfJson as CMakePresets; - - if (!cmakePresets.configurePresets || cmakePresets.configurePresets.length === 0) { + if (configJson.version !== undefined && configJson.configurePresets) { + const cmakePresets = configJson as CMakePresets; + + if ( + !cmakePresets.configurePresets || + cmakePresets.configurePresets.length === 0 + ) { return {}; } @@ -412,11 +656,11 @@ export async function getProjectConfigurationElements( workspaceFolder, resolvePaths ); - + projectConfElements[preset.name] = processedPreset; } catch (error) { Logger.warn( - `Failed to process configure preset "${preset.name}": ${error.message}`, + `Failed to process configure preset "${preset.name}" from ${fileName}: ${error.message}`, error ); } @@ -424,13 +668,12 @@ export async function getProjectConfigurationElements( } else { // This might be a legacy file that wasn't migrated Logger.warn( - `Invalid CMakePresets.json format detected. Expected 'version' and 'configurePresets' fields.`, + `Invalid ${fileName} format detected. Expected 'version' and 'configurePresets' fields.`, new Error("Invalid CMakePresets format") ); window.showErrorMessage( - `Invalid CMakePresets.json format. Please ensure the file follows the CMakePresets specification.` + `Invalid ${fileName} format. Please ensure the file follows the CMakePresets specification.` ); - return {}; } return projectConfElements; @@ -439,9 +682,14 @@ export async function getProjectConfigurationElements( /** * Checks for legacy project configuration file and prompts user for migration */ -async function checkAndPromptLegacyMigration(workspaceFolder: Uri): Promise { - const legacyFilePath = Uri.joinPath(workspaceFolder, "esp_idf_project_configuration.json"); - +async function checkAndPromptLegacyMigration( + workspaceFolder: Uri +): Promise { + const legacyFilePath = Uri.joinPath( + workspaceFolder, + "esp_idf_project_configuration.json" + ); + if (await pathExists(legacyFilePath.fsPath)) { await promptLegacyMigration(workspaceFolder, legacyFilePath); } @@ -450,23 +698,26 @@ async function checkAndPromptLegacyMigration(workspaceFolder: Uri): Promise { +export async function promptLegacyMigration( + workspaceFolder: Uri, + legacyFilePath: Uri +): Promise { const message = l10n.t( "A legacy project configuration file (esp_idf_project_configuration.json) was found. " + - "Would you like to migrate it to the new CMakePresets.json format? " + - "Your original file will remain unchanged." + "Would you like to migrate it to the new CMakePresets.json format? " + + "Your original file will remain unchanged." ); - + const migrateOption = l10n.t("Migrate"); const cancelOption = l10n.t("Cancel"); - + const choice = await window.showInformationMessage( message, { modal: true }, migrateOption, cancelOption ); - + if (choice === migrateOption) { await migrateLegacyConfiguration(workspaceFolder, legacyFilePath); } @@ -475,13 +726,16 @@ export async function promptLegacyMigration(workspaceFolder: Uri, legacyFilePath /** * Migrates legacy configuration to CMakePresets format */ -export async function migrateLegacyConfiguration(workspaceFolder: Uri, legacyFilePath: Uri): Promise { +export async function migrateLegacyConfiguration( + workspaceFolder: Uri, + legacyFilePath: Uri +): Promise { // Read legacy configuration const legacyConfig = await readJson(legacyFilePath.fsPath); - + // Convert to new format const projectConfElements: { [key: string]: ProjectConfElement } = {}; - + // Process legacy configurations for (const [confName, rawConfig] of Object.entries(legacyConfig)) { if (typeof rawConfig === "object" && rawConfig !== null) { @@ -500,14 +754,16 @@ export async function migrateLegacyConfiguration(workspaceFolder: Uri, legacyFil } } } - + // Save in new format using legacy compatibility function await saveProjectConfFileLegacy(workspaceFolder, projectConfElements); - - Logger.info(`Successfully migrated ${Object.keys(projectConfElements).length} configurations to CMakePresets.json`); -} - + Logger.info( + `Successfully migrated ${ + Object.keys(projectConfElements).length + } configurations to CMakePresets.json` + ); +} /** * Processes legacy project configuration format @@ -661,10 +917,38 @@ async function processConfigurePresetVariables( ): Promise { const processedPreset: ConfigurePreset = { ...preset, - binaryDir: preset.binaryDir ? await processConfigurePresetPath(preset.binaryDir, workspaceFolder, preset, resolvePaths) : undefined, - cacheVariables: preset.cacheVariables ? await processConfigurePresetCacheVariables(preset.cacheVariables, workspaceFolder, preset, resolvePaths) : undefined, - environment: preset.environment ? await processConfigurePresetEnvironment(preset.environment, workspaceFolder, preset, resolvePaths) : undefined, - vendor: preset.vendor ? await processConfigurePresetVendor(preset.vendor, workspaceFolder, preset, resolvePaths) : undefined, + binaryDir: preset.binaryDir + ? await processConfigurePresetPath( + preset.binaryDir, + workspaceFolder, + preset, + resolvePaths + ) + : undefined, + cacheVariables: preset.cacheVariables + ? await processConfigurePresetCacheVariables( + preset.cacheVariables, + workspaceFolder, + preset, + resolvePaths + ) + : undefined, + environment: preset.environment + ? await processConfigurePresetEnvironment( + preset.environment, + workspaceFolder, + preset, + resolvePaths + ) + : undefined, + vendor: preset.vendor + ? await processConfigurePresetVendor( + preset.vendor, + workspaceFolder, + preset, + resolvePaths + ) + : undefined, }; return processedPreset; @@ -680,15 +964,19 @@ async function processConfigurePresetPath( resolvePaths: boolean ): Promise { // Apply variable substitution - let processedPath = substituteVariablesInConfigurePreset(pathValue, workspaceFolder, preset); - + let processedPath = substituteVariablesInConfigurePreset( + pathValue, + workspaceFolder, + preset + ); + if (resolvePaths && processedPath) { // Resolve relative paths to absolute paths if (!path.isAbsolute(processedPath)) { processedPath = path.join(workspaceFolder.fsPath, processedPath); } } - + return processedPath || pathValue; } @@ -702,23 +990,30 @@ async function processConfigurePresetCacheVariables( resolvePaths: boolean ): Promise<{ [key: string]: any }> { const processedCacheVariables: { [key: string]: any } = {}; - + for (const [key, value] of Object.entries(cacheVariables)) { if (typeof value === "string") { - processedCacheVariables[key] = substituteVariablesInConfigurePreset(value, workspaceFolder, preset); - + processedCacheVariables[key] = substituteVariablesInConfigurePreset( + value, + workspaceFolder, + preset + ); + // Special handling for path-related cache variables if (resolvePaths && (key === "SDKCONFIG" || key.includes("PATH"))) { const processedValue = processedCacheVariables[key]; if (processedValue && !path.isAbsolute(processedValue)) { - processedCacheVariables[key] = path.join(workspaceFolder.fsPath, processedValue); + processedCacheVariables[key] = path.join( + workspaceFolder.fsPath, + processedValue + ); } } } else { processedCacheVariables[key] = value; } } - + return processedCacheVariables; } @@ -732,11 +1027,13 @@ async function processConfigurePresetEnvironment( resolvePaths: boolean ): Promise<{ [key: string]: string }> { const processedEnvironment: { [key: string]: string } = {}; - + for (const [key, value] of Object.entries(environment)) { - processedEnvironment[key] = substituteVariablesInConfigurePreset(value, workspaceFolder, preset) || value; + processedEnvironment[key] = + substituteVariablesInConfigurePreset(value, workspaceFolder, preset) || + value; } - + return processedEnvironment; } @@ -751,33 +1048,47 @@ async function processConfigurePresetVendor( ): Promise { const processedVendor: ESPIDFVendorSettings = { "espressif/vscode-esp-idf": { - settings: [] - } + settings: [], + }, }; - + const espIdfSettings = vendor["espressif/vscode-esp-idf"]?.settings || []; - + for (const setting of espIdfSettings) { const processedSetting: ESPIDFSettings = { ...setting }; - + // Process string values in settings if (typeof setting.value === "string") { - processedSetting.value = substituteVariablesInConfigurePreset(setting.value, workspaceFolder, preset) || setting.value; + processedSetting.value = + substituteVariablesInConfigurePreset( + setting.value, + workspaceFolder, + preset + ) || setting.value; } else if (Array.isArray(setting.value)) { // Process arrays of strings - processedSetting.value = setting.value.map(item => - typeof item === "string" - ? substituteVariablesInConfigurePreset(item, workspaceFolder, preset) || item + processedSetting.value = setting.value.map((item) => + typeof item === "string" + ? substituteVariablesInConfigurePreset( + item, + workspaceFolder, + preset + ) || item : item ); } else if (typeof setting.value === "object" && setting.value !== null) { // Process objects (like openOCD settings) - processedSetting.value = await processConfigurePresetSettingObject(setting.value, workspaceFolder, preset, resolvePaths); + processedSetting.value = await processConfigurePresetSettingObject( + setting.value, + workspaceFolder, + preset, + resolvePaths + ); } - + processedVendor["espressif/vscode-esp-idf"].settings.push(processedSetting); } - + return processedVendor; } @@ -791,21 +1102,27 @@ async function processConfigurePresetSettingObject( resolvePaths: boolean ): Promise { const processedObj: any = {}; - + for (const [key, value] of Object.entries(obj)) { if (typeof value === "string") { - processedObj[key] = substituteVariablesInConfigurePreset(value, workspaceFolder, preset) || value; + processedObj[key] = + substituteVariablesInConfigurePreset(value, workspaceFolder, preset) || + value; } else if (Array.isArray(value)) { - processedObj[key] = value.map(item => - typeof item === "string" - ? substituteVariablesInConfigurePreset(item, workspaceFolder, preset) || item + processedObj[key] = value.map((item) => + typeof item === "string" + ? substituteVariablesInConfigurePreset( + item, + workspaceFolder, + preset + ) || item : item ); } else { processedObj[key] = value; } } - + return processedObj; } @@ -865,8 +1182,11 @@ function substituteVariablesInConfigurePreset( configVarName = configVar.substring(0, delimiterIndex); prefix = configVar.substring(delimiterIndex + 1).trim(); } - - const configVarValue = getConfigurePresetParameterValue(configVarName, preset); + + const configVarValue = getConfigurePresetParameterValue( + configVarName, + preset + ); if (!configVarValue) { return match; @@ -910,7 +1230,10 @@ function substituteVariablesInConfigurePreset( /** * Gets parameter value from ConfigurePreset for variable substitution */ -function getConfigurePresetParameterValue(param: string, preset: ConfigurePreset): any { +function getConfigurePresetParameterValue( + param: string, + preset: ConfigurePreset +): any { switch (param) { case "idf.cmakeCompilerArgs": return getESPIDFSettingValue(preset, "compileArgs") || ""; @@ -962,23 +1285,36 @@ function getConfigurePresetParameterValue(param: string, preset: ConfigurePreset /** * Helper function to get ESP-IDF setting value from ConfigurePreset */ -function getESPIDFSettingValue(preset: ConfigurePreset, settingType: string): any { - const espIdfSettings = preset.vendor?.["espressif/vscode-esp-idf"]?.settings || []; - const setting = espIdfSettings.find(s => s.type === settingType); +function getESPIDFSettingValue( + preset: ConfigurePreset, + settingType: string +): any { + const espIdfSettings = + preset.vendor?.["espressif/vscode-esp-idf"]?.settings || []; + const setting = espIdfSettings.find((s) => s.type === settingType); return setting ? setting.value : undefined; } /** * Converts ConfigurePreset to ProjectConfElement for store compatibility */ -export function configurePresetToProjectConfElement(preset: ConfigurePreset): ProjectConfElement { - return convertConfigurePresetToProjectConfElement(preset, Uri.file(""), false); +export function configurePresetToProjectConfElement( + preset: ConfigurePreset +): ProjectConfElement { + return convertConfigurePresetToProjectConfElement( + preset, + Uri.file(""), + false + ); } /** * Converts ProjectConfElement to ConfigurePreset for store compatibility */ -export function projectConfElementToConfigurePreset(name: string, element: ProjectConfElement): ConfigurePreset { +export function projectConfElementToConfigurePreset( + name: string, + element: ProjectConfElement +): ConfigurePreset { return convertProjectConfElementToConfigurePreset(name, element); } @@ -998,11 +1334,12 @@ function convertConfigurePresetToProjectConfElement( resolvePaths: boolean = false ): ProjectConfElement { // Extract ESP-IDF specific settings from vendor section - const espIdfSettings = preset.vendor?.["espressif/vscode-esp-idf"]?.settings || []; - + const espIdfSettings = + preset.vendor?.["espressif/vscode-esp-idf"]?.settings || []; + // Helper function to find setting by type const findSetting = (type: string): any => { - const setting = espIdfSettings.find(s => s.type === type); + const setting = espIdfSettings.find((s) => s.type === type); return setting ? setting.value : undefined; }; @@ -1011,18 +1348,32 @@ function convertConfigurePresetToProjectConfElement( const ninjaArgs = findSetting("ninjaArgs") || []; const flashBaudRate = findSetting("flashBaudRate") || ""; const monitorBaudRate = findSetting("monitorBaudRate") || ""; - const openOCDSettings = findSetting("openOCD") || { debugLevel: -1, configs: [], args: [] }; - const taskSettings = findSetting("tasks") || { preBuild: "", preFlash: "", postBuild: "", postFlash: "" }; + const openOCDSettings = findSetting("openOCD") || { + debugLevel: -1, + configs: [], + args: [], + }; + const taskSettings = findSetting("tasks") || { + preBuild: "", + preFlash: "", + postBuild: "", + postFlash: "", + }; // Process paths based on resolvePaths flag const binaryDir = preset.binaryDir || ""; - const buildDirectoryPath = resolvePaths && binaryDir - ? (path.isAbsolute(binaryDir) ? binaryDir : path.join(workspaceFolder.fsPath, binaryDir)) - : binaryDir; + const buildDirectoryPath = + resolvePaths && binaryDir + ? path.isAbsolute(binaryDir) + ? binaryDir + : path.join(workspaceFolder.fsPath, binaryDir) + : binaryDir; // Process SDKCONFIG_DEFAULTS - convert semicolon-separated string to array const sdkconfigDefaultsStr = preset.cacheVariables?.SDKCONFIG_DEFAULTS || ""; - const sdkconfigDefaults = sdkconfigDefaultsStr ? sdkconfigDefaultsStr.split(";") : []; + const sdkconfigDefaults = sdkconfigDefaultsStr + ? sdkconfigDefaultsStr.split(";") + : []; return { build: { @@ -1038,7 +1389,9 @@ function convertConfigurePresetToProjectConfElement( monitorBaudRate, openOCD: { debugLevel: openOCDSettings.debugLevel || -1, - configs: Array.isArray(openOCDSettings.configs) ? openOCDSettings.configs : [], + configs: Array.isArray(openOCDSettings.configs) + ? openOCDSettings.configs + : [], args: Array.isArray(openOCDSettings.args) ? openOCDSettings.args : [], }, tasks: { @@ -1058,9 +1411,10 @@ function convertProjectConfElementToConfigurePreset( element: ProjectConfElement ): ConfigurePreset { // Convert SDKCONFIG_DEFAULTS array to semicolon-separated string - const sdkconfigDefaults = element.build.sdkconfigDefaults.length > 0 - ? element.build.sdkconfigDefaults.join(";") - : undefined; + const sdkconfigDefaults = + element.build.sdkconfigDefaults.length > 0 + ? element.build.sdkconfigDefaults.join(";") + : undefined; const settings: ESPIDFSettings[] = [ { type: "compileArgs", value: element.build.compileArgs }, @@ -1077,7 +1431,9 @@ function convertProjectConfElementToConfigurePreset( cacheVariables: { ...(element.idfTarget && { IDF_TARGET: element.idfTarget }), ...(sdkconfigDefaults && { SDKCONFIG_DEFAULTS: sdkconfigDefaults }), - ...(element.build.sdkconfigFilePath && { SDKCONFIG: element.build.sdkconfigFilePath }), + ...(element.build.sdkconfigFilePath && { + SDKCONFIG: element.build.sdkconfigFilePath, + }), }, environment: Object.keys(element.env).length > 0 ? element.env : undefined, vendor: { diff --git a/src/project-conf/projectConfiguration.ts b/src/project-conf/projectConfiguration.ts index cf541e845..486aa737a 100644 --- a/src/project-conf/projectConfiguration.ts +++ b/src/project-conf/projectConfiguration.ts @@ -50,7 +50,13 @@ export interface CMakeVersion { } export interface ESPIDFSettings { - type: "compileArgs" | "ninjaArgs" | "flashBaudRate" | "monitorBaudRate" | "openOCD" | "tasks"; + type: + | "compileArgs" + | "ninjaArgs" + | "flashBaudRate" + | "monitorBaudRate" + | "openOCD" + | "tasks"; value: any; } @@ -62,6 +68,7 @@ export interface ESPIDFVendorSettings { export interface ConfigurePreset { name: string; + inherits?: string | string[]; binaryDir?: string; cacheVariables?: { IDF_TARGET?: string; diff --git a/src/statusBar/index.ts b/src/statusBar/index.ts index 8ed1f5669..89dad62cf 100644 --- a/src/statusBar/index.ts +++ b/src/statusBar/index.ts @@ -69,11 +69,17 @@ export async function createCmdsStatusBarItems(workspaceFolder: Uri) { let projectConf = ESP.ProjectConfiguration.store.get( ESP.ProjectConfiguration.SELECTED_CONFIG ); - let projectConfPath = path.join( + let cmakePresetsPath = path.join( workspaceFolder.fsPath, ESP.ProjectConfiguration.PROJECT_CONFIGURATION_FILENAME ); - let projectConfExists = await pathExists(projectConfPath); + let cmakeUserPresetsPath = path.join( + workspaceFolder.fsPath, + ESP.ProjectConfiguration.USER_CONFIGURATION_FILENAME + ); + let cmakePresetsExists = await pathExists(cmakePresetsPath); + let cmakeUserPresetsExists = await pathExists(cmakeUserPresetsPath); + let anyConfigFileExists = cmakePresetsExists || cmakeUserPresetsExists; let currentIdfVersion = await getCurrentIdfSetup(workspaceFolder, false); @@ -129,8 +135,8 @@ export async function createCmdsStatusBarItems(workspaceFolder: Uri) { } } - // Only create the project configuration status bar item if the configuration file exists - if (projectConfExists) { + // Only create the project configuration status bar item if any configuration file exists + if (anyConfigFileExists) { if (!projectConf) { // No configuration selected but file exists with configurations let statusBarItemName = "No Configuration Selected"; @@ -158,7 +164,7 @@ export async function createCmdsStatusBarItems(workspaceFolder: Uri) { ); } } else if (statusBarItems["projectConf"]) { - // If the configuration file doesn't exist but the status bar item does, remove it + // If no configuration files exist but the status bar item does, remove it statusBarItems["projectConf"].dispose(); statusBarItems["projectConf"] = undefined; } From 6f94cf829c5ead47b60ae4340ca8791cd587df8e Mon Sep 17 00:00:00 2001 From: Radu Date: Thu, 25 Sep 2025 19:59:40 +0300 Subject: [PATCH 05/21] Disable CMake extension autoconfiguration - Disable prompts from CMake extension that might confuse the esp-idf users. - Disable autoconfiguration from CMake extension - Remove CMake status bar icons --- templates/.vscode/settings.json | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/templates/.vscode/settings.json b/templates/.vscode/settings.json index 0bb915ab8..b55d209b4 100644 --- a/templates/.vscode/settings.json +++ b/templates/.vscode/settings.json @@ -1,3 +1,21 @@ { - "C_Cpp.intelliSenseEngine": "default" + "C_Cpp.intelliSenseEngine": "default", + "cmake.configureOnOpen": false, + "cmake.configureOnEdit": false, + "cmake.automaticReconfigure": false, + "cmake.autoSelectActiveFolder": false, + "cmake.options.advanced": { + "build": { + "statusBarVisibility": "inherit", + "inheritDefault": "hidden" + }, + "launch": { + "statusBarVisibility": "inherit", + "inheritDefault": "hidden" + }, + "debug": { + "statusBarVisibility": "inherit", + "inheritDefault": "hidden" + } + } } From f027df11bfa73346b94de5c36f58a6d9b7e3a0c0 Mon Sep 17 00:00:00 2001 From: Radu Date: Thu, 25 Sep 2025 22:03:35 +0300 Subject: [PATCH 06/21] feat: Add save configuration option as default --- package.json | 6 ++++ package.nls.json | 3 +- src/extension.ts | 8 ++++- .../ProjectConfigurationManager.ts | 33 ++++++++++++++----- 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index beece7e5e..e02cab655 100644 --- a/package.json +++ b/package.json @@ -1310,6 +1310,12 @@ "default": 60, "scope": "resource", "description": "%param.serialPortDetectionTimeout%" + }, + "idf.saveLastProjectConfiguration": { + "type": "boolean", + "default": true, + "scope": "resource", + "description": "%param.saveLastProjectConfiguration%" } } } diff --git a/package.nls.json b/package.nls.json index 9cb08f006..f004c3a49 100644 --- a/package.nls.json +++ b/package.nls.json @@ -212,5 +212,6 @@ "command.errorHints.clearAll.title": "Clear All Error Hints", "command.errorHints.clearBuild.title": "Clear Build Error Hints", "command.errorHints.clearOpenOCD.title": "Clear OpenOCD Error Hints", - "Launch Debug": "Launch Debug" + "Launch Debug": "Launch Debug", + "param.saveLastProjectConfiguration": "Save and restore the last selected project configuration when reopening a workspace. When enabled, the extension will restore the last used configuration if it exists, otherwise no configuration will be selected. When disabled, no configuration will be selected by default." } diff --git a/src/extension.ts b/src/extension.ts index efb99e5cb..487095bf1 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -282,7 +282,13 @@ export async function activate(context: vscode.ExtensionContext) { Logger.init(context); ESP.GlobalConfiguration.store = ExtensionConfigStore.init(context); ESP.ProjectConfiguration.store = ProjectConfigStore.init(context); - clearSelectedProjectConfiguration(); + + // Only clear selected project configuration if the setting is disabled + const saveLastProjectConfiguration = idfConf.readParameter("idf.saveLastProjectConfiguration"); + if (saveLastProjectConfiguration === false) { + clearSelectedProjectConfiguration(); + } + Telemetry.init(idfConf.readParameter("idf.telemetry") || false); utils.setExtensionContext(context); ChangelogViewer.showChangeLogAndUpdateVersion(context); diff --git a/src/project-conf/ProjectConfigurationManager.ts b/src/project-conf/ProjectConfigurationManager.ts index 312fbaa94..32a57d845 100644 --- a/src/project-conf/ProjectConfigurationManager.ts +++ b/src/project-conf/ProjectConfigurationManager.ts @@ -28,6 +28,7 @@ import { } from "./index"; import { pathExists } from "fs-extra"; import { configureClangSettings } from "../clang"; +import * as idfConf from "../idfConfiguration"; export function clearSelectedProjectConfiguration(): void { if (ESP.ProjectConfiguration.store) { @@ -137,14 +138,30 @@ export class ProjectConfigurationManager { if (cmakePresetsExists) fileInfo.push("CMakePresets.json"); if (cmakeUserPresetsExists) fileInfo.push("CMakeUserPresets.json"); - window.showInformationMessage( - `Loaded ${ - this.configVersions.length - } project configuration(s) from ${fileInfo.join( - " and " - )}: ${this.configVersions.join(", ")}` - ); - this.setNoConfigurationSelectedStatus(); + // Check if we should show no configuration selected status + const saveLastProjectConfiguration = idfConf.readParameter("idf.saveLastProjectConfiguration", this.workspaceUri); + + if (saveLastProjectConfiguration !== false) { + // When setting is enabled, show no configuration selected status + window.showInformationMessage( + `Loaded ${ + this.configVersions.length + } project configuration(s) from ${fileInfo.join( + " and " + )}: ${this.configVersions.join(", ")}. No configuration selected.` + ); + this.setNoConfigurationSelectedStatus(); + } else { + // Show the current behavior when auto-selection is disabled + window.showInformationMessage( + `Loaded ${ + this.configVersions.length + } project configuration(s) from ${fileInfo.join( + " and " + )}: ${this.configVersions.join(", ")}` + ); + this.setNoConfigurationSelectedStatus(); + } } else { // No configurations found Logger.info( From 19fae5a1f6ac64d3c55efacb7e69505c527c86f2 Mon Sep 17 00:00:00 2001 From: Radu Date: Wed, 1 Oct 2025 16:38:24 +0300 Subject: [PATCH 07/21] fix: updating configurations --- src/espIdf/openOcd/boardConfiguration.ts | 5 + src/espIdf/setTarget/index.ts | 19 +- src/extension.ts | 1 + src/project-conf/index.ts | 272 ++++++++++++++++++++++- 4 files changed, 285 insertions(+), 12 deletions(-) diff --git a/src/espIdf/openOcd/boardConfiguration.ts b/src/espIdf/openOcd/boardConfiguration.ts index f00eaff33..73938a55f 100644 --- a/src/espIdf/openOcd/boardConfiguration.ts +++ b/src/espIdf/openOcd/boardConfiguration.ts @@ -23,6 +23,7 @@ import { commands, ConfigurationTarget, l10n, Uri, window } from "vscode"; import { defaultBoards } from "./defaultBoards"; import { IdfToolsManager } from "../../idfToolsManager"; import { getIdfTargetFromSdkconfig } from "../../workspaceConfig"; +import { updateCurrentProfileOpenOcdConfigs } from "../../project-conf"; export interface IdfBoard { name: string; @@ -199,6 +200,10 @@ export async function selectOpenOcdConfigFiles( ConfigurationTarget.WorkspaceFolder, workspaceFolder ); + + // Update project configuration with OpenOCD configs if a configuration is selected + await updateCurrentProfileOpenOcdConfigs(selectedBoard.target.configFiles, workspaceFolder); + Logger.infoNotify( l10n.t(`OpenOCD Board configuration files set to {boards}.`, { boards: selectedBoard.target.configFiles.join(","), diff --git a/src/espIdf/setTarget/index.ts b/src/espIdf/setTarget/index.ts index b5e7fdd8c..7296d538f 100644 --- a/src/espIdf/setTarget/index.ts +++ b/src/espIdf/setTarget/index.ts @@ -36,7 +36,11 @@ import { OutputChannel } from "../../logger/outputChannel"; import { selectOpenOcdConfigFiles } from "../openOcd/boardConfiguration"; import { getTargetsFromEspIdf, IdfTarget } from "./getTargets"; import { setTargetInIDF } from "./setTargetInIdf"; -import { updateCurrentProfileIdfTarget } from "../../project-conf"; +import { + updateCurrentProfileIdfTarget, + updateCurrentProfileOpenOcdConfigs, + updateCurrentProfileCustomExtraVars +} from "../../project-conf"; import { DevkitsCommand } from "./DevkitsCommand"; export let isSettingIDFTarget = false; @@ -165,6 +169,10 @@ export async function setIdfTarget( configurationTarget, workspaceFolder.uri ); + + // Update project configuration with OpenOCD configs if a configuration is selected + await updateCurrentProfileOpenOcdConfigs(configFiles, workspaceFolder.uri); + // Store USB location if available if (selectedTarget.boardInfo.location) { const customExtraVars = readParameter( @@ -182,6 +190,12 @@ export async function setIdfTarget( configurationTarget, workspaceFolder.uri ); + + // Update project configuration with custom extra vars if a configuration is selected + await updateCurrentProfileCustomExtraVars( + { "OPENOCD_USB_ADAPTER_LOCATION": location }, + workspaceFolder.uri + ); } } else { await selectOpenOcdConfigFiles( @@ -202,6 +216,9 @@ export async function setIdfTarget( configurationTarget, workspaceFolder.uri ); + + // Update project configuration with IDF_TARGET if a configuration is selected + // Note: IDF_TARGET goes in cacheVariables, not environment await updateCurrentProfileIdfTarget( selectedTarget.idfTarget.target, workspaceFolder.uri diff --git a/src/extension.ts b/src/extension.ts index 487095bf1..448cd65ad 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -3952,6 +3952,7 @@ export async function activate(context: vscode.ExtensionContext) { } }); }); + } function checkAndNotifyMissingCompileCommands() { diff --git a/src/project-conf/index.ts b/src/project-conf/index.ts index bd01f9847..700a9c351 100644 --- a/src/project-conf/index.ts +++ b/src/project-conf/index.ts @@ -59,11 +59,91 @@ export async function updateCurrentProfileIdfTarget( idfTarget: string, workspaceFolder: Uri ) { + await updateCurrentProjectConfiguration(workspaceFolder, (config) => { + // Update IDF_TARGET in cacheVariables for ConfigurePreset + if (!config.cacheVariables) { + config.cacheVariables = {}; + } + config.cacheVariables.IDF_TARGET = idfTarget; + return config; + }); +} + +/** + * Updates OpenOCD configuration for the currently selected project configuration + */ +export async function updateCurrentProfileOpenOcdConfigs( + configs: string[], + workspaceFolder: Uri +) { + await updateCurrentProjectConfiguration(workspaceFolder, (config) => { + // Update OpenOCD configs in vendor settings + if (!config.vendor) { + config.vendor = { "espressif/vscode-esp-idf": { settings: [] } }; + } + if (!config.vendor["espressif/vscode-esp-idf"]) { + config.vendor["espressif/vscode-esp-idf"] = { settings: [] }; + } + + // Remove existing openOCD setting + config.vendor["espressif/vscode-esp-idf"].settings = + config.vendor["espressif/vscode-esp-idf"].settings.filter( + (setting) => setting.type !== "openOCD" + ); + + // Add new openOCD setting + config.vendor["espressif/vscode-esp-idf"].settings.push({ + type: "openOCD", + value: { + debugLevel: 2, + configs: configs, + args: [] + } + }); + + return config; + }); +} + +/** + * Updates custom extra variables for the currently selected project configuration + * Note: IDF_TARGET is excluded as it should be in cacheVariables, not environment + */ +export async function updateCurrentProfileCustomExtraVars( + customVars: { [key: string]: string }, + workspaceFolder: Uri +) { + await updateCurrentProjectConfiguration(workspaceFolder, (config) => { + // Update custom extra variables in environment + if (!config.environment) { + config.environment = {}; + } + + // Filter out IDF_TARGET as it should be in cacheVariables, not environment + const filteredVars = { ...customVars }; + delete filteredVars.IDF_TARGET; + + // Merge the custom variables into the environment (excluding IDF_TARGET) + Object.assign(config.environment, filteredVars); + + return config; + }); +} + + +/** + * Generic function to update any configuration setting for the currently selected project configuration + */ +export async function updateCurrentProjectConfiguration( + workspaceFolder: Uri, + updateFunction: (config: ConfigurePreset) => ConfigurePreset +): Promise { const selectedConfig = ESP.ProjectConfiguration.store.get( ESP.ProjectConfiguration.SELECTED_CONFIG ); if (!selectedConfig) { + // No configuration selected - don't update any files return; } @@ -74,27 +154,197 @@ export async function updateCurrentProfileIdfTarget( if (!projectConfJson[selectedConfig]) { const err = new Error( - `Configuration preset "${selectedConfig}" not found in ${ESP.ProjectConfiguration.PROJECT_CONFIGURATION_FILENAME}. Please check your CMakePresets configurePresets section.` + `Configuration preset "${selectedConfig}" not found in project configuration files. Please check your CMakePresets configurePresets section.` ); Logger.errorNotify( err.message, err, - "updateCurrentProfileIdfTarget project-conf" + "updateCurrentProjectConfiguration project-conf" ); return; } - // Update IDF_TARGET in cacheVariables for ConfigurePreset - if (!projectConfJson[selectedConfig].cacheVariables) { - projectConfJson[selectedConfig].cacheVariables = {}; + // Apply the update function to the configuration + const updatedConfig = updateFunction(projectConfJson[selectedConfig]); + + // Update the store + ESP.ProjectConfiguration.store.set(selectedConfig, updatedConfig); + + // Save to the correct file based on where the configuration originated + await saveProjectConfigurationToCorrectFile(workspaceFolder, selectedConfig, updatedConfig); +} + +/** + * Saves a single configuration to the correct file based on its source + */ +export async function saveProjectConfigurationToCorrectFile( + workspaceFolder: Uri, + configName: string, + configPreset: ConfigurePreset +) { + // Determine which file the configuration should be saved to + const configSource = await determineConfigurationSource(workspaceFolder, configName); + + if (configSource === 'user') { + await saveConfigurationToUserPresets(workspaceFolder, configName, configPreset); + } else if (configSource === 'project') { + await saveConfigurationToProjectPresets(workspaceFolder, configName, configPreset); + } else { + // If source is unknown and we have a selected config, default to user presets + // This handles the case where a user modifies a configuration that doesn't exist yet + await saveConfigurationToUserPresets(workspaceFolder, configName, configPreset); + } +} + +/** + * Determines the source file for a configuration (project vs user presets) + */ +async function determineConfigurationSource( + workspaceFolder: Uri, + configName: string +): Promise<'project' | 'user' | 'unknown'> { + const cmakePresetsFilePath = Uri.joinPath( + workspaceFolder, + ESP.ProjectConfiguration.PROJECT_CONFIGURATION_FILENAME + ); + const cmakeUserPresetsFilePath = Uri.joinPath( + workspaceFolder, + ESP.ProjectConfiguration.USER_CONFIGURATION_FILENAME + ); + + // Check if config exists in CMakeUserPresets.json first (user presets take precedence) + if (await pathExists(cmakeUserPresetsFilePath.fsPath)) { + try { + const userPresetsJson = await readJson(cmakeUserPresetsFilePath.fsPath); + if (userPresetsJson?.configurePresets?.some((preset: any) => preset.name === configName)) { + return 'user'; + } + } catch (error) { + Logger.error(`Error reading user presets file: ${error.message}`, error, "determineConfigurationSource"); + } + } + + // Check if config exists in CMakePresets.json + if (await pathExists(cmakePresetsFilePath.fsPath)) { + try { + const projectPresetsJson = await readJson(cmakePresetsFilePath.fsPath); + if (projectPresetsJson?.configurePresets?.some((preset: any) => preset.name === configName)) { + return 'project'; + } + } catch (error) { + Logger.error(`Error reading project presets file: ${error.message}`, error, "determineConfigurationSource"); + } + } + + return 'unknown'; +} + +/** + * Saves a configuration to CMakeUserPresets.json + */ +async function saveConfigurationToUserPresets( + workspaceFolder: Uri, + configName: string, + configPreset: ConfigurePreset +) { + const cmakeUserPresetsFilePath = Uri.joinPath( + workspaceFolder, + ESP.ProjectConfiguration.USER_CONFIGURATION_FILENAME + ); + + let userPresets: CMakePresets; + + // Read existing user presets or create new structure + if (await pathExists(cmakeUserPresetsFilePath.fsPath)) { + try { + userPresets = await readJson(cmakeUserPresetsFilePath.fsPath); + } catch (error) { + Logger.error(`Error reading user presets file: ${error.message}`, error, "saveConfigurationToUserPresets"); + userPresets = { + version: 3, + configurePresets: [] + }; + } + } else { + userPresets = { + version: 3, + configurePresets: [] + }; + } + + // Ensure configurePresets array exists + if (!userPresets.configurePresets) { + userPresets.configurePresets = []; } - projectConfJson[selectedConfig].cacheVariables.IDF_TARGET = idfTarget; - ESP.ProjectConfiguration.store.set( - selectedConfig, - projectConfJson[selectedConfig] + // Update or add the configuration + const existingIndex = userPresets.configurePresets.findIndex( + (preset: ConfigurePreset) => preset.name === configName ); - await saveProjectConfFile(workspaceFolder, projectConfJson); + + if (existingIndex >= 0) { + userPresets.configurePresets[existingIndex] = configPreset; + } else { + userPresets.configurePresets.push(configPreset); + } + + await writeJson(cmakeUserPresetsFilePath.fsPath, userPresets, { + spaces: 2, + }); +} + +/** + * Saves a configuration to CMakePresets.json + */ +async function saveConfigurationToProjectPresets( + workspaceFolder: Uri, + configName: string, + configPreset: ConfigurePreset +) { + const cmakePresetsFilePath = Uri.joinPath( + workspaceFolder, + ESP.ProjectConfiguration.PROJECT_CONFIGURATION_FILENAME + ); + + let projectPresets: CMakePresets; + + // Read existing project presets or create new structure + if (await pathExists(cmakePresetsFilePath.fsPath)) { + try { + projectPresets = await readJson(cmakePresetsFilePath.fsPath); + } catch (error) { + Logger.error(`Error reading project presets file: ${error.message}`, error, "saveConfigurationToProjectPresets"); + projectPresets = { + version: 3, + configurePresets: [] + }; + } + } else { + projectPresets = { + version: 3, + configurePresets: [] + }; + } + + // Ensure configurePresets array exists + if (!projectPresets.configurePresets) { + projectPresets.configurePresets = []; + } + + // Update or add the configuration + const existingIndex = projectPresets.configurePresets.findIndex( + (preset: ConfigurePreset) => preset.name === configName + ); + + if (existingIndex >= 0) { + projectPresets.configurePresets[existingIndex] = configPreset; + } else { + projectPresets.configurePresets.push(configPreset); + } + + await writeJson(cmakePresetsFilePath.fsPath, projectPresets, { + spaces: 2, + }); } export async function saveProjectConfFile( @@ -112,7 +362,7 @@ export async function saveProjectConfFile( ); const cmakePresets: CMakePresets = { - version: 1, + version: 3, cmakeMinimumRequired: { major: 3, minor: 23, patch: 0 }, configurePresets, }; From ce608911b32713c3a2c11bc156be98d2edfa8478 Mon Sep 17 00:00:00 2001 From: Radu Date: Fri, 17 Oct 2025 10:30:42 +0300 Subject: [PATCH 08/21] fix: keep selected preset buildPath consistent after config updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - This keeps idfConfiguration.parameterToProjectConfigMap('idf.buildPath') returning the preset’s binaryDir (as buildDirectoryPath) instead of falling back to the default ${workspaceFolder}/build. - Fixes issue where Set Target invoked idf.py with the wrong -B dir after profile edits or when “Save last project configuration” is enabled. --- src/project-conf/index.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/project-conf/index.ts b/src/project-conf/index.ts index 700a9c351..fe13436c5 100644 --- a/src/project-conf/index.ts +++ b/src/project-conf/index.ts @@ -167,11 +167,27 @@ export async function updateCurrentProjectConfiguration( // Apply the update function to the configuration const updatedConfig = updateFunction(projectConfJson[selectedConfig]); - // Update the store - ESP.ProjectConfiguration.store.set(selectedConfig, updatedConfig); - // Save to the correct file based on where the configuration originated - await saveProjectConfigurationToCorrectFile(workspaceFolder, selectedConfig, updatedConfig); + await saveProjectConfigurationToCorrectFile( + workspaceFolder, + selectedConfig, + updatedConfig + ); + + // Keep in-memory store consistent with consumers expecting legacy ProjectConfElement + // Re-read processed presets (with resolved paths) and convert to legacy shape + try { + const resolvedConfigs = await getProjectConfigurationElements( + workspaceFolder, + true + ); + const resolvedPreset = resolvedConfigs[selectedConfig] || updatedConfig; + const legacyElement = configurePresetToProjectConfElement(resolvedPreset); + ESP.ProjectConfiguration.store.set(selectedConfig, legacyElement); + } catch (e) { + // Fallback: ensure we at least keep the updated preset in store + ESP.ProjectConfiguration.store.set(selectedConfig, updatedConfig); + } } /** From b7a6b02d3e324d483cc07c420c6fb6e48749e670 Mon Sep 17 00:00:00 2001 From: Radu Date: Fri, 17 Oct 2025 12:16:12 +0300 Subject: [PATCH 09/21] fix(openocd): coalesce/silence version checks; store OpenOCD output as text - Run openocd --version with silent output and coalesce concurrent calls to avoid duplicate banners during set-target/devkit detection/hints. - Store aggregated OpenOCD output as string (text-only) to avoid Buffer typing issues and unnecessary Buffer.concat allocations. --- src/espIdf/openOcd/openOcdManager.ts | 45 ++++++++++++++++++---------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/src/espIdf/openOcd/openOcdManager.ts b/src/espIdf/openOcd/openOcdManager.ts index f7bb271e3..a9570c8b7 100644 --- a/src/espIdf/openOcd/openOcdManager.ts +++ b/src/espIdf/openOcd/openOcdManager.ts @@ -49,10 +49,11 @@ export class OpenOCDManager extends EventEmitter { } private static instance: OpenOCDManager; private server: ChildProcess; - private chan: Buffer; + private chan: string; private statusBar: vscode.StatusBarItem; private workspace: vscode.Uri; private encounteredErrors: boolean = false; + private versionPromise: Promise | null = null; // coalesce concurrent lookups only private constructor() { super(); @@ -60,20 +61,32 @@ export class OpenOCDManager extends EventEmitter { } public async version(): Promise { - const modifiedEnv = await appendIdfAndToolsToPath(this.workspace); - if (!isBinInPath("openocd", modifiedEnv)) { - return ""; + // Coalesce concurrent calls; do not cache long-term to respect version changes + if (this.versionPromise) { + return this.versionPromise; } - const resp = await sspawn("openocd", ["--version"], { - cwd: this.workspace.fsPath, - env: modifiedEnv, - }); - const versionString = resp.toString(); - const match = versionString.match(/v\d+\.\d+\.\d+\-\S*/gi); - if (!match) { - return "failed+to+match+version"; + + this.versionPromise = (async () => { + const modifiedEnv = await appendIdfAndToolsToPath(this.workspace); + if (!isBinInPath("openocd", modifiedEnv)) { + return ""; + } + const resp = await sspawn("openocd", ["--version"], { + cwd: this.workspace.fsPath, + env: modifiedEnv, + silent: true, + appendMode: "append", + }); + const versionString = resp.toString(); + const match = versionString.match(/v\d+\.\d+\.\d+\-\S*/gi); + return match ? match[0].replace("-dirty", "") : "failed+to+match+version"; + })(); + + try { + return await this.versionPromise; + } finally { + this.versionPromise = null; } - return match[0].replace("-dirty", ""); } public statusBarItem(): vscode.StatusBarItem { @@ -263,7 +276,7 @@ export class OpenOCDManager extends EventEmitter { } this.stop(); }); - this.updateStatusText("❇️ OpenOCD Server (Running)"); + this.updateStatusText("❇️ OpenOCD Server (Running)"); OutputChannel.show(); } @@ -306,7 +319,7 @@ export class OpenOCDManager extends EventEmitter { if (PreCheck.isWorkspaceFolderOpen()) { this.workspace = vscode.workspace.workspaceFolders[0].uri; } - this.chan = Buffer.alloc(0); + this.chan = ""; OutputChannel.init(); if (vscode.env.uiKind !== vscode.UIKind.Web) { this.registerOpenOCDStatusBarItem(); @@ -314,6 +327,6 @@ export class OpenOCDManager extends EventEmitter { } private sendToOutputChannel(data: Buffer) { - this.chan = Buffer.concat([this.chan, data]); + this.chan = (this.chan || "") + data.toString(); } } From facebaf68e161a5350eff23f3b7dfda807f7d6e2 Mon Sep 17 00:00:00 2001 From: Radu Date: Tue, 21 Oct 2025 15:14:55 +0300 Subject: [PATCH 10/21] fix: race condifiton and revert chan back to buffer --- src/espIdf/openOcd/openOcdManager.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/espIdf/openOcd/openOcdManager.ts b/src/espIdf/openOcd/openOcdManager.ts index a9570c8b7..2afb4d628 100644 --- a/src/espIdf/openOcd/openOcdManager.ts +++ b/src/espIdf/openOcd/openOcdManager.ts @@ -49,7 +49,7 @@ export class OpenOCDManager extends EventEmitter { } private static instance: OpenOCDManager; private server: ChildProcess; - private chan: string; + private chan: Buffer; private statusBar: vscode.StatusBarItem; private workspace: vscode.Uri; private encounteredErrors: boolean = false; @@ -81,11 +81,13 @@ export class OpenOCDManager extends EventEmitter { const match = versionString.match(/v\d+\.\d+\.\d+\-\S*/gi); return match ? match[0].replace("-dirty", "") : "failed+to+match+version"; })(); - + const p = this.versionPromise; try { return await this.versionPromise; } finally { - this.versionPromise = null; + if (this.versionPromise === p) { + this.versionPromise = null; + } } } @@ -319,7 +321,7 @@ export class OpenOCDManager extends EventEmitter { if (PreCheck.isWorkspaceFolderOpen()) { this.workspace = vscode.workspace.workspaceFolders[0].uri; } - this.chan = ""; + this.chan = Buffer.alloc(0);; OutputChannel.init(); if (vscode.env.uiKind !== vscode.UIKind.Web) { this.registerOpenOCDStatusBarItem(); @@ -327,6 +329,6 @@ export class OpenOCDManager extends EventEmitter { } private sendToOutputChannel(data: Buffer) { - this.chan = (this.chan || "") + data.toString(); + this.chan = Buffer.concat([this.chan, data]); } } From f808c1c4760cff80341001c9441f561b4ac3e74f Mon Sep 17 00:00:00 2001 From: Radu Date: Tue, 21 Oct 2025 16:52:18 +0300 Subject: [PATCH 11/21] remove: direct build with idf.py --- package.json | 6 --- src/build/buildTask.ts | 101 ----------------------------------------- 2 files changed, 107 deletions(-) diff --git a/package.json b/package.json index e02cab655..2b74cb37a 100644 --- a/package.json +++ b/package.json @@ -762,12 +762,6 @@ "description": "%param.buildPath%", "scope": "resource" }, - "idf.useCMakePresets": { - "type": "boolean", - "default": false, - "description": "Use idf.py --preset build instead of direct CMake calls. Requires ESP-IDF with CMakePresets support.", - "scope": "resource" - }, "idf.buildPathWin": { "type": "string", "default": "${workspaceFolder}\\build", diff --git a/src/build/buildTask.ts b/src/build/buildTask.ts index 360d1df3a..541b0d153 100644 --- a/src/build/buildTask.ts +++ b/src/build/buildTask.ts @@ -79,21 +79,6 @@ export class BuildTask { } this.building(true); - // Check if CMakePresets build mode is enabled - const useCMakePresets = idfConf.readParameter( - "idf.useCMakePresets", - this.currentWorkspace - ) as boolean; - - if (useCMakePresets) { - return await this.buildWithPresets(buildType); - } - - // Continue with traditional CMake build - return await this.buildWithCMake(buildType); - } - - private async buildWithCMake(buildType?: ESP.BuildType) { await ensureDir(this.buildDirPath); const modifiedEnv = await appendIdfAndToolsToPath(this.currentWorkspace); const processOptions = { @@ -306,90 +291,4 @@ export class BuildTask { buildPresentationOptions ); } - - private async buildWithPresets(buildType?: ESP.BuildType) { - await ensureDir(this.buildDirPath); - const modifiedEnv = await appendIdfAndToolsToPath(this.currentWorkspace); - - // Get the selected project configuration preset name - const selectedConfig = ESP.ProjectConfiguration.store.get( - ESP.ProjectConfiguration.SELECTED_CONFIG - ); - - if (!selectedConfig) { - throw new Error( - "No project configuration selected. Please select a CMakePresets configuration first." - ); - } - - const currentWorkspaceFolder = vscode.workspace.workspaceFolders.find( - (w) => w.uri === this.currentWorkspace - ); - - const notificationMode = idfConf.readParameter( - "idf.notificationMode", - this.currentWorkspace - ) as string; - const showTaskOutput = - notificationMode === idfConf.NotificationMode.All || - notificationMode === idfConf.NotificationMode.Output - ? vscode.TaskRevealKind.Always - : vscode.TaskRevealKind.Silent; - - // Build idf.py command with preset - const pythonBinPath = await getVirtualEnvPythonPath(this.currentWorkspace); - const idfPy = join(this.idfPathDir, "tools", "idf.py"); - - let args = [idfPy, "--preset", selectedConfig]; - - // Add build type specific arguments if needed - if (buildType) { - switch (buildType) { - case ESP.BuildType.Bootloader: - args.push("bootloader"); - break; - case ESP.BuildType.PartitionTable: - args.push("partition-table"); - break; - default: - args.push("build"); - break; - } - } else { - args.push("build"); - } - - Logger.info(`Building with CMakePresets using: ${pythonBinPath} ${args.join(" ")}`); - - const processOptions = { - cwd: this.currentWorkspace.fsPath, - env: modifiedEnv, - }; - - const buildExecution = new vscode.ProcessExecution( - pythonBinPath, - args, - processOptions - ); - - const buildPresentationOptions = { - reveal: showTaskOutput, - showReuseMessage: false, - clear: false, - panel: vscode.TaskPanelKind.Shared, - } as vscode.TaskPresentationOptions; - - TaskManager.addTask( - { - type: "esp-idf", - command: `ESP-IDF Build (CMakePresets: ${selectedConfig})`, - taskId: "idf-build-presets-task", - }, - currentWorkspaceFolder || vscode.TaskScope.Workspace, - `ESP-IDF Build (CMakePresets: ${selectedConfig})`, - buildExecution, - ["espIdf"], - buildPresentationOptions - ); - } } From 3f2c12a4cfe0c038c655276332739ac134073861 Mon Sep 17 00:00:00 2001 From: Radu Date: Tue, 21 Oct 2025 17:20:24 +0300 Subject: [PATCH 12/21] fix: error messages --- .../ProjectConfigurationManager.ts | 51 ++++++++----------- src/project-conf/index.ts | 18 +------ 2 files changed, 22 insertions(+), 47 deletions(-) diff --git a/src/project-conf/ProjectConfigurationManager.ts b/src/project-conf/ProjectConfigurationManager.ts index 32a57d845..05db7bc9f 100644 --- a/src/project-conf/ProjectConfigurationManager.ts +++ b/src/project-conf/ProjectConfigurationManager.ts @@ -170,9 +170,6 @@ export class ProjectConfigurationManager { this.setNoConfigurationSelectedStatus(); } } catch (error) { - window.showErrorMessage( - `Error reading or parsing project configuration files: ${error.message}` - ); Logger.errorNotify( `Failed to parse project configuration files`, error, @@ -283,8 +280,10 @@ export class ProjectConfigurationManager { this.setNoConfigurationSelectedStatus(); } } catch (error) { - window.showErrorMessage( - `Error parsing configuration files: ${error.message}` + Logger.errorNotify( + `Error parsing configuration files: ${error.message}`, + error, + "ProjectConfigurationManager handleConfigFileChange" ); this.setNoConfigurationSelectedStatus(); } @@ -311,11 +310,9 @@ export class ProjectConfigurationManager { this.statusBarItems["projectConf"].dispose(); this.statusBarItems["projectConf"] = undefined; } - + // Optionally notify the user - window.showInformationMessage( - "Project configuration file has been deleted." - ); + Logger.infoNotify("Project configuration file has been deleted."); } private async handleConfigFileCreate(): Promise { @@ -360,8 +357,10 @@ export class ProjectConfigurationManager { this.setNoConfigurationSelectedStatus(); } } catch (error) { - window.showErrorMessage( - `Error parsing newly created configuration file: ${error.message}` + Logger.errorNotify( + `Error parsing newly created configuration file: ${error.message}`, + error, + "ProjectConfigurationManager handleConfigFileCreate" ); this.setNoConfigurationSelectedStatus(); } @@ -495,8 +494,10 @@ export class ProjectConfigurationManager { await this.updateConfiguration(option.target); } catch (error) { - window.showErrorMessage( - `Error selecting configuration: ${error.message}` + Logger.errorNotify( + `Error selecting configuration: ${error.message}`, + error, + "ProjectConfigurationManager selectProjectConfiguration" ); } } @@ -629,15 +630,9 @@ export class ProjectConfigurationManager { } } catch (error) { Logger.errorNotify( - "Failed to handle legacy migration", + l10n.t("Failed to handle legacy migration: {0}", error.message), error, - "handleLegacyMigrationDialog" - ); - window.showErrorMessage( - l10n.t( - "Failed to process legacy configuration file: {0}", - error.message - ) + "ProjectConfigurationManager handleLegacyMigrationDialog" ); } } @@ -659,12 +654,9 @@ export class ProjectConfigurationManager { ); } catch (error) { Logger.errorNotify( - "Failed to perform migration", + l10n.t("Failed to migrate project configuration: {0}", error.message), error, - "performMigration" - ); - window.showErrorMessage( - l10n.t("Failed to migrate project configuration: {0}", error.message) + "ProjectConfigurationManager performMigration" ); } } @@ -686,12 +678,9 @@ export class ProjectConfigurationManager { ); } catch (error) { Logger.errorNotify( - "Failed to perform direct migration", + l10n.t("Failed to migrate project configuration: {0}", error.message), error, - "performDirectMigration" - ); - window.showErrorMessage( - l10n.t("Failed to migrate project configuration: {0}", error.message) + "ProjectConfigurationManager performDirectMigration" ); } } diff --git a/src/project-conf/index.ts b/src/project-conf/index.ts index fe13436c5..c29e6536a 100644 --- a/src/project-conf/index.ts +++ b/src/project-conf/index.ts @@ -684,9 +684,6 @@ export async function getProjectConfigurationElements( error, "getProjectConfigurationElements" ); - window.showErrorMessage( - `Error reading or parsing CMakePresets.json file (${cmakePresetsFilePath.fsPath}): ${error.message}` - ); } } @@ -712,9 +709,6 @@ export async function getProjectConfigurationElements( error, "getProjectConfigurationElements" ); - window.showErrorMessage( - `Error reading or parsing CMakeUserPresets.json file (${cmakeUserPresetsFilePath.fsPath}): ${error.message}` - ); } } @@ -772,12 +766,8 @@ async function loadRawConfigurationFile( } } else { // This might be a legacy file that wasn't migrated - Logger.warn( - `Invalid ${fileName} format detected. Expected 'version' and 'configurePresets' fields.`, - new Error("Invalid CMakePresets format") - ); - window.showErrorMessage( - `Invalid ${fileName} format. Please ensure the file follows the CMakePresets specification.` + Logger.warnNotify( + `Invalid ${fileName} format detected. Please ensure the file follows the CMakePresets specification.` ); } @@ -934,10 +924,6 @@ async function processConfigurationFile( } else { // This might be a legacy file that wasn't migrated Logger.warn( - `Invalid ${fileName} format detected. Expected 'version' and 'configurePresets' fields.`, - new Error("Invalid CMakePresets format") - ); - window.showErrorMessage( `Invalid ${fileName} format. Please ensure the file follows the CMakePresets specification.` ); } From f4e694a49f4e285ea4849fec1c91a48e5b937280 Mon Sep 17 00:00:00 2001 From: Radu Date: Tue, 21 Oct 2025 17:31:01 +0300 Subject: [PATCH 13/21] refactor: hardcoded string value --- src/project-conf/index.ts | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/project-conf/index.ts b/src/project-conf/index.ts index c29e6536a..818fb39e8 100644 --- a/src/project-conf/index.ts +++ b/src/project-conf/index.ts @@ -31,6 +31,8 @@ import { import { Logger } from "../logger/logger"; import { resolveVariables } from "../idfConfiguration"; +const ESP_IDF_VENDOR_KEY = "espressif/vscode-esp-idf"; + export class ProjectConfigStore { private static self: ProjectConfigStore; private ctx: ExtensionContext; @@ -79,20 +81,20 @@ export async function updateCurrentProfileOpenOcdConfigs( await updateCurrentProjectConfiguration(workspaceFolder, (config) => { // Update OpenOCD configs in vendor settings if (!config.vendor) { - config.vendor = { "espressif/vscode-esp-idf": { settings: [] } }; + config.vendor = { [ESP_IDF_VENDOR_KEY]: { settings: [] } }; } - if (!config.vendor["espressif/vscode-esp-idf"]) { - config.vendor["espressif/vscode-esp-idf"] = { settings: [] }; + if (!config.vendor[ESP_IDF_VENDOR_KEY]) { + config.vendor[ESP_IDF_VENDOR_KEY] = { settings: [] }; } // Remove existing openOCD setting - config.vendor["espressif/vscode-esp-idf"].settings = - config.vendor["espressif/vscode-esp-idf"].settings.filter( + config.vendor[ESP_IDF_VENDOR_KEY].settings = + config.vendor[ESP_IDF_VENDOR_KEY].settings.filter( (setting) => setting.type !== "openOCD" ); // Add new openOCD setting - config.vendor["espressif/vscode-esp-idf"].settings.push({ + config.vendor[ESP_IDF_VENDOR_KEY].settings.push({ type: "openOCD", value: { debugLevel: 2, @@ -863,10 +865,10 @@ function mergePresets( // Merge vendor settings (child overrides parent) if (child.vendor || parent.vendor) { merged.vendor = { - "espressif/vscode-esp-idf": { + [ESP_IDF_VENDOR_KEY]: { settings: [ - ...(parent.vendor?.["espressif/vscode-esp-idf"]?.settings || []), - ...(child.vendor?.["espressif/vscode-esp-idf"]?.settings || []), + ...(parent.vendor?.[ESP_IDF_VENDOR_KEY]?.settings || []), + ...(child.vendor?.[ESP_IDF_VENDOR_KEY]?.settings || []), ], }, }; @@ -1299,12 +1301,12 @@ async function processConfigurePresetVendor( resolvePaths: boolean ): Promise { const processedVendor: ESPIDFVendorSettings = { - "espressif/vscode-esp-idf": { + [ESP_IDF_VENDOR_KEY]: { settings: [], }, }; - const espIdfSettings = vendor["espressif/vscode-esp-idf"]?.settings || []; + const espIdfSettings = vendor[ESP_IDF_VENDOR_KEY]?.settings || []; for (const setting of espIdfSettings) { const processedSetting: ESPIDFSettings = { ...setting }; @@ -1338,7 +1340,7 @@ async function processConfigurePresetVendor( ); } - processedVendor["espressif/vscode-esp-idf"].settings.push(processedSetting); + processedVendor[ESP_IDF_VENDOR_KEY].settings.push(processedSetting); } return processedVendor; @@ -1542,7 +1544,7 @@ function getESPIDFSettingValue( settingType: string ): any { const espIdfSettings = - preset.vendor?.["espressif/vscode-esp-idf"]?.settings || []; + preset.vendor?.[ESP_IDF_VENDOR_KEY]?.settings || []; const setting = espIdfSettings.find((s) => s.type === settingType); return setting ? setting.value : undefined; } @@ -1587,7 +1589,7 @@ function convertConfigurePresetToProjectConfElement( ): ProjectConfElement { // Extract ESP-IDF specific settings from vendor section const espIdfSettings = - preset.vendor?.["espressif/vscode-esp-idf"]?.settings || []; + preset.vendor?.[ESP_IDF_VENDOR_KEY]?.settings || []; // Helper function to find setting by type const findSetting = (type: string): any => { @@ -1689,7 +1691,7 @@ function convertProjectConfElementToConfigurePreset( }, environment: Object.keys(element.env).length > 0 ? element.env : undefined, vendor: { - "espressif/vscode-esp-idf": { + [ESP_IDF_VENDOR_KEY]: { settings, }, }, From a75dc814751deb9aa70d396e3394eb03c31e5173 Mon Sep 17 00:00:00 2001 From: Radu Date: Tue, 21 Oct 2025 18:22:42 +0300 Subject: [PATCH 14/21] Add and Update JS Docs comments --- src/project-conf/index.ts | 249 ++++++++++++++++++++++++++++++++------ 1 file changed, 215 insertions(+), 34 deletions(-) diff --git a/src/project-conf/index.ts b/src/project-conf/index.ts index 818fb39e8..621a48035 100644 --- a/src/project-conf/index.ts +++ b/src/project-conf/index.ts @@ -57,6 +57,12 @@ export class ProjectConfigStore { } } +/** + * Updates the IDF target (e.g., esp32, esp32s3) for the currently selected project configuration. + * The IDF_TARGET is stored in the cacheVariables section of the ConfigurePreset. + * @param idfTarget The target chip name (e.g., "esp32", "esp32s3", "esp32c3") + * @param workspaceFolder The workspace folder Uri where the configuration files are located + */ export async function updateCurrentProfileIdfTarget( idfTarget: string, workspaceFolder: Uri @@ -215,7 +221,13 @@ export async function saveProjectConfigurationToCorrectFile( } /** - * Determines the source file for a configuration (project vs user presets) + * Determines the source file for a configuration preset (CMakePresets.json vs CMakeUserPresets.json). + * User presets take precedence over project presets when both contain the same configuration name. + * This is important for determining where to save modifications to a configuration. + * + * @param workspaceFolder The workspace folder Uri where the configuration files are located + * @param configName The name of the configuration preset to locate + * @returns 'user' if found in CMakeUserPresets.json, 'project' if in CMakePresets.json, 'unknown' if not found */ async function determineConfigurationSource( workspaceFolder: Uri, @@ -258,7 +270,13 @@ async function determineConfigurationSource( } /** - * Saves a configuration to CMakeUserPresets.json + * Saves a configuration preset to the CMakeUserPresets.json file. + * If the file doesn't exist, it will be created. If a preset with the same name already exists, + * it will be updated. CMakeUserPresets.json is gitignored and used for user-specific overrides. + * + * @param workspaceFolder The workspace folder Uri where CMakeUserPresets.json is located + * @param configName The name of the configuration preset to save + * @param configPreset The ConfigurePreset object to save */ async function saveConfigurationToUserPresets( workspaceFolder: Uri, @@ -312,7 +330,13 @@ async function saveConfigurationToUserPresets( } /** - * Saves a configuration to CMakePresets.json + * Saves a configuration preset to the CMakePresets.json file. + * If the file doesn't exist, it will be created. If a preset with the same name already exists, + * it will be updated. CMakePresets.json is typically committed to version control. + * + * @param workspaceFolder The workspace folder Uri where CMakePresets.json is located + * @param configName The name of the configuration preset to save + * @param configPreset The ConfigurePreset object to save */ async function saveConfigurationToProjectPresets( workspaceFolder: Uri, @@ -365,6 +389,12 @@ async function saveConfigurationToProjectPresets( }); } +/** + * Saves project configuration elements to CMakePresets.json file. + * This function writes multiple ConfigurePreset objects to the project's CMakePresets.json file. + * @param workspaceFolder The workspace folder Uri where the configuration file will be saved + * @param projectConfElements An object mapping configuration names to their ConfigurePreset objects + */ export async function saveProjectConfFile( workspaceFolder: Uri, projectConfElements: { [key: string]: ConfigurePreset } @@ -390,7 +420,14 @@ export async function saveProjectConfFile( }); } -// Legacy compatibility function +/** + * Legacy compatibility function to save project configuration elements to CMakePresets.json. + * This function converts legacy ProjectConfElement objects to ConfigurePreset format and saves them. + * Used during migration from the old esp_idf_project_configuration.json format. + * @param workspaceFolder The workspace folder Uri where the configuration file will be saved + * @param projectConfElements An object mapping configuration names to their legacy ProjectConfElement objects + * @deprecated Use saveProjectConfFile instead for new code + */ export async function saveProjectConfFileLegacy( workspaceFolder: Uri, projectConfElements: { [key: string]: ProjectConfElement } @@ -418,6 +455,16 @@ export async function saveProjectConfFileLegacy( }); } +/** + * Maps legacy parameter names to their corresponding values in a ProjectConfElement (legacy format). + * This function is used for backward compatibility when processing legacy configuration files + * that use variable substitution with parameter names like ${config:idf.buildPath}. + * + * @param param The legacy parameter name (e.g., "idf.buildPath", "idf.cmakeCompilerArgs") + * @param currentProjectConf The ProjectConfElement to extract the value from + * @returns The parameter value, or empty string if not found or undefined + * @deprecated Use getConfigurePresetParameterValue for new code working with ConfigurePresets + */ function parameterToSameProjectConfigMap( param: string, currentProjectConf: ProjectConfElement @@ -495,10 +542,19 @@ function parameterToSameProjectConfigMap( } /** - * Substitutes variables like ${workspaceFolder} and ${env:VARNAME} in a string. - * @param text The input string potentially containing variables. - * @param workspaceFolder The workspace folder Uri to resolve ${workspaceFolder}. - * @returns The string with variables substituted, or undefined if input was undefined/null. + * Substitutes variables in a string for legacy ProjectConfElement format. + * Supports variable patterns like: + * - ${workspaceFolder} or ${workspaceRoot}: Replaced with workspace folder path + * - ${env:VARNAME}: Replaced with environment variable value + * - ${config:PARAM}: Replaced with configuration parameter value + * + * This is the legacy version; for ConfigurePreset use substituteVariablesInConfigurePreset. + * + * @param text The input string potentially containing variables + * @param workspaceFolder The workspace folder Uri to resolve ${workspaceFolder} + * @param config The legacy configuration object for resolving ${config:*} variables + * @returns The string with variables substituted, or undefined if input was undefined/null + * @deprecated Use substituteVariablesInConfigurePreset for new code */ function substituteVariablesInString( text: string | undefined, @@ -777,10 +833,14 @@ async function loadRawConfigurationFile( } /** - * Resolves inheritance for a preset by merging it with its parent presets - * @param preset The preset to resolve inheritance for - * @param allPresets All available presets (for inheritance lookup) - * @returns The preset with inheritance resolved + * Resolves inheritance for a preset by merging it with its parent presets. + * CMakePresets supports inheritance where a preset can inherit from one or more parent presets + * using the "inherits" field. This function recursively resolves the inheritance chain and + * merges all parent properties, with child properties overriding parent properties. + * + * @param preset The preset to resolve inheritance for (may have an "inherits" field) + * @param allPresets All available presets from both CMakePresets.json and CMakeUserPresets.json + * @returns The preset with inheritance fully resolved and the "inherits" field removed */ async function resolvePresetInheritance( preset: ConfigurePreset, @@ -830,10 +890,14 @@ async function resolvePresetInheritance( } /** - * Merges two presets, with the child preset overriding the parent preset - * @param parent The parent preset - * @param child The child preset (takes precedence) - * @returns The merged preset + * Merges two presets, with the child preset overriding the parent preset. + * Used during inheritance resolution to combine parent and child preset properties. + * For object properties (cacheVariables, environment, vendor), child properties are merged + * into parent properties rather than replacing them entirely. + * + * @param parent The parent preset (provides default values) + * @param child The child preset (takes precedence when properties conflict) + * @returns A new merged preset with child properties overriding parent properties */ function mergePresets( parent: ConfigurePreset, @@ -878,13 +942,19 @@ function mergePresets( } /** - * Processes a single configuration file (CMakePresets.json or CMakeUserPresets.json) + * Processes a single configuration file (CMakePresets.json or CMakeUserPresets.json). + * This function loads and processes all configure presets from a single file, + * applying variable substitution and path resolution directly without inheritance processing. + * + * Note: This function does NOT handle preset inheritance. For proper inheritance handling, + * use the two-pass approach with loadRawConfigurationFile and resolvePresetInheritance. + * * @param configJson The parsed JSON content of the configuration file - * @param workspaceFolder The workspace folder Uri - * @param resolvePaths Whether to resolve paths to absolute paths - * @param fileName The name of the file being processed (for error messages) + * @param workspaceFolder The workspace folder Uri for variable substitution and path resolution + * @param resolvePaths Whether to resolve relative paths to absolute paths + * @param fileName The name of the file being processed (used in error messages) * @returns An object mapping configuration names to their processed ConfigurePreset - * @deprecated Use loadRawConfigurationFile and resolvePresetInheritance instead + * @deprecated Use loadRawConfigurationFile and resolvePresetInheritance instead for proper inheritance */ async function processConfigurationFile( configJson: any, @@ -934,7 +1004,11 @@ async function processConfigurationFile( } /** - * Checks for legacy project configuration file and prompts user for migration + * Checks for the legacy project configuration file (esp_idf_project_configuration.json) + * and prompts the user to migrate it to the new CMakePresets.json format if found. + * This ensures a smooth transition from the old configuration format to the new one. + * + * @param workspaceFolder The workspace folder Uri where the legacy file might be located */ async function checkAndPromptLegacyMigration( workspaceFolder: Uri @@ -1020,7 +1094,15 @@ export async function migrateLegacyConfiguration( } /** - * Processes legacy project configuration format + * Processes legacy project configuration format (esp_idf_project_configuration.json). + * Converts the old configuration structure to the ProjectConfElement format, handling + * variable substitution and path resolution. This function is used during migration + * from the legacy format to CMakePresets. + * + * @param rawConfig The raw legacy configuration object + * @param workspaceFolder The workspace folder Uri for variable substitution and path resolution + * @param resolvePaths If true, resolves relative paths to absolute paths + * @returns A ProjectConfElement with all legacy settings converted and processed */ async function processLegacyProjectConfig( rawConfig: any, @@ -1162,7 +1244,21 @@ async function processLegacyProjectConfig( } /** - * Processes variable substitution and path resolution for ConfigurePreset + * Processes variable substitution and path resolution for a ConfigurePreset. + * This is a complex function that handles the transformation of raw ConfigurePreset data + * by substituting variables (like ${workspaceFolder}, ${env:VAR}) and optionally resolving + * relative paths to absolute paths. + * + * The function processes: + * - binaryDir: The build output directory path + * - cacheVariables: CMake cache variables (like IDF_TARGET, SDKCONFIG) + * - environment: Environment variables for the build + * - vendor: ESP-IDF specific vendor settings (OpenOCD configs, tasks, etc.) + * + * @param preset The raw ConfigurePreset to process + * @param workspaceFolder The workspace folder Uri used for resolving ${workspaceFolder} and relative paths + * @param resolvePaths If true, converts relative paths to absolute paths; if false, returns paths as-is for display + * @returns A new ConfigurePreset with all variables substituted and paths optionally resolved */ async function processConfigurePresetVariables( preset: ConfigurePreset, @@ -1209,7 +1305,13 @@ async function processConfigurePresetVariables( } /** - * Processes paths in ConfigurePreset + * Processes path strings in ConfigurePreset by substituting variables and optionally resolving to absolute paths. + * Handles paths like "build" or "${workspaceFolder}/build" and converts them based on the resolvePaths flag. + * @param pathValue The raw path string from the preset (may contain variables) + * @param workspaceFolder The workspace folder Uri used for variable substitution and path resolution + * @param preset The ConfigurePreset containing environment and other variables for substitution + * @param resolvePaths If true, converts relative paths to absolute paths; if false, only substitutes variables + * @returns The processed path string with variables substituted and optionally resolved to absolute path */ async function processConfigurePresetPath( pathValue: string, @@ -1235,7 +1337,15 @@ async function processConfigurePresetPath( } /** - * Processes cache variables in ConfigurePreset + * Processes CMake cache variables in ConfigurePreset by substituting variables and resolving paths. + * Cache variables include important CMake settings like IDF_TARGET, SDKCONFIG, and SDKCONFIG_DEFAULTS. + * String values undergo variable substitution, and path-related variables (SDKCONFIG, *PATH) are + * optionally resolved to absolute paths when resolvePaths is true. + * @param cacheVariables The cache variables object from the preset + * @param workspaceFolder The workspace folder Uri for variable substitution and path resolution + * @param preset The ConfigurePreset containing environment variables for substitution + * @param resolvePaths If true, resolves relative paths to absolute for path-related variables + * @returns A new cache variables object with all substitutions and resolutions applied */ async function processConfigurePresetCacheVariables( cacheVariables: { [key: string]: any }, @@ -1272,7 +1382,14 @@ async function processConfigurePresetCacheVariables( } /** - * Processes environment variables in ConfigurePreset + * Processes environment variables in ConfigurePreset by substituting variable references. + * Environment variables can reference other variables using ${env:VAR}, ${workspaceFolder}, etc. + * These variables are available to the build process and can be used in build scripts. + * @param environment The environment variables object from the preset + * @param workspaceFolder The workspace folder Uri for variable substitution + * @param preset The ConfigurePreset containing other variables for substitution + * @param resolvePaths Currently unused for environment variables but kept for consistency + * @returns A new environment object with all variable references substituted */ async function processConfigurePresetEnvironment( environment: { [key: string]: string }, @@ -1292,7 +1409,19 @@ async function processConfigurePresetEnvironment( } /** - * Processes vendor-specific settings in ConfigurePreset + * Processes ESP-IDF vendor-specific settings in ConfigurePreset. + * Vendor settings contain extension-specific configuration like: + * - OpenOCD configuration (debug level, configs, args) + * - Build tasks (preBuild, postBuild, preFlash, postFlash) + * - Compiler arguments and ninja arguments + * - Flash and monitor baud rates + * + * Each setting is processed to substitute variables in string values, arrays, and nested objects. + * @param vendor The vendor settings object from the preset (under "espressif/vscode-esp-idf" key) + * @param workspaceFolder The workspace folder Uri for variable substitution + * @param preset The ConfigurePreset containing other variables for substitution + * @param resolvePaths If true, paths in vendor settings will be resolved to absolute paths + * @returns A new vendor settings object with all variables substituted */ async function processConfigurePresetVendor( vendor: ESPIDFVendorSettings, @@ -1347,7 +1476,14 @@ async function processConfigurePresetVendor( } /** - * Processes object values in vendor settings + * Processes object values within vendor settings (e.g., OpenOCD configuration objects). + * Recursively processes string and array values within objects to substitute variables. + * For example, processes objects like { debugLevel: 2, configs: ["${env:BOARD_CONFIG}"], args: [] } + * @param obj The object to process (from a vendor setting value) + * @param workspaceFolder The workspace folder Uri for variable substitution + * @param preset The ConfigurePreset containing other variables for substitution + * @param resolvePaths Currently unused but kept for future path resolution in objects + * @returns A new object with all string and array values processed for variable substitution */ async function processConfigurePresetSettingObject( obj: any, @@ -1381,7 +1517,15 @@ async function processConfigurePresetSettingObject( } /** - * Processes variable substitution and path resolution for ProjectConfElement (legacy compatibility) + * Processes variable substitution and path resolution for ProjectConfElement (legacy compatibility). + * This function provides backward compatibility for code that still uses the legacy ProjectConfElement + * format. It delegates to processLegacyProjectConfig for the actual processing. + * + * @param element The ProjectConfElement to process + * @param workspaceFolder The workspace folder Uri for variable substitution and path resolution + * @param resolvePaths If true, resolves relative paths to absolute paths + * @returns A new ProjectConfElement with all variables substituted and paths resolved + * @deprecated Use processConfigurePresetVariables for new code */ async function processProjectConfElementVariables( element: ProjectConfElement, @@ -1482,7 +1626,21 @@ function substituteVariablesInConfigurePreset( } /** - * Gets parameter value from ConfigurePreset for variable substitution + * Retrieves parameter values from a ConfigurePreset for variable substitution. + * This function maps legacy parameter names (like "idf.buildPath") to their corresponding + * values in the ConfigurePreset structure. Used to support ${config:idf.buildPath} style + * variable references in configuration strings. + * + * Supported parameters include: + * - idf.cmakeCompilerArgs, idf.ninjaArgs + * - idf.buildPath, idf.sdkconfigDefaults, idf.sdkconfigFilePath + * - idf.flashBaudRate, idf.monitorBaudRate + * - idf.openOcdDebugLevel, idf.openOcdConfigs, idf.openOcdLaunchArgs + * - idf.preBuildTask, idf.postBuildTask, idf.preFlashTask, idf.postFlashTask + * + * @param param The parameter name to look up (e.g., "idf.buildPath") + * @param preset The ConfigurePreset to extract the value from + * @returns The parameter value (string, array, or empty string if not found) */ function getConfigurePresetParameterValue( param: string, @@ -1537,7 +1695,13 @@ function getConfigurePresetParameterValue( } /** - * Helper function to get ESP-IDF setting value from ConfigurePreset + * Helper function to retrieve ESP-IDF specific setting values from a ConfigurePreset's vendor section. + * ESP-IDF settings are stored in the vendor section under the "espressif/vscode-esp-idf" key. + * Each setting has a type (e.g., "openOCD", "tasks", "compileArgs") and a value. + * + * @param preset The ConfigurePreset containing vendor settings + * @param settingType The type of setting to retrieve (e.g., "openOCD", "tasks", "flashBaudRate") + * @returns The setting value if found, or undefined if the setting doesn't exist */ function getESPIDFSettingValue( preset: ConfigurePreset, @@ -1574,13 +1738,23 @@ export function projectConfElementToConfigurePreset( /** * Type guard to filter out undefined values from arrays. + * Useful with array.filter() to remove undefined elements while maintaining type safety. + * @param value The value to check + * @returns True if the value is defined (not undefined), false otherwise */ function isDefined(value: T | undefined): value is T { return value !== undefined; } /** - * Converts a CMakePresets ConfigurePreset to the legacy ProjectConfElement format + * Converts a CMakePresets ConfigurePreset to the legacy ProjectConfElement format. + * This conversion is necessary for backward compatibility with code that expects the legacy format. + * Extracts ESP-IDF specific settings from the vendor section and maps them to the legacy structure. + * + * @param preset The ConfigurePreset to convert + * @param workspaceFolder The workspace folder Uri for path resolution + * @param resolvePaths If true, resolves relative paths to absolute paths + * @returns A ProjectConfElement representing the same configuration in the legacy format */ function convertConfigurePresetToProjectConfElement( preset: ConfigurePreset, @@ -1658,7 +1832,14 @@ function convertConfigurePresetToProjectConfElement( } /** - * Converts a ProjectConfElement to CMakePresets ConfigurePreset format + * Converts a legacy ProjectConfElement to CMakePresets ConfigurePreset format. + * This conversion is used during migration and when saving configurations from legacy code. + * Maps the legacy structure to the CMakePresets format, storing ESP-IDF specific settings + * in the vendor section under "espressif/vscode-esp-idf". + * + * @param name The name for the configuration preset + * @param element The ProjectConfElement to convert + * @returns A ConfigurePreset representing the same configuration in the CMakePresets format */ function convertProjectConfElementToConfigurePreset( name: string, From 7b7512d15c196f862bab39ba472ac9f7a00c49d0 Mon Sep 17 00:00:00 2001 From: Radu Date: Fri, 24 Oct 2025 11:16:19 +0300 Subject: [PATCH 15/21] Add: Translations --- l10n/bundle.l10n.es.json | 36 +++++++++++- l10n/bundle.l10n.pt.json | 36 +++++++++++- l10n/bundle.l10n.ru.json | 36 +++++++++++- l10n/bundle.l10n.zh-CN.json | 36 +++++++++++- src/build/buildTask.ts | 9 ++- src/espIdf/openOcd/boardConfiguration.ts | 6 +- src/espIdf/openOcd/openOcdManager.ts | 33 +++++++---- src/espIdf/setTarget/index.ts | 30 ++++++---- .../ProjectConfigurationManager.ts | 58 +++++++++++-------- src/project-conf/projectConfPanel.ts | 2 +- 10 files changed, 226 insertions(+), 56 deletions(-) diff --git a/l10n/bundle.l10n.es.json b/l10n/bundle.l10n.es.json index d13034945..f5a57458d 100644 --- a/l10n/bundle.l10n.es.json +++ b/l10n/bundle.l10n.es.json @@ -257,5 +257,39 @@ "Clear Build Error Hints": "Borrar Pistas de Error de Compilación", "Clear OpenOCD Error Hints": "Borrar Pistas de Error de OpenOCD", "Open Reference": "Abrir Referencia", - "Launch Debug": "Iniciar depuración" + "Launch Debug": "Iniciar depuración", + "Failed to save unsaved files, ignoring and continuing with the build": "No se pudieron guardar los archivos sin guardar; se continuará con la compilación", + "Could not determine ESP-IDF version. Using default compiler arguments for the latest known version.": "No se pudo determinar la versión de ESP-IDF. Usando argumentos predeterminados del compilador para la última versión conocida.", + "OpenOCD is not running, do you want to launch it?": "OpenOCD no se está ejecutando, ¿desea iniciarlo?", + "Cancel": "Cancelar", + "Invalid OpenOCD bin path or access is denied for the user": "Ruta del binario de OpenOCD inválida o acceso denegado para el usuario", + "OPENOCD_SCRIPTS environment variable is missing. Please set it in idf.customExtraVars or in your system environment variables.": "Falta la variable de entorno OPENOCD_SCRIPTS. Establézcala en idf.customExtraVars o en las variables de entorno del sistema.", + "Invalid OpenOCD Config files. Check idf.openOcdConfigs configuration value.": "Archivos de configuración de OpenOCD inválidos. Verifique el valor de configuración idf.openOcdConfigs.", + "For assistance with OpenOCD errors, please refer to our Troubleshooting FAQ: {0}": "Para obtener ayuda con errores de OpenOCD, consulte nuestras Preguntas Frecuentes de Solución de Problemas: {0}", + "OpenOCD Exit with non-zero error code {0}": "OpenOCD salió con un código de error distinto de cero {0}", + "OpenOCD Server (Running)": "Servidor OpenOCD (En ejecución)", + "OpenOCD Server (Stopped)": "Servidor OpenOCD (Detenido)", + "[Stopped] : OpenOCD Server": "[Detenido] : Servidor OpenOCD", + "ESP-IDF board not selected. Remember to set the configuration files for OpenOCD with idf.openOcdConfigs": "No se seleccionó una placa ESP-IDF. Recuerde establecer los archivos de configuración de OpenOCD con idf.openOcdConfigs", + "ESP-IDF: Setting device target...": "ESP-IDF: Estableciendo objetivo del dispositivo...", + "Devkit detection script not available. A default list of targets will be displayed instead.": "El script de detección de devkits no está disponible. Se mostrará una lista predeterminada de objetivos.", + "No connected boards detected or error running DevkitsCommand: ": "No se detectaron placas conectadas o error al ejecutar DevkitsCommand: ", + "Preview target": "Objetivo de vista previa", + "Default Boards": "Placas predeterminadas", + "Status: CONNECTED": "Estado: CONECTADO", + "Location: {0}": "Ubicación: {0}", + "Loaded {0} project configuration(s) from {1}: {2}. No configuration selected.": "Se cargaron {0} configuraciones de proyecto desde {1}: {2}. Ninguna configuración seleccionada.", + "Loaded {0} project configuration(s) from {1}: {2}": "Se cargaron {0} configuraciones de proyecto desde {1}: {2}", + "Failed to parse project configuration files": "Error al analizar los archivos de configuración del proyecto", + "New configurations added: {0}": "Nuevas configuraciones agregadas: {0}", + "Configurations removed: {0}": "Configuraciones eliminadas: {0}", + "Project configuration file has been deleted.": "Se ha eliminado el archivo de configuración del proyecto.", + "Project configuration file created with {0} configuration(s). Select one to use.": "Archivo de configuración del proyecto creado con {0} configuración(es). Seleccione una para usar.", + "Open editor": "Abrir editor", + "Configuration {0}": "Configuración {0}", + "No Configuration Selected": "Ninguna configuración seleccionada", + "No project configuration selected. Click to select one": "No se seleccionó ninguna configuración de proyecto. Haga clic para seleccionar una", + "Legacy Configs ({0})": "Configuraciones heredadas ({0})", + "Found legacy project configurations: {0}. Click to migrate to the new CMakePresets.json format.": "Se encontraron configuraciones de proyecto heredadas: {0}. Haga clic para migrar al nuevo formato CMakePresets.json.", + "Project Configuration changes have been saved": "Se han guardado los cambios de configuración del proyecto" } diff --git a/l10n/bundle.l10n.pt.json b/l10n/bundle.l10n.pt.json index 2cf68d5b6..c801ed209 100644 --- a/l10n/bundle.l10n.pt.json +++ b/l10n/bundle.l10n.pt.json @@ -258,5 +258,39 @@ "Clear Build Error Hints": "Limpar Dicas de Erro de Compilação", "Clear OpenOCD Error Hints": "Limpar Dicas de Erro do OpenOCD", "Open Reference": "Abrir Referência", - "Launch Debug": "Iniciar depuração" + "Launch Debug": "Iniciar depuração", + "Failed to save unsaved files, ignoring and continuing with the build": "Falha ao salvar arquivos não salvos; continuando com a compilação", + "Could not determine ESP-IDF version. Using default compiler arguments for the latest known version.": "Não foi possível determinar a versão do ESP-IDF. Usando argumentos padrão do compilador para a versão mais recente conhecida.", + "OpenOCD is not running, do you want to launch it?": "OpenOCD não está em execução, deseja iniciá-lo?", + "Cancel": "Cancelar", + "Invalid OpenOCD bin path or access is denied for the user": "Caminho do binário do OpenOCD inválido ou acesso negado ao usuário", + "OPENOCD_SCRIPTS environment variable is missing. Please set it in idf.customExtraVars or in your system environment variables.": "A variável de ambiente OPENOCD_SCRIPTS está ausente. Defina-a em idf.customExtraVars ou nas variáveis de ambiente do sistema.", + "Invalid OpenOCD Config files. Check idf.openOcdConfigs configuration value.": "Arquivos de configuração do OpenOCD inválidos. Verifique o valor de configuração idf.openOcdConfigs.", + "For assistance with OpenOCD errors, please refer to our Troubleshooting FAQ: {0}": "Para obter ajuda com erros do OpenOCD, consulte nosso FAQ de Solução de Problemas: {0}", + "OpenOCD Exit with non-zero error code {0}": "OpenOCD saiu com código de erro diferente de zero {0}", + "OpenOCD Server (Running)": "Servidor OpenOCD (Em execução)", + "OpenOCD Server (Stopped)": "Servidor OpenOCD (Parado)", + "[Stopped] : OpenOCD Server": "[Parado] : Servidor OpenOCD", + "ESP-IDF board not selected. Remember to set the configuration files for OpenOCD with idf.openOcdConfigs": "Placa ESP-IDF não selecionada. Lembre-se de definir os arquivos de configuração do OpenOCD com idf.openOcdConfigs", + "ESP-IDF: Setting device target...": "ESP-IDF: Definindo alvo do dispositivo...", + "Devkit detection script not available. A default list of targets will be displayed instead.": "Script de detecção de devkits indisponível. Uma lista padrão de alvos será exibida.", + "No connected boards detected or error running DevkitsCommand: ": "Nenhuma placa conectada detectada ou erro ao executar DevkitsCommand: ", + "Preview target": "Alvo de visualização", + "Default Boards": "Placas padrão", + "Status: CONNECTED": "Status: CONECTADO", + "Location: {0}": "Localização: {0}", + "Loaded {0} project configuration(s) from {1}: {2}. No configuration selected.": "{0} configuração(ões) de projeto carregada(s) de {1}: {2}. Nenhuma configuração selecionada.", + "Loaded {0} project configuration(s) from {1}: {2}": "{0} configuração(ões) de projeto carregada(s) de {1}: {2}", + "Failed to parse project configuration files": "Falha ao analisar os arquivos de configuração do projeto", + "New configurations added: {0}": "Novas configurações adicionadas: {0}", + "Configurations removed: {0}": "Configurações removidas: {0}", + "Project configuration file has been deleted.": "O arquivo de configuração do projeto foi excluído.", + "Project configuration file created with {0} configuration(s). Select one to use.": "Arquivo de configuração do projeto criado com {0} configuração(ões). Selecione uma para usar.", + "Open editor": "Abrir editor", + "Configuration {0}": "Configuração {0}", + "No Configuration Selected": "Nenhuma configuração selecionada", + "No project configuration selected. Click to select one": "Nenhuma configuração de projeto selecionada. Clique para selecionar uma", + "Legacy Configs ({0})": "Configurações legadas ({0})", + "Found legacy project configurations: {0}. Click to migrate to the new CMakePresets.json format.": "Foram encontradas configurações de projeto legadas: {0}. Clique para migrar para o novo formato CMakePresets.json.", + "Project Configuration changes have been saved": "As alterações na configuração do projeto foram salvas" } diff --git a/l10n/bundle.l10n.ru.json b/l10n/bundle.l10n.ru.json index ddebd793f..9abc363ee 100644 --- a/l10n/bundle.l10n.ru.json +++ b/l10n/bundle.l10n.ru.json @@ -258,5 +258,39 @@ "Clear Build Error Hints": "Очистить подсказки по ошибкам сборки", "Clear OpenOCD Error Hints": "Очистить подсказки по ошибкам OpenOCD", "Open Reference": "Открыть ссылку", - "Launch Debug": "Запуск отладки" + "Launch Debug": "Запуск отладки", + "Failed to save unsaved files, ignoring and continuing with the build": "Не удалось сохранить несохраненные файлы; продолжаем сборку", + "Could not determine ESP-IDF version. Using default compiler arguments for the latest known version.": "Не удалось определить версию ESP-IDF. Используются стандартные аргументы компилятора для последней известной версии.", + "OpenOCD is not running, do you want to launch it?": "OpenOCD не запущен, хотите запустить его?", + "Cancel": "Отмена", + "Invalid OpenOCD bin path or access is denied for the user": "Неверный путь к бинарному файлу OpenOCD или доступ запрещен", + "OPENOCD_SCRIPTS environment variable is missing. Please set it in idf.customExtraVars or in your system environment variables.": "Отсутствует переменная окружения OPENOCD_SCRIPTS. Установите её в idf.customExtraVars или в системных переменных окружения.", + "Invalid OpenOCD Config files. Check idf.openOcdConfigs configuration value.": "Неверные файлы конфигурации OpenOCD. Проверьте параметр конфигурации idf.openOcdConfigs.", + "For assistance with OpenOCD errors, please refer to our Troubleshooting FAQ: {0}": "Для помощи с ошибками OpenOCD обратитесь к нашему разделу Часто задаваемых вопросов по устранению неполадок: {0}", + "OpenOCD Exit with non-zero error code {0}": "OpenOCD завершился с ненулевым кодом ошибки {0}", + "OpenOCD Server (Running)": "Сервер OpenOCD (Запущен)", + "OpenOCD Server (Stopped)": "Сервер OpenOCD (Остановлен)", + "[Stopped] : OpenOCD Server": "[Остановлен] : Сервер OpenOCD", + "ESP-IDF board not selected. Remember to set the configuration files for OpenOCD with idf.openOcdConfigs": "Плата ESP-IDF не выбрана. Не забудьте установить файлы конфигурации OpenOCD с помощью параметра idf.openOcdConfigs", + "ESP-IDF: Setting device target...": "ESP-IDF: Установка целевого устройства...", + "Devkit detection script not available. A default list of targets will be displayed instead.": "Скрипт обнаружения devkit недоступен. Вместо этого будет показан список целей по умолчанию.", + "No connected boards detected or error running DevkitsCommand: ": "Не обнаружены подключенные платы или ошибка при выполнении DevkitsCommand: ", + "Preview target": "Цель (предпросмотр)", + "Default Boards": "Платы по умолчанию", + "Status: CONNECTED": "Статус: ПОДКЛЮЧЕНО", + "Location: {0}": "Местоположение: {0}", + "Loaded {0} project configuration(s) from {1}: {2}. No configuration selected.": "Загружено {0} конфигураций проекта из {1}: {2}. Конфигурация не выбрана.", + "Loaded {0} project configuration(s) from {1}: {2}": "Загружено {0} конфигураций проекта из {1}: {2}", + "Failed to parse project configuration files": "Не удалось проанализировать файлы конфигурации проекта", + "New configurations added: {0}": "Добавлены новые конфигурации: {0}", + "Configurations removed: {0}": "Конфигурации удалены: {0}", + "Project configuration file has been deleted.": "Файл конфигурации проекта был удален.", + "Project configuration file created with {0} configuration(s). Select one to use.": "Создан файл конфигурации проекта с {0} конфигурациями. Выберите одну для использования.", + "Open editor": "Открыть редактор", + "Configuration {0}": "Конфигурация {0}", + "No Configuration Selected": "Конфигурация не выбрана", + "No project configuration selected. Click to select one": "Конфигурация проекта не выбрана. Нажмите, чтобы выбрать", + "Legacy Configs ({0})": "Унаследованные конфигурации ({0})", + "Found legacy project configurations: {0}. Click to migrate to the new CMakePresets.json format.": "Найдены устаревшие конфигурации проекта: {0}. Нажмите, чтобы выполнить миграцию в новый формат CMakePresets.json.", + "Project Configuration changes have been saved": "Изменения конфигурации проекта сохранены" } diff --git a/l10n/bundle.l10n.zh-CN.json b/l10n/bundle.l10n.zh-CN.json index a8ff62797..dbb0427a3 100644 --- a/l10n/bundle.l10n.zh-CN.json +++ b/l10n/bundle.l10n.zh-CN.json @@ -258,5 +258,39 @@ "Clear Build Error Hints": "清除构建错误提示", "Clear OpenOCD Error Hints": "清除 OpenOCD 错误提示", "Open Reference": "打开参考", - "Launch Debug": "启动调试" + "Launch Debug": "启动调试", + "Failed to save unsaved files, ignoring and continuing with the build": "无法保存未保存的文件;将继续进行构建", + "Could not determine ESP-IDF version. Using default compiler arguments for the latest known version.": "无法确定 ESP-IDF 版本。将使用最新已知版本的默认编译参数。", + "OpenOCD is not running, do you want to launch it?": "OpenOCD 未运行,是否启动?", + "Cancel": "取消", + "Invalid OpenOCD bin path or access is denied for the user": "OpenOCD 二进制路径无效或用户无权限", + "OPENOCD_SCRIPTS environment variable is missing. Please set it in idf.customExtraVars or in your system environment variables.": "缺少 OPENOCD_SCRIPTS 环境变量。请在 idf.customExtraVars 或系统环境变量中设置。", + "Invalid OpenOCD Config files. Check idf.openOcdConfigs configuration value.": "OpenOCD 配置文件无效。请检查 idf.openOcdConfigs 配置值。", + "For assistance with OpenOCD errors, please refer to our Troubleshooting FAQ: {0}": "如需帮助解决 OpenOCD 错误,请参考常见问题排查文档:{0}", + "OpenOCD Exit with non-zero error code {0}": "OpenOCD 以非零错误代码退出:{0}", + "OpenOCD Server (Running)": "OpenOCD 服务器(运行中)", + "OpenOCD Server (Stopped)": "OpenOCD 服务器(已停止)", + "[Stopped] : OpenOCD Server": "[已停止]:OpenOCD 服务器", + "ESP-IDF board not selected. Remember to set the configuration files for OpenOCD with idf.openOcdConfigs": "未选择 ESP-IDF 开发板。请记得通过 idf.openOcdConfigs 设置 OpenOCD 配置文件", + "ESP-IDF: Setting device target...": "ESP-IDF:正在设置设备目标...", + "Devkit detection script not available. A default list of targets will be displayed instead.": "Devkit 检测脚本不可用。将显示默认的目标列表。", + "No connected boards detected or error running DevkitsCommand: ": "未检测到已连接的开发板或执行 DevkitsCommand 时出错:", + "Preview target": "预览目标", + "Default Boards": "默认开发板", + "Status: CONNECTED": "状态:已连接", + "Location: {0}": "位置:{0}", + "Loaded {0} project configuration(s) from {1}: {2}. No configuration selected.": "已从 {1} 加载 {0} 个项目配置:{2}。未选择配置。", + "Loaded {0} project configuration(s) from {1}: {2}": "已从 {1} 加载 {0} 个项目配置:{2}", + "Failed to parse project configuration files": "解析项目配置文件失败", + "New configurations added: {0}": "已添加新配置:{0}", + "Configurations removed: {0}": "已移除配置:{0}", + "Project configuration file has been deleted.": "项目配置文件已删除。", + "Project configuration file created with {0} configuration(s). Select one to use.": "已创建包含 {0} 个配置的项目配置文件。请选择一个使用。", + "Open editor": "打开编辑器", + "Configuration {0}": "配置 {0}", + "No Configuration Selected": "未选择配置", + "No project configuration selected. Click to select one": "未选择项目配置。单击选择一个", + "Legacy Configs ({0})": "旧版配置({0})", + "Found legacy project configurations: {0}. Click to migrate to the new CMakePresets.json format.": "发现旧版项目配置:{0}。单击迁移到新的 CMakePresets.json 格式。", + "Project Configuration changes have been saved": "项目配置更改已保存" } diff --git a/src/build/buildTask.ts b/src/build/buildTask.ts index 541b0d153..8b2314d26 100644 --- a/src/build/buildTask.ts +++ b/src/build/buildTask.ts @@ -69,8 +69,9 @@ export class BuildTask { try { await this.saveBeforeBuild(); } catch (error) { - const errorMessage = - "Failed to save unsaved files, ignoring and continuing with the build"; + const errorMessage = vscode.l10n.t( + "Failed to save unsaved files, ignoring and continuing with the build" + ); Logger.error(errorMessage, error, "build saveBeforeBuild"); Logger.warnNotify(errorMessage); } @@ -120,7 +121,9 @@ export class BuildTask { let defaultCompilerArgs; if (espIdfVersion === "x.x") { Logger.warn( - "Could not determine ESP-IDF version. Using default compiler arguments for the latest known version." + vscode.l10n.t( + "Could not determine ESP-IDF version. Using default compiler arguments for the latest known version." + ) ); defaultCompilerArgs = [ "-G", diff --git a/src/espIdf/openOcd/boardConfiguration.ts b/src/espIdf/openOcd/boardConfiguration.ts index 73938a55f..4e87ff348 100644 --- a/src/espIdf/openOcd/boardConfiguration.ts +++ b/src/espIdf/openOcd/boardConfiguration.ts @@ -182,12 +182,14 @@ export async function selectOpenOcdConfigFiles( const selectedBoard = boardQuickPick.selectedItems[0]; if (!selectedBoard) { Logger.infoNotify( - `ESP-IDF board not selected. Remember to set the configuration files for OpenOCD with idf.openOcdConfigs` + l10n.t( + "ESP-IDF board not selected. Remember to set the configuration files for OpenOCD with idf.openOcdConfigs" + ) ); } else if (selectedBoard && selectedBoard.target) { if (selectedBoard.label.indexOf("Custom board") !== -1) { const inputBoard = await window.showInputBox({ - placeHolder: "Enter comma-separated configuration files", + placeHolder: l10n.t("Enter comma-separated configuration files"), value: selectedBoard.target.configFiles.join(","), }); if (inputBoard) { diff --git a/src/espIdf/openOcd/openOcdManager.ts b/src/espIdf/openOcd/openOcdManager.ts index 2afb4d628..0e8e29238 100644 --- a/src/espIdf/openOcd/openOcdManager.ts +++ b/src/espIdf/openOcd/openOcdManager.ts @@ -153,12 +153,12 @@ export class OpenOCDManager extends EventEmitter { const tclClient = new TCLClient(tclConnectionParams); if (!(await tclClient.isOpenOCDServerRunning())) { const resp = await vscode.window.showInformationMessage( - "OpenOCD is not running, do you want to launch it?", + vscode.l10n.t("OpenOCD is not running, do you want to launch it?"), { modal: true }, - { title: "Yes" }, - { title: "Cancel", isCloseAffordance: true } + { title: vscode.l10n.t("Yes") }, + { title: vscode.l10n.t("Cancel"), isCloseAffordance: true } ); - if (resp && resp.title === "Yes") { + if (resp && resp.title === vscode.l10n.t("Yes")) { await OpenOCDManager.init().start(); return await tclClient.isOpenOCDServerRunning(); } @@ -174,12 +174,16 @@ export class OpenOCDManager extends EventEmitter { const modifiedEnv = await appendIdfAndToolsToPath(this.workspace); if (!isBinInPath("openocd", modifiedEnv)) { throw new Error( - "Invalid OpenOCD bin path or access is denied for the user" + vscode.l10n.t( + "Invalid OpenOCD bin path or access is denied for the user" + ) ); } if (typeof modifiedEnv.OPENOCD_SCRIPTS === "undefined") { throw new Error( - "OPENOCD_SCRIPTS environment variable is missing. Please set it in idf.customExtraVars or in your system environment variables." + vscode.l10n.t( + "OPENOCD_SCRIPTS environment variable is missing. Please set it in idf.customExtraVars or in your system environment variables." + ) ); } @@ -202,7 +206,9 @@ export class OpenOCDManager extends EventEmitter { openOcdConfigFilesList.length < 1 ) { throw new Error( - "Invalid OpenOCD Config files. Check idf.openOcdConfigs configuration value." + vscode.l10n.t( + "Invalid OpenOCD Config files. Check idf.openOcdConfigs configuration value." + ) ); } @@ -260,7 +266,10 @@ export class OpenOCDManager extends EventEmitter { this.server.on("close", (code: number, signal: string) => { if (this.encounteredErrors) { OutputChannel.appendLine( - `For assistance with OpenOCD errors, please refer to our Troubleshooting FAQ: ${ESP.URL.OpenOcdTroubleshootingFaq}`, + vscode.l10n.t( + "For assistance with OpenOCD errors, please refer to our Troubleshooting FAQ: {0}", + ESP.URL.OpenOcdTroubleshootingFaq + ), "OpenOCD" ); } @@ -278,7 +287,7 @@ export class OpenOCDManager extends EventEmitter { } this.stop(); }); - this.updateStatusText("❇️ OpenOCD Server (Running)"); + this.updateStatusText(`❇️ ${vscode.l10n.t("OpenOCD Server (Running)")}`); OutputChannel.show(); } @@ -286,8 +295,8 @@ export class OpenOCDManager extends EventEmitter { if (this.server && !this.server.killed) { this.server.kill("SIGKILL"); this.server = undefined; - this.updateStatusText("❌ OpenOCD Server (Stopped)"); - const endMsg = "[Stopped] : OpenOCD Server"; + this.updateStatusText(`❌ ${vscode.l10n.t("OpenOCD Server (Stopped)")}`); + const endMsg = `${vscode.l10n.t("[Stopped] : OpenOCD Server")}`; OutputChannel.appendLine(endMsg, "OpenOCD"); Logger.info(endMsg); } @@ -303,7 +312,7 @@ export class OpenOCDManager extends EventEmitter { vscode.StatusBarAlignment.Left, 1 ); - this.statusBar.name = this.statusBar.text = "OpenOCD Server"; + this.statusBar.name = this.statusBar.text = vscode.l10n.t("OpenOCD Server"); const commandDictionary = createCommandDictionary(); this.statusBar.tooltip = commandDictionary[CommandKeys.OpenOCD].tooltip; this.statusBar.command = CommandKeys.OpenOCD; diff --git a/src/espIdf/setTarget/index.ts b/src/espIdf/setTarget/index.ts index 7296d538f..bd17d153e 100644 --- a/src/espIdf/setTarget/index.ts +++ b/src/espIdf/setTarget/index.ts @@ -84,7 +84,7 @@ export async function setIdfTarget( { cancellable: false, location: progressLocation, - title: "ESP-IDF: Setting device target...", + title: l10n.t("ESP-IDF: Setting device target..."), }, async (progress: Progress<{ message: string; increment: number }>) => { try { @@ -111,9 +111,11 @@ export async function setIdfTarget( (t) => t.target === b.target ), description: b.description, - detail: `Status: CONNECTED${ - b.location ? ` Location: ${b.location}` : "" - }`, + detail: + l10n.t("Status: CONNECTED") + + (b.location + ? ` ${l10n.t("Location: {0}", b.location)}` + : ""), isConnected: true, boardInfo: b, } as ISetTargetQuickPickItems) @@ -122,18 +124,23 @@ export async function setIdfTarget( } } else { Logger.info( - "Devkit detection script not available. A default list of targets will be displayed instead." + l10n.t( + "Devkit detection script not available. A default list of targets will be displayed instead." + ) ); } } catch (e) { Logger.info( - "No connected boards detected or error running DevkitsCommand: " + - (e && e.message ? e.message : e) + l10n.t( + "No connected boards detected or error running DevkitsCommand: " + ) + (e && e.message ? e.message : e) ); } } else { Logger.info( - "Connected ESP-IDF devkit detection is skipped while debugging. You can still select a target manually." + l10n.t( + "Connected ESP-IDF devkit detection is skipped while debugging. You can still select a target manually." + ) ); } let quickPickItems: ISetTargetQuickPickItems[] = []; @@ -141,7 +148,7 @@ export async function setIdfTarget( (t) => ({ label: t.label, idfTarget: t, - description: t.isPreview ? "Preview target" : undefined, + description: t.isPreview ? l10n.t("Preview target") : undefined, isConnected: false, }) ); @@ -150,7 +157,10 @@ export async function setIdfTarget( connectedBoards.length > 0 ? [ ...connectedBoards, - { kind: QuickPickItemKind.Separator, label: "Default Boards" }, + { + kind: QuickPickItemKind.Separator, + label: l10n.t("Default Boards"), + }, ...defaultBoards, ] : defaultBoards; diff --git a/src/project-conf/ProjectConfigurationManager.ts b/src/project-conf/ProjectConfigurationManager.ts index 05db7bc9f..e22215e96 100644 --- a/src/project-conf/ProjectConfigurationManager.ts +++ b/src/project-conf/ProjectConfigurationManager.ts @@ -144,21 +144,23 @@ export class ProjectConfigurationManager { if (saveLastProjectConfiguration !== false) { // When setting is enabled, show no configuration selected status window.showInformationMessage( - `Loaded ${ - this.configVersions.length - } project configuration(s) from ${fileInfo.join( - " and " - )}: ${this.configVersions.join(", ")}. No configuration selected.` + l10n.t( + "Loaded {0} project configuration(s) from {1}: {2}. No configuration selected.", + this.configVersions.length, + fileInfo.join(" and "), + this.configVersions.join(", ") + ) ); this.setNoConfigurationSelectedStatus(); } else { // Show the current behavior when auto-selection is disabled window.showInformationMessage( - `Loaded ${ - this.configVersions.length - } project configuration(s) from ${fileInfo.join( - " and " - )}: ${this.configVersions.join(", ")}` + l10n.t( + "Loaded {0} project configuration(s) from {1}: {2}", + this.configVersions.length, + fileInfo.join(" and "), + this.configVersions.join(", ") + ) ); this.setNoConfigurationSelectedStatus(); } @@ -171,7 +173,7 @@ export class ProjectConfigurationManager { } } catch (error) { Logger.errorNotify( - `Failed to parse project configuration files`, + l10n.t("Failed to parse project configuration files"), error, "ProjectConfigurationManager initialize" ); @@ -243,13 +245,13 @@ export class ProjectConfigurationManager { if (addedVersions.length > 0) { window.showInformationMessage( - `New configurations added: ${addedVersions.join(", ")}` + l10n.t("New configurations added: {0}", addedVersions.join(", ")) ); } if (removedVersions.length > 0) { window.showInformationMessage( - `Configurations removed: ${removedVersions.join(", ")}` + l10n.t("Configurations removed: {0}", removedVersions.join(", ")) ); } @@ -312,7 +314,7 @@ export class ProjectConfigurationManager { } // Optionally notify the user - Logger.infoNotify("Project configuration file has been deleted."); + Logger.infoNotify(l10n.t("Project configuration file has been deleted.")); } private async handleConfigFileCreate(): Promise { @@ -349,7 +351,10 @@ export class ProjectConfigurationManager { // Notify the user about available configurations window.showInformationMessage( - `Project configuration file created with ${this.configVersions.length} configuration(s). Select one to use.` + l10n.t( + "Project configuration file created with {0} configuration(s). Select one to use.", + this.configVersions.length + ) ); } } else { @@ -370,9 +375,9 @@ export class ProjectConfigurationManager { * Sets the status bar to indicate no configuration is selected */ private setNoConfigurationSelectedStatus(): void { - const statusBarItemName = "No Configuration Selected"; + const statusBarItemName = l10n.t("No Configuration Selected"); const statusBarItemTooltip = - "No project configuration selected. Click to select one"; + l10n.t("No project configuration selected. Click to select one"); const commandToUse = "espIdf.projectConf"; if (this.statusBarItems["projectConf"]) { @@ -462,12 +467,13 @@ export class ProjectConfigurationManager { return; } + const openEditorLabel = l10n.t("Open editor"); const emptyOption = await window.showInformationMessage( l10n.t("No CMakePresets configure presets found"), - "Open editor" + openEditorLabel ); - if (emptyOption === "Open editor") { + if (emptyOption === openEditorLabel) { commands.executeCommand("espIdf.projectConfigurationEditor"); } return; @@ -477,7 +483,7 @@ export class ProjectConfigurationManager { let quickPickItems = Object.keys(projectConfigurations).map((k) => { return { description: k, - label: `Configuration ${k}`, + label: l10n.t("Configuration {0}", k), target: k, }; }); @@ -545,10 +551,14 @@ export class ProjectConfigurationManager { * Sets status bar to indicate legacy configurations are available */ private setLegacyConfigurationStatus(legacyConfigNames: string[]): void { - const statusBarItemName = `Legacy Configs (${legacyConfigNames.length})`; - const statusBarItemTooltip = `Found legacy project configurations: ${legacyConfigNames.join( - ", " - )}. Click to migrate to CMakePresets.json format.`; + const statusBarItemName = l10n.t( + "Legacy Configs ({0})", + legacyConfigNames.length + ); + const statusBarItemTooltip = l10n.t( + "Found legacy project configurations: {0}. Click to migrate to the new CMakePresets.json format.", + legacyConfigNames.join(", ") + ); const commandToUse = "espIdf.projectConf"; if (this.statusBarItems["projectConf"]) { diff --git a/src/project-conf/projectConfPanel.ts b/src/project-conf/projectConfPanel.ts index 46ac1cd9b..26e40c82a 100644 --- a/src/project-conf/projectConfPanel.ts +++ b/src/project-conf/projectConfPanel.ts @@ -194,7 +194,7 @@ export class projectConfigurationPanel { this.clearSelectedProject(projectConfKeys); await saveProjectConfFileLegacy(this.workspaceFolder, projectConfDict); window.showInformationMessage( - "Project Configuration changes has been saved" + l10n.t("Project Configuration changes have been saved") ); } From b24c8b8f1a8759a69b8a880d864b8f4ac6019c58 Mon Sep 17 00:00:00 2001 From: Radu Date: Sun, 26 Oct 2025 08:38:53 +0200 Subject: [PATCH 16/21] doc: CMakePresets in multiple-project --- .../additionalfeatures/multiple-projects.rst | 59 ++++++++++++++----- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/docs_espressif/en/additionalfeatures/multiple-projects.rst b/docs_espressif/en/additionalfeatures/multiple-projects.rst index 8dead8011..698a1607a 100644 --- a/docs_espressif/en/additionalfeatures/multiple-projects.rst +++ b/docs_espressif/en/additionalfeatures/multiple-projects.rst @@ -102,25 +102,56 @@ Use Multiple Build Configurations in the Same Workspace Folder Use the ESP-IDF CMake `Multiple Build Configurations Example `_ to follow this tutorial. -Use the ``ESP-IDF: Open Project Configuration`` command to create two configuration profiles: ``prod1`` and ``prod2``. Set ``sdkconfig.prod_common;sdkconfig.prod1`` and ``sdkconfig.prod_common;sdkconfig.prod2`` in the sdkconfig defaults field as shown below: - -.. image:: ../../../media/tutorials/project_conf/enterConfigName.png - -.. image:: ../../../media/tutorials/project_conf/prod1.png - -.. image:: ../../../media/tutorials/project_conf/prod2.png - -After creating each profile and setting the configuration, click the ``Save`` button. Use the ``ESP-IDF: Select Project Configuration`` command to choose the configuration to override extension configuration settings. +.. note:: -.. image:: ../../../media/tutorials/project_conf/selectConfig.png + The ESP-IDF ``multi_config`` example already ships with a ready-to-use ``CMakePresets.json`` and works out of the box. When you select a project configuration in this extension, the extension will automatically attach vendor settings under ``espressif/vscode-esp-idf`` for the chosen preset (for example, OpenOCD configuration and ``IDF_TARGET``) based on your currently selected board configuration and target in the extension. -Once a configuration profile is selected, it will appear in the status bar as shown before. +Define your configurations in ``CMakePresets.json`` (and optionally ``CMakeUserPresets.json``). Then use ``ESP-IDF: Select Project Configuration`` to choose among the discovered presets. The extension will apply the selected preset when building/flashing/monitoring. -.. image:: ../../../media/tutorials/project_conf/configInStatusBar.png +Typical entries in ``CMakePresets.json``: -Now, use the ``ESP-IDF: Build your Project`` command to build the project for ``prod1`` and ``prod2``. You will see binaries generated for each profile in the specified path. Use the ``ESP-IDF: Select Project Configuration`` command to switch between configurations. +.. code-block:: JSON -Use the ``ESP-IDF: Open Project Configuration`` command to modify, add, or delete the configuration profiles. To stop using these profiles, delete all configuration profiles. + { + "version": 3, + "configurePresets": [ + { + "name": "default", + "binaryDir": "build/default", + "displayName": "Default (development)", + "description": "Development configuration", + "cacheVariables": { + "SDKCONFIG": "./build/default/sdkconfig" + } + }, + { + "name": "prod1", + "binaryDir": "build/prod1", + "displayName": "Product 1", + "description": "Production configuration for product 1", + "cacheVariables": { + "SDKCONFIG_DEFAULTS": "sdkconfig.defaults.prod_common;sdkconfig.defaults.prod1", + "SDKCONFIG": "./build/prod1/sdkconfig" + } + }, + { + "name": "prod2", + "binaryDir": "build/prod2", + "displayName": "Product 2", + "description": "Production configuration for product 2", + "cacheVariables": { + "SDKCONFIG_DEFAULTS": "sdkconfig.defaults.prod_common;sdkconfig.defaults.prod2", + "SDKCONFIG": "./build/prod2/sdkconfig" + } + } + ] + } + +Selecting presets: + +1. Open Command Palette and run ``ESP-IDF: Select Project Configuration``. +2. Pick ``default``, ``prod1`` or ``prod2``. +3. Run ``ESP-IDF: Build Your Project`` to build with the selected preset. Switch presets any time via the same selection command. Multiple ESP-IDF Versions ------------------------- From f2536685b73f9ae8731b2262a245e61feb4fd5ac Mon Sep 17 00:00:00 2001 From: radurentea Date: Thu, 30 Oct 2025 13:53:54 +0800 Subject: [PATCH 17/21] Remove unwanted translations --- l10n/bundle.l10n.es.json | 18 --------- l10n/bundle.l10n.pt.json | 18 --------- l10n/bundle.l10n.ru.json | 18 --------- l10n/bundle.l10n.zh-CN.json | 18 --------- src/build/buildTask.ts | 8 +--- src/espIdf/openOcd/boardConfiguration.ts | 4 +- src/espIdf/openOcd/openOcdManager.ts | 50 ++++++------------------ src/espIdf/setTarget/index.ts | 24 ++++-------- 8 files changed, 24 insertions(+), 134 deletions(-) diff --git a/l10n/bundle.l10n.es.json b/l10n/bundle.l10n.es.json index f5a57458d..8d5859df2 100644 --- a/l10n/bundle.l10n.es.json +++ b/l10n/bundle.l10n.es.json @@ -258,26 +258,8 @@ "Clear OpenOCD Error Hints": "Borrar Pistas de Error de OpenOCD", "Open Reference": "Abrir Referencia", "Launch Debug": "Iniciar depuración", - "Failed to save unsaved files, ignoring and continuing with the build": "No se pudieron guardar los archivos sin guardar; se continuará con la compilación", - "Could not determine ESP-IDF version. Using default compiler arguments for the latest known version.": "No se pudo determinar la versión de ESP-IDF. Usando argumentos predeterminados del compilador para la última versión conocida.", - "OpenOCD is not running, do you want to launch it?": "OpenOCD no se está ejecutando, ¿desea iniciarlo?", "Cancel": "Cancelar", - "Invalid OpenOCD bin path or access is denied for the user": "Ruta del binario de OpenOCD inválida o acceso denegado para el usuario", - "OPENOCD_SCRIPTS environment variable is missing. Please set it in idf.customExtraVars or in your system environment variables.": "Falta la variable de entorno OPENOCD_SCRIPTS. Establézcala en idf.customExtraVars o en las variables de entorno del sistema.", - "Invalid OpenOCD Config files. Check idf.openOcdConfigs configuration value.": "Archivos de configuración de OpenOCD inválidos. Verifique el valor de configuración idf.openOcdConfigs.", - "For assistance with OpenOCD errors, please refer to our Troubleshooting FAQ: {0}": "Para obtener ayuda con errores de OpenOCD, consulte nuestras Preguntas Frecuentes de Solución de Problemas: {0}", "OpenOCD Exit with non-zero error code {0}": "OpenOCD salió con un código de error distinto de cero {0}", - "OpenOCD Server (Running)": "Servidor OpenOCD (En ejecución)", - "OpenOCD Server (Stopped)": "Servidor OpenOCD (Detenido)", - "[Stopped] : OpenOCD Server": "[Detenido] : Servidor OpenOCD", - "ESP-IDF board not selected. Remember to set the configuration files for OpenOCD with idf.openOcdConfigs": "No se seleccionó una placa ESP-IDF. Recuerde establecer los archivos de configuración de OpenOCD con idf.openOcdConfigs", - "ESP-IDF: Setting device target...": "ESP-IDF: Estableciendo objetivo del dispositivo...", - "Devkit detection script not available. A default list of targets will be displayed instead.": "El script de detección de devkits no está disponible. Se mostrará una lista predeterminada de objetivos.", - "No connected boards detected or error running DevkitsCommand: ": "No se detectaron placas conectadas o error al ejecutar DevkitsCommand: ", - "Preview target": "Objetivo de vista previa", - "Default Boards": "Placas predeterminadas", - "Status: CONNECTED": "Estado: CONECTADO", - "Location: {0}": "Ubicación: {0}", "Loaded {0} project configuration(s) from {1}: {2}. No configuration selected.": "Se cargaron {0} configuraciones de proyecto desde {1}: {2}. Ninguna configuración seleccionada.", "Loaded {0} project configuration(s) from {1}: {2}": "Se cargaron {0} configuraciones de proyecto desde {1}: {2}", "Failed to parse project configuration files": "Error al analizar los archivos de configuración del proyecto", diff --git a/l10n/bundle.l10n.pt.json b/l10n/bundle.l10n.pt.json index c801ed209..b66ba1afd 100644 --- a/l10n/bundle.l10n.pt.json +++ b/l10n/bundle.l10n.pt.json @@ -259,26 +259,8 @@ "Clear OpenOCD Error Hints": "Limpar Dicas de Erro do OpenOCD", "Open Reference": "Abrir Referência", "Launch Debug": "Iniciar depuração", - "Failed to save unsaved files, ignoring and continuing with the build": "Falha ao salvar arquivos não salvos; continuando com a compilação", - "Could not determine ESP-IDF version. Using default compiler arguments for the latest known version.": "Não foi possível determinar a versão do ESP-IDF. Usando argumentos padrão do compilador para a versão mais recente conhecida.", - "OpenOCD is not running, do you want to launch it?": "OpenOCD não está em execução, deseja iniciá-lo?", "Cancel": "Cancelar", - "Invalid OpenOCD bin path or access is denied for the user": "Caminho do binário do OpenOCD inválido ou acesso negado ao usuário", - "OPENOCD_SCRIPTS environment variable is missing. Please set it in idf.customExtraVars or in your system environment variables.": "A variável de ambiente OPENOCD_SCRIPTS está ausente. Defina-a em idf.customExtraVars ou nas variáveis de ambiente do sistema.", - "Invalid OpenOCD Config files. Check idf.openOcdConfigs configuration value.": "Arquivos de configuração do OpenOCD inválidos. Verifique o valor de configuração idf.openOcdConfigs.", - "For assistance with OpenOCD errors, please refer to our Troubleshooting FAQ: {0}": "Para obter ajuda com erros do OpenOCD, consulte nosso FAQ de Solução de Problemas: {0}", "OpenOCD Exit with non-zero error code {0}": "OpenOCD saiu com código de erro diferente de zero {0}", - "OpenOCD Server (Running)": "Servidor OpenOCD (Em execução)", - "OpenOCD Server (Stopped)": "Servidor OpenOCD (Parado)", - "[Stopped] : OpenOCD Server": "[Parado] : Servidor OpenOCD", - "ESP-IDF board not selected. Remember to set the configuration files for OpenOCD with idf.openOcdConfigs": "Placa ESP-IDF não selecionada. Lembre-se de definir os arquivos de configuração do OpenOCD com idf.openOcdConfigs", - "ESP-IDF: Setting device target...": "ESP-IDF: Definindo alvo do dispositivo...", - "Devkit detection script not available. A default list of targets will be displayed instead.": "Script de detecção de devkits indisponível. Uma lista padrão de alvos será exibida.", - "No connected boards detected or error running DevkitsCommand: ": "Nenhuma placa conectada detectada ou erro ao executar DevkitsCommand: ", - "Preview target": "Alvo de visualização", - "Default Boards": "Placas padrão", - "Status: CONNECTED": "Status: CONECTADO", - "Location: {0}": "Localização: {0}", "Loaded {0} project configuration(s) from {1}: {2}. No configuration selected.": "{0} configuração(ões) de projeto carregada(s) de {1}: {2}. Nenhuma configuração selecionada.", "Loaded {0} project configuration(s) from {1}: {2}": "{0} configuração(ões) de projeto carregada(s) de {1}: {2}", "Failed to parse project configuration files": "Falha ao analisar os arquivos de configuração do projeto", diff --git a/l10n/bundle.l10n.ru.json b/l10n/bundle.l10n.ru.json index 9abc363ee..da3f88ca8 100644 --- a/l10n/bundle.l10n.ru.json +++ b/l10n/bundle.l10n.ru.json @@ -259,26 +259,8 @@ "Clear OpenOCD Error Hints": "Очистить подсказки по ошибкам OpenOCD", "Open Reference": "Открыть ссылку", "Launch Debug": "Запуск отладки", - "Failed to save unsaved files, ignoring and continuing with the build": "Не удалось сохранить несохраненные файлы; продолжаем сборку", - "Could not determine ESP-IDF version. Using default compiler arguments for the latest known version.": "Не удалось определить версию ESP-IDF. Используются стандартные аргументы компилятора для последней известной версии.", - "OpenOCD is not running, do you want to launch it?": "OpenOCD не запущен, хотите запустить его?", "Cancel": "Отмена", - "Invalid OpenOCD bin path or access is denied for the user": "Неверный путь к бинарному файлу OpenOCD или доступ запрещен", - "OPENOCD_SCRIPTS environment variable is missing. Please set it in idf.customExtraVars or in your system environment variables.": "Отсутствует переменная окружения OPENOCD_SCRIPTS. Установите её в idf.customExtraVars или в системных переменных окружения.", - "Invalid OpenOCD Config files. Check idf.openOcdConfigs configuration value.": "Неверные файлы конфигурации OpenOCD. Проверьте параметр конфигурации idf.openOcdConfigs.", - "For assistance with OpenOCD errors, please refer to our Troubleshooting FAQ: {0}": "Для помощи с ошибками OpenOCD обратитесь к нашему разделу Часто задаваемых вопросов по устранению неполадок: {0}", "OpenOCD Exit with non-zero error code {0}": "OpenOCD завершился с ненулевым кодом ошибки {0}", - "OpenOCD Server (Running)": "Сервер OpenOCD (Запущен)", - "OpenOCD Server (Stopped)": "Сервер OpenOCD (Остановлен)", - "[Stopped] : OpenOCD Server": "[Остановлен] : Сервер OpenOCD", - "ESP-IDF board not selected. Remember to set the configuration files for OpenOCD with idf.openOcdConfigs": "Плата ESP-IDF не выбрана. Не забудьте установить файлы конфигурации OpenOCD с помощью параметра idf.openOcdConfigs", - "ESP-IDF: Setting device target...": "ESP-IDF: Установка целевого устройства...", - "Devkit detection script not available. A default list of targets will be displayed instead.": "Скрипт обнаружения devkit недоступен. Вместо этого будет показан список целей по умолчанию.", - "No connected boards detected or error running DevkitsCommand: ": "Не обнаружены подключенные платы или ошибка при выполнении DevkitsCommand: ", - "Preview target": "Цель (предпросмотр)", - "Default Boards": "Платы по умолчанию", - "Status: CONNECTED": "Статус: ПОДКЛЮЧЕНО", - "Location: {0}": "Местоположение: {0}", "Loaded {0} project configuration(s) from {1}: {2}. No configuration selected.": "Загружено {0} конфигураций проекта из {1}: {2}. Конфигурация не выбрана.", "Loaded {0} project configuration(s) from {1}: {2}": "Загружено {0} конфигураций проекта из {1}: {2}", "Failed to parse project configuration files": "Не удалось проанализировать файлы конфигурации проекта", diff --git a/l10n/bundle.l10n.zh-CN.json b/l10n/bundle.l10n.zh-CN.json index dbb0427a3..2994e4afd 100644 --- a/l10n/bundle.l10n.zh-CN.json +++ b/l10n/bundle.l10n.zh-CN.json @@ -259,26 +259,8 @@ "Clear OpenOCD Error Hints": "清除 OpenOCD 错误提示", "Open Reference": "打开参考", "Launch Debug": "启动调试", - "Failed to save unsaved files, ignoring and continuing with the build": "无法保存未保存的文件;将继续进行构建", - "Could not determine ESP-IDF version. Using default compiler arguments for the latest known version.": "无法确定 ESP-IDF 版本。将使用最新已知版本的默认编译参数。", - "OpenOCD is not running, do you want to launch it?": "OpenOCD 未运行,是否启动?", "Cancel": "取消", - "Invalid OpenOCD bin path or access is denied for the user": "OpenOCD 二进制路径无效或用户无权限", - "OPENOCD_SCRIPTS environment variable is missing. Please set it in idf.customExtraVars or in your system environment variables.": "缺少 OPENOCD_SCRIPTS 环境变量。请在 idf.customExtraVars 或系统环境变量中设置。", - "Invalid OpenOCD Config files. Check idf.openOcdConfigs configuration value.": "OpenOCD 配置文件无效。请检查 idf.openOcdConfigs 配置值。", - "For assistance with OpenOCD errors, please refer to our Troubleshooting FAQ: {0}": "如需帮助解决 OpenOCD 错误,请参考常见问题排查文档:{0}", "OpenOCD Exit with non-zero error code {0}": "OpenOCD 以非零错误代码退出:{0}", - "OpenOCD Server (Running)": "OpenOCD 服务器(运行中)", - "OpenOCD Server (Stopped)": "OpenOCD 服务器(已停止)", - "[Stopped] : OpenOCD Server": "[已停止]:OpenOCD 服务器", - "ESP-IDF board not selected. Remember to set the configuration files for OpenOCD with idf.openOcdConfigs": "未选择 ESP-IDF 开发板。请记得通过 idf.openOcdConfigs 设置 OpenOCD 配置文件", - "ESP-IDF: Setting device target...": "ESP-IDF:正在设置设备目标...", - "Devkit detection script not available. A default list of targets will be displayed instead.": "Devkit 检测脚本不可用。将显示默认的目标列表。", - "No connected boards detected or error running DevkitsCommand: ": "未检测到已连接的开发板或执行 DevkitsCommand 时出错:", - "Preview target": "预览目标", - "Default Boards": "默认开发板", - "Status: CONNECTED": "状态:已连接", - "Location: {0}": "位置:{0}", "Loaded {0} project configuration(s) from {1}: {2}. No configuration selected.": "已从 {1} 加载 {0} 个项目配置:{2}。未选择配置。", "Loaded {0} project configuration(s) from {1}: {2}": "已从 {1} 加载 {0} 个项目配置:{2}", "Failed to parse project configuration files": "解析项目配置文件失败", diff --git a/src/build/buildTask.ts b/src/build/buildTask.ts index 8b2314d26..d76b013ca 100644 --- a/src/build/buildTask.ts +++ b/src/build/buildTask.ts @@ -69,9 +69,8 @@ export class BuildTask { try { await this.saveBeforeBuild(); } catch (error) { - const errorMessage = vscode.l10n.t( - "Failed to save unsaved files, ignoring and continuing with the build" - ); + const errorMessage = + "Failed to save unsaved files, ignoring and continuing with the build"; Logger.error(errorMessage, error, "build saveBeforeBuild"); Logger.warnNotify(errorMessage); } @@ -79,7 +78,6 @@ export class BuildTask { throw new Error("ALREADY_BUILDING"); } this.building(true); - await ensureDir(this.buildDirPath); const modifiedEnv = await appendIdfAndToolsToPath(this.currentWorkspace); const processOptions = { @@ -121,9 +119,7 @@ export class BuildTask { let defaultCompilerArgs; if (espIdfVersion === "x.x") { Logger.warn( - vscode.l10n.t( "Could not determine ESP-IDF version. Using default compiler arguments for the latest known version." - ) ); defaultCompilerArgs = [ "-G", diff --git a/src/espIdf/openOcd/boardConfiguration.ts b/src/espIdf/openOcd/boardConfiguration.ts index 4e87ff348..383e60868 100644 --- a/src/espIdf/openOcd/boardConfiguration.ts +++ b/src/espIdf/openOcd/boardConfiguration.ts @@ -182,9 +182,7 @@ export async function selectOpenOcdConfigFiles( const selectedBoard = boardQuickPick.selectedItems[0]; if (!selectedBoard) { Logger.infoNotify( - l10n.t( - "ESP-IDF board not selected. Remember to set the configuration files for OpenOCD with idf.openOcdConfigs" - ) + `ESP-IDF board not selected. Remember to set the configuration files for OpenOCD with idf.openOcdConfigs` ); } else if (selectedBoard && selectedBoard.target) { if (selectedBoard.label.indexOf("Custom board") !== -1) { diff --git a/src/espIdf/openOcd/openOcdManager.ts b/src/espIdf/openOcd/openOcdManager.ts index 0e8e29238..61fd68d54 100644 --- a/src/espIdf/openOcd/openOcdManager.ts +++ b/src/espIdf/openOcd/openOcdManager.ts @@ -53,7 +53,6 @@ export class OpenOCDManager extends EventEmitter { private statusBar: vscode.StatusBarItem; private workspace: vscode.Uri; private encounteredErrors: boolean = false; - private versionPromise: Promise | null = null; // coalesce concurrent lookups only private constructor() { super(); @@ -61,12 +60,6 @@ export class OpenOCDManager extends EventEmitter { } public async version(): Promise { - // Coalesce concurrent calls; do not cache long-term to respect version changes - if (this.versionPromise) { - return this.versionPromise; - } - - this.versionPromise = (async () => { const modifiedEnv = await appendIdfAndToolsToPath(this.workspace); if (!isBinInPath("openocd", modifiedEnv)) { return ""; @@ -74,21 +67,13 @@ export class OpenOCDManager extends EventEmitter { const resp = await sspawn("openocd", ["--version"], { cwd: this.workspace.fsPath, env: modifiedEnv, - silent: true, - appendMode: "append", }); const versionString = resp.toString(); const match = versionString.match(/v\d+\.\d+\.\d+\-\S*/gi); - return match ? match[0].replace("-dirty", "") : "failed+to+match+version"; - })(); - const p = this.versionPromise; - try { - return await this.versionPromise; - } finally { - if (this.versionPromise === p) { - this.versionPromise = null; - } + if (!match) { + return "failed+to+match+version"; } + return match[0].replace("-dirty", ""); } public statusBarItem(): vscode.StatusBarItem { @@ -153,12 +138,12 @@ export class OpenOCDManager extends EventEmitter { const tclClient = new TCLClient(tclConnectionParams); if (!(await tclClient.isOpenOCDServerRunning())) { const resp = await vscode.window.showInformationMessage( - vscode.l10n.t("OpenOCD is not running, do you want to launch it?"), + "OpenOCD is not running, do you want to launch it?", { modal: true }, - { title: vscode.l10n.t("Yes") }, - { title: vscode.l10n.t("Cancel"), isCloseAffordance: true } + { title: "Yes" }, + { title: "Cancel", isCloseAffordance: true } ); - if (resp && resp.title === vscode.l10n.t("Yes")) { + if (resp && resp.title === "Yes") { await OpenOCDManager.init().start(); return await tclClient.isOpenOCDServerRunning(); } @@ -174,16 +159,12 @@ export class OpenOCDManager extends EventEmitter { const modifiedEnv = await appendIdfAndToolsToPath(this.workspace); if (!isBinInPath("openocd", modifiedEnv)) { throw new Error( - vscode.l10n.t( "Invalid OpenOCD bin path or access is denied for the user" - ) ); } if (typeof modifiedEnv.OPENOCD_SCRIPTS === "undefined") { throw new Error( - vscode.l10n.t( "OPENOCD_SCRIPTS environment variable is missing. Please set it in idf.customExtraVars or in your system environment variables." - ) ); } @@ -206,9 +187,7 @@ export class OpenOCDManager extends EventEmitter { openOcdConfigFilesList.length < 1 ) { throw new Error( - vscode.l10n.t( "Invalid OpenOCD Config files. Check idf.openOcdConfigs configuration value." - ) ); } @@ -266,10 +245,7 @@ export class OpenOCDManager extends EventEmitter { this.server.on("close", (code: number, signal: string) => { if (this.encounteredErrors) { OutputChannel.appendLine( - vscode.l10n.t( - "For assistance with OpenOCD errors, please refer to our Troubleshooting FAQ: {0}", - ESP.URL.OpenOcdTroubleshootingFaq - ), + `For assistance with OpenOCD errors, please refer to our Troubleshooting FAQ: ${ESP.URL.OpenOcdTroubleshootingFaq}`, "OpenOCD" ); } @@ -287,7 +263,7 @@ export class OpenOCDManager extends EventEmitter { } this.stop(); }); - this.updateStatusText(`❇️ ${vscode.l10n.t("OpenOCD Server (Running)")}`); + this.updateStatusText("❇️ OpenOCD Server (Running)"); OutputChannel.show(); } @@ -295,8 +271,8 @@ export class OpenOCDManager extends EventEmitter { if (this.server && !this.server.killed) { this.server.kill("SIGKILL"); this.server = undefined; - this.updateStatusText(`❌ ${vscode.l10n.t("OpenOCD Server (Stopped)")}`); - const endMsg = `${vscode.l10n.t("[Stopped] : OpenOCD Server")}`; + this.updateStatusText("❌ OpenOCD Server (Stopped)"); + const endMsg = "[Stopped] : OpenOCD Server"; OutputChannel.appendLine(endMsg, "OpenOCD"); Logger.info(endMsg); } @@ -312,7 +288,7 @@ export class OpenOCDManager extends EventEmitter { vscode.StatusBarAlignment.Left, 1 ); - this.statusBar.name = this.statusBar.text = vscode.l10n.t("OpenOCD Server"); + this.statusBar.name = this.statusBar.text = "OpenOCD Server"; const commandDictionary = createCommandDictionary(); this.statusBar.tooltip = commandDictionary[CommandKeys.OpenOCD].tooltip; this.statusBar.command = CommandKeys.OpenOCD; @@ -330,7 +306,7 @@ export class OpenOCDManager extends EventEmitter { if (PreCheck.isWorkspaceFolderOpen()) { this.workspace = vscode.workspace.workspaceFolders[0].uri; } - this.chan = Buffer.alloc(0);; + this.chan = Buffer.alloc(0); OutputChannel.init(); if (vscode.env.uiKind !== vscode.UIKind.Web) { this.registerOpenOCDStatusBarItem(); diff --git a/src/espIdf/setTarget/index.ts b/src/espIdf/setTarget/index.ts index bd17d153e..d27738eb1 100644 --- a/src/espIdf/setTarget/index.ts +++ b/src/espIdf/setTarget/index.ts @@ -84,7 +84,7 @@ export async function setIdfTarget( { cancellable: false, location: progressLocation, - title: l10n.t("ESP-IDF: Setting device target..."), + title: "ESP-IDF: Setting device target...", }, async (progress: Progress<{ message: string; increment: number }>) => { try { @@ -111,11 +111,9 @@ export async function setIdfTarget( (t) => t.target === b.target ), description: b.description, - detail: - l10n.t("Status: CONNECTED") + - (b.location - ? ` ${l10n.t("Location: {0}", b.location)}` - : ""), + detail: `Status: CONNECTED${ + b.location ? ` Location: ${b.location}` : "" + }`, isConnected: true, boardInfo: b, } as ISetTargetQuickPickItems) @@ -124,16 +122,13 @@ export async function setIdfTarget( } } else { Logger.info( - l10n.t( "Devkit detection script not available. A default list of targets will be displayed instead." - ) ); } } catch (e) { Logger.info( - l10n.t( - "No connected boards detected or error running DevkitsCommand: " - ) + (e && e.message ? e.message : e) + "No connected boards detected or error running DevkitsCommand: " + + (e && e.message ? e.message : e) ); } } else { @@ -148,7 +143,7 @@ export async function setIdfTarget( (t) => ({ label: t.label, idfTarget: t, - description: t.isPreview ? l10n.t("Preview target") : undefined, + description: t.isPreview ? "Preview target" : undefined, isConnected: false, }) ); @@ -157,10 +152,7 @@ export async function setIdfTarget( connectedBoards.length > 0 ? [ ...connectedBoards, - { - kind: QuickPickItemKind.Separator, - label: l10n.t("Default Boards"), - }, + { kind: QuickPickItemKind.Separator, label: "Default Boards" }, ...defaultBoards, ] : defaultBoards; From b9dbe926ccfa55939cc434533c0978f509427d8d Mon Sep 17 00:00:00 2001 From: radurentea Date: Thu, 30 Oct 2025 15:46:22 +0800 Subject: [PATCH 18/21] clean: remove unnecessary fn --- src/project-conf/index.ts | 62 --------------------------------------- 1 file changed, 62 deletions(-) diff --git a/src/project-conf/index.ts b/src/project-conf/index.ts index 621a48035..1154fd8b8 100644 --- a/src/project-conf/index.ts +++ b/src/project-conf/index.ts @@ -941,68 +941,6 @@ function mergePresets( return merged; } -/** - * Processes a single configuration file (CMakePresets.json or CMakeUserPresets.json). - * This function loads and processes all configure presets from a single file, - * applying variable substitution and path resolution directly without inheritance processing. - * - * Note: This function does NOT handle preset inheritance. For proper inheritance handling, - * use the two-pass approach with loadRawConfigurationFile and resolvePresetInheritance. - * - * @param configJson The parsed JSON content of the configuration file - * @param workspaceFolder The workspace folder Uri for variable substitution and path resolution - * @param resolvePaths Whether to resolve relative paths to absolute paths - * @param fileName The name of the file being processed (used in error messages) - * @returns An object mapping configuration names to their processed ConfigurePreset - * @deprecated Use loadRawConfigurationFile and resolvePresetInheritance instead for proper inheritance - */ -async function processConfigurationFile( - configJson: any, - workspaceFolder: Uri, - resolvePaths: boolean, - fileName: string -): Promise<{ [key: string]: ConfigurePreset }> { - const projectConfElements: { [key: string]: ConfigurePreset } = {}; - - // Only support CMakePresets format - if (configJson.version !== undefined && configJson.configurePresets) { - const cmakePresets = configJson as CMakePresets; - - if ( - !cmakePresets.configurePresets || - cmakePresets.configurePresets.length === 0 - ) { - return {}; - } - - // Process each configure preset - for (const preset of cmakePresets.configurePresets) { - try { - // Apply variable substitution and path resolution directly to ConfigurePreset - const processedPreset = await processConfigurePresetVariables( - preset, - workspaceFolder, - resolvePaths - ); - - projectConfElements[preset.name] = processedPreset; - } catch (error) { - Logger.warn( - `Failed to process configure preset "${preset.name}" from ${fileName}: ${error.message}`, - error - ); - } - } - } else { - // This might be a legacy file that wasn't migrated - Logger.warn( - `Invalid ${fileName} format. Please ensure the file follows the CMakePresets specification.` - ); - } - - return projectConfElements; -} - /** * Checks for the legacy project configuration file (esp_idf_project_configuration.json) * and prompts the user to migrate it to the new CMakePresets.json format if found. From 07d6092346dea555bb1b44bf4265244f5f2ebe93 Mon Sep 17 00:00:00 2001 From: radurentea Date: Thu, 30 Oct 2025 15:51:04 +0800 Subject: [PATCH 19/21] refactor: use const, fix schemaVersion - created const for CMAKE_PRESET_VERSION & CMAKE_PRESET_SCHEMA_VERSION - Added default schemaVersion - Use openOcdDebugLevel setting as default or value 2 --- src/project-conf/index.ts | 24 ++++++++++++++++-------- src/project-conf/projectConfiguration.ts | 1 + 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/project-conf/index.ts b/src/project-conf/index.ts index 1154fd8b8..a6ee28165 100644 --- a/src/project-conf/index.ts +++ b/src/project-conf/index.ts @@ -29,9 +29,11 @@ import { ESPIDFVendorSettings, } from "./projectConfiguration"; import { Logger } from "../logger/logger"; -import { resolveVariables } from "../idfConfiguration"; +import { resolveVariables, readParameter } from "../idfConfiguration"; const ESP_IDF_VENDOR_KEY = "espressif/vscode-esp-idf"; +const CMAKE_PRESET_VERSION = 3; +const CMAKE_PRESET_SCHEMA_VERSION = 1; export class ProjectConfigStore { private static self: ProjectConfigStore; @@ -100,10 +102,14 @@ export async function updateCurrentProfileOpenOcdConfigs( ); // Add new openOCD setting + const openOcdDebugLevel = readParameter( + "idf.openOcdDebugLevel", + this.workspace + ) as string; config.vendor[ESP_IDF_VENDOR_KEY].settings.push({ type: "openOCD", value: { - debugLevel: 2, + debugLevel: openOcdDebugLevel || 2, configs: configs, args: [] } @@ -297,13 +303,13 @@ async function saveConfigurationToUserPresets( } catch (error) { Logger.error(`Error reading user presets file: ${error.message}`, error, "saveConfigurationToUserPresets"); userPresets = { - version: 3, + version: CMAKE_PRESET_VERSION, configurePresets: [] }; } } else { userPresets = { - version: 3, + version: CMAKE_PRESET_VERSION, configurePresets: [] }; } @@ -357,13 +363,13 @@ async function saveConfigurationToProjectPresets( } catch (error) { Logger.error(`Error reading project presets file: ${error.message}`, error, "saveConfigurationToProjectPresets"); projectPresets = { - version: 3, + version: CMAKE_PRESET_VERSION, configurePresets: [] }; } } else { projectPresets = { - version: 3, + version: CMAKE_PRESET_VERSION, configurePresets: [] }; } @@ -410,7 +416,7 @@ export async function saveProjectConfFile( ); const cmakePresets: CMakePresets = { - version: 3, + version: CMAKE_PRESET_VERSION, cmakeMinimumRequired: { major: 3, minor: 23, patch: 0 }, configurePresets, }; @@ -445,7 +451,7 @@ export async function saveProjectConfFileLegacy( ); const cmakePresets: CMakePresets = { - version: 1, + version: CMAKE_PRESET_VERSION, cmakeMinimumRequired: { major: 3, minor: 23, patch: 0 }, configurePresets, }; @@ -1370,6 +1376,7 @@ async function processConfigurePresetVendor( const processedVendor: ESPIDFVendorSettings = { [ESP_IDF_VENDOR_KEY]: { settings: [], + schemaVersion: 1 }, }; @@ -1812,6 +1819,7 @@ function convertProjectConfElementToConfigurePreset( vendor: { [ESP_IDF_VENDOR_KEY]: { settings, + schemaVersion: CMAKE_PRESET_SCHEMA_VERSION }, }, }; diff --git a/src/project-conf/projectConfiguration.ts b/src/project-conf/projectConfiguration.ts index 486aa737a..a76a373df 100644 --- a/src/project-conf/projectConfiguration.ts +++ b/src/project-conf/projectConfiguration.ts @@ -63,6 +63,7 @@ export interface ESPIDFSettings { export interface ESPIDFVendorSettings { "espressif/vscode-esp-idf": { settings: ESPIDFSettings[]; + schemaVersion?: number }; } From 05fcfca1a71f9d07f0627f9db9876db8279987c8 Mon Sep 17 00:00:00 2001 From: Radu Date: Thu, 6 Nov 2025 10:38:12 +0200 Subject: [PATCH 20/21] fix: race condition --- src/project-conf/ProjectConfigurationManager.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/project-conf/ProjectConfigurationManager.ts b/src/project-conf/ProjectConfigurationManager.ts index e22215e96..ec7c7a144 100644 --- a/src/project-conf/ProjectConfigurationManager.ts +++ b/src/project-conf/ProjectConfigurationManager.ts @@ -54,6 +54,7 @@ export class ProjectConfigurationManager { private workspaceUri: Uri; private context: ExtensionContext; private commandDictionary: any; + private initPromise: Promise; constructor( workspaceUri: Uri, @@ -91,8 +92,8 @@ export class ProjectConfigurationManager { ); this.registerEventHandlers(); - // Initialize asynchronously - this.initialize(); + // Initialize asynchronously and store the promise to prevent race conditions + this.initPromise = this.initialize(); } private async initialize(): Promise { @@ -224,6 +225,9 @@ export class ProjectConfigurationManager { } private async handleConfigFileChange(): Promise { + // Wait for initialization to complete before processing file changes + await this.initPromise; + try { // Use the updated getProjectConfigurationElements function that handles both files const projectConfElements = await getProjectConfigurationElements( @@ -292,6 +296,9 @@ export class ProjectConfigurationManager { } private async handleConfigFileDelete(): Promise { + // Wait for initialization to complete before processing file deletion + await this.initPromise; + // When the config file is deleted, clear all configurations this.configVersions = []; @@ -318,6 +325,9 @@ export class ProjectConfigurationManager { } private async handleConfigFileCreate(): Promise { + // Wait for initialization to complete before processing file creation + await this.initPromise; + try { // Use the updated getProjectConfigurationElements function that handles both files const projectConfElements = await getProjectConfigurationElements( @@ -445,6 +455,9 @@ export class ProjectConfigurationManager { * Method to select a project configuration via command */ public async selectProjectConfiguration(): Promise { + // Wait for initialization to complete before allowing configuration selection + await this.initPromise; + try { const projectConfigurations = await getProjectConfigurationElements( this.workspaceUri, From 32a849118590aabd257e782fcd1c836da5dd3075 Mon Sep 17 00:00:00 2001 From: Radu Date: Tue, 11 Nov 2025 09:44:26 +0200 Subject: [PATCH 21/21] refactor: moving consts --- src/config.ts | 5 ++++ src/project-conf/index.ts | 49 ++++++++++++++++++--------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/config.ts b/src/config.ts index d62a4187e..f2d959167 100644 --- a/src/config.ts +++ b/src/config.ts @@ -147,4 +147,9 @@ export namespace ESP { export const Title = "ESP Component Registry"; } } + export namespace CMakePresets { + export const ESP_IDF_VENDOR_KEY = "espressif/vscode-esp-idf"; + export const CMAKE_PRESET_VERSION = 3; + export const CMAKE_PRESET_SCHEMA_VERSION = 1; + } } diff --git a/src/project-conf/index.ts b/src/project-conf/index.ts index a6ee28165..fb081dc5e 100644 --- a/src/project-conf/index.ts +++ b/src/project-conf/index.ts @@ -24,17 +24,12 @@ import { ProjectConfElement, CMakePresets, ConfigurePreset, - BuildPreset, ESPIDFSettings, ESPIDFVendorSettings, } from "./projectConfiguration"; import { Logger } from "../logger/logger"; import { resolveVariables, readParameter } from "../idfConfiguration"; -const ESP_IDF_VENDOR_KEY = "espressif/vscode-esp-idf"; -const CMAKE_PRESET_VERSION = 3; -const CMAKE_PRESET_SCHEMA_VERSION = 1; - export class ProjectConfigStore { private static self: ProjectConfigStore; private ctx: ExtensionContext; @@ -89,15 +84,15 @@ export async function updateCurrentProfileOpenOcdConfigs( await updateCurrentProjectConfiguration(workspaceFolder, (config) => { // Update OpenOCD configs in vendor settings if (!config.vendor) { - config.vendor = { [ESP_IDF_VENDOR_KEY]: { settings: [] } }; + config.vendor = { [ESP.CMakePresets.ESP_IDF_VENDOR_KEY]: { settings: [] } }; } - if (!config.vendor[ESP_IDF_VENDOR_KEY]) { - config.vendor[ESP_IDF_VENDOR_KEY] = { settings: [] }; + if (!config.vendor[ESP.CMakePresets.ESP_IDF_VENDOR_KEY]) { + config.vendor[ESP.CMakePresets.ESP_IDF_VENDOR_KEY] = { settings: [] }; } // Remove existing openOCD setting - config.vendor[ESP_IDF_VENDOR_KEY].settings = - config.vendor[ESP_IDF_VENDOR_KEY].settings.filter( + config.vendor[ESP.CMakePresets.ESP_IDF_VENDOR_KEY].settings = + config.vendor[ESP.CMakePresets.ESP_IDF_VENDOR_KEY].settings.filter( (setting) => setting.type !== "openOCD" ); @@ -106,7 +101,7 @@ export async function updateCurrentProfileOpenOcdConfigs( "idf.openOcdDebugLevel", this.workspace ) as string; - config.vendor[ESP_IDF_VENDOR_KEY].settings.push({ + config.vendor[ESP.CMakePresets.ESP_IDF_VENDOR_KEY].settings.push({ type: "openOCD", value: { debugLevel: openOcdDebugLevel || 2, @@ -303,13 +298,13 @@ async function saveConfigurationToUserPresets( } catch (error) { Logger.error(`Error reading user presets file: ${error.message}`, error, "saveConfigurationToUserPresets"); userPresets = { - version: CMAKE_PRESET_VERSION, + version: ESP.CMakePresets.CMAKE_PRESET_VERSION, configurePresets: [] }; } } else { userPresets = { - version: CMAKE_PRESET_VERSION, + version: ESP.CMakePresets.CMAKE_PRESET_VERSION, configurePresets: [] }; } @@ -363,13 +358,13 @@ async function saveConfigurationToProjectPresets( } catch (error) { Logger.error(`Error reading project presets file: ${error.message}`, error, "saveConfigurationToProjectPresets"); projectPresets = { - version: CMAKE_PRESET_VERSION, + version: ESP.CMakePresets.CMAKE_PRESET_VERSION, configurePresets: [] }; } } else { projectPresets = { - version: CMAKE_PRESET_VERSION, + version: ESP.CMakePresets.CMAKE_PRESET_VERSION, configurePresets: [] }; } @@ -416,7 +411,7 @@ export async function saveProjectConfFile( ); const cmakePresets: CMakePresets = { - version: CMAKE_PRESET_VERSION, + version: ESP.CMakePresets.CMAKE_PRESET_VERSION, cmakeMinimumRequired: { major: 3, minor: 23, patch: 0 }, configurePresets, }; @@ -451,7 +446,7 @@ export async function saveProjectConfFileLegacy( ); const cmakePresets: CMakePresets = { - version: CMAKE_PRESET_VERSION, + version: ESP.CMakePresets.CMAKE_PRESET_VERSION, cmakeMinimumRequired: { major: 3, minor: 23, patch: 0 }, configurePresets, }; @@ -935,10 +930,10 @@ function mergePresets( // Merge vendor settings (child overrides parent) if (child.vendor || parent.vendor) { merged.vendor = { - [ESP_IDF_VENDOR_KEY]: { + [ESP.CMakePresets.ESP_IDF_VENDOR_KEY]: { settings: [ - ...(parent.vendor?.[ESP_IDF_VENDOR_KEY]?.settings || []), - ...(child.vendor?.[ESP_IDF_VENDOR_KEY]?.settings || []), + ...(parent.vendor?.[ESP.CMakePresets.ESP_IDF_VENDOR_KEY]?.settings || []), + ...(child.vendor?.[ESP.CMakePresets.ESP_IDF_VENDOR_KEY]?.settings || []), ], }, }; @@ -1374,13 +1369,13 @@ async function processConfigurePresetVendor( resolvePaths: boolean ): Promise { const processedVendor: ESPIDFVendorSettings = { - [ESP_IDF_VENDOR_KEY]: { + [ESP.CMakePresets.ESP_IDF_VENDOR_KEY]: { settings: [], schemaVersion: 1 }, }; - const espIdfSettings = vendor[ESP_IDF_VENDOR_KEY]?.settings || []; + const espIdfSettings = vendor[ESP.CMakePresets.ESP_IDF_VENDOR_KEY]?.settings || []; for (const setting of espIdfSettings) { const processedSetting: ESPIDFSettings = { ...setting }; @@ -1414,7 +1409,7 @@ async function processConfigurePresetVendor( ); } - processedVendor[ESP_IDF_VENDOR_KEY].settings.push(processedSetting); + processedVendor[ESP.CMakePresets.ESP_IDF_VENDOR_KEY].settings.push(processedSetting); } return processedVendor; @@ -1653,7 +1648,7 @@ function getESPIDFSettingValue( settingType: string ): any { const espIdfSettings = - preset.vendor?.[ESP_IDF_VENDOR_KEY]?.settings || []; + preset.vendor?.[ESP.CMakePresets.ESP_IDF_VENDOR_KEY]?.settings || []; const setting = espIdfSettings.find((s) => s.type === settingType); return setting ? setting.value : undefined; } @@ -1708,7 +1703,7 @@ function convertConfigurePresetToProjectConfElement( ): ProjectConfElement { // Extract ESP-IDF specific settings from vendor section const espIdfSettings = - preset.vendor?.[ESP_IDF_VENDOR_KEY]?.settings || []; + preset.vendor?.[ESP.CMakePresets.ESP_IDF_VENDOR_KEY]?.settings || []; // Helper function to find setting by type const findSetting = (type: string): any => { @@ -1817,9 +1812,9 @@ function convertProjectConfElementToConfigurePreset( }, environment: Object.keys(element.env).length > 0 ? element.env : undefined, vendor: { - [ESP_IDF_VENDOR_KEY]: { + [ESP.CMakePresets.ESP_IDF_VENDOR_KEY]: { settings, - schemaVersion: CMAKE_PRESET_SCHEMA_VERSION + schemaVersion: ESP.CMakePresets.CMAKE_PRESET_SCHEMA_VERSION }, }, };