diff --git a/CHANGELOG.md b/CHANGELOG.md index ee1de8dcd..ac5502946 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,59 @@ All notable changes to the "Espressif IDF" extension will be documented in this file. +## [1.11.0](https://github.com/espressif/vscode-esp-idf-extension/releases/tag/v1.11.0) + +## Features and enhancements + +- [Add DevKits support](https://github.com/espressif/vscode-esp-idf-extension/pull/1557) +- [Add gitignore on project creation](https://github.com/espressif/vscode-esp-idf-extension/pull/1578) +- [Pre-Release branch docs](https://github.com/espressif/vscode-esp-idf-extension/pull/1599) +- [Modify event activation for file types](https://github.com/espressif/vscode-esp-idf-extension/pull/1568) +- [Add classic menuconfig in Editor Panel](https://github.com/espressif/vscode-esp-idf-extension/pull/1598) +- [Update webviews to VS Code UI Style](https://github.com/espressif/vscode-esp-idf-extension/pull/1554) +- [Allow customize Pytest glob pattern and unit test services](https://github.com/espressif/vscode-esp-idf-extension/pull/1593) +- [CLang install prompt if not installed](https://github.com/espressif/vscode-esp-idf-extension/pull/1615) +- [Allow additional files and directories for Full Clean commands](https://github.com/espressif/vscode-esp-idf-extension/pull/1613) +- [Extend JTAG flash arguments as configuration setting](https://github.com/espressif/vscode-esp-idf-extension/pull/1583) +- [Range support for downloads in Setup Wizard](https://github.com/espressif/vscode-esp-idf-extension/pull/1625) +- [Check OpenOCD is running before debug is launched](https://github.com/espressif/vscode-esp-idf-extension/pull/1638) +- [Add function names in Disassembly view](https://github.com/espressif/vscode-esp-idf-extension/pull/1634) +- [OpenOCD Hints in Hints Viewer](https://github.com/espressif/vscode-esp-idf-extension/pull/1476) +- [Add detect as default serial port option and use esptool.py to find serial port](https://github.com/espressif/vscode-esp-idf-extension/pull/1632) +- [Pre-release campaign notification](https://github.com/espressif/vscode-esp-idf-extension/pull/1643) +- [Prefer gdbinit prefix_map with fallback to prefix_map_gdbinit](https://github.com/espressif/vscode-esp-idf-extension/pull/1660) +- [AI Integration with Copilot Chat using Language Tool API](https://github.com/espressif/vscode-esp-idf-extension/pull/1621) +- [Allow customize PyPi Index URL in setup wizard](https://github.com/espressif/vscode-esp-idf-extension/pull/1692) +- [Add create empty project command](https://github.com/espressif/vscode-esp-idf-extension/pull/1698) +- [Add Unity Runner and Parser, Remove Pytest](https://github.com/espressif/vscode-esp-idf-extension/pull/1681) + + +### Bug Fixes + +- [Update disassemble screenshot](https://github.com/espressif/vscode-esp-idf-extension/pull/1588) +- [JTAG acronym issues](https://github.com/espressif/vscode-esp-idf-extension/pull/1604) +- [Fix IDF_TARGET in multiple project configuration profiles](https://github.com/espressif/vscode-esp-idf-extension/pull/1579) +- [Fix Partial encryption in encrypted flashing](https://github.com/espressif/vscode-esp-idf-extension/pull/1373) +- [Close OpenOCD after JTAG flash end](https://github.com/espressif/vscode-esp-idf-extension/pull/1601) +- [NodeJS 20 in CI](https://github.com/espressif/vscode-esp-idf-extension/pull/1611) +- [Update build message](https://github.com/espressif/vscode-esp-idf-extension/pull/1603) +- [Fix append git and pigweed to PATH instead of prepend](https://github.com/espressif/vscode-esp-idf-extension/pull/1614) +- [Use latest in master in docs](https://github.com/espressif/vscode-esp-idf-extension/pull/1636) +- [Fix fileExists check in Setup panel](https://github.com/espressif/vscode-esp-idf-extension/pull/1609) Thanks @jonsambro ! +- [Use mon program_esp instead of load for Symbol loading in debug](https://github.com/espressif/vscode-esp-idf-extension/pull/1556) Thanks @wormyrocks ! +- [Move Status bar items to the left](https://github.com/espressif/vscode-esp-idf-extension/pull/1626) +- [Fix set target preview targets](https://github.com/espressif/vscode-esp-idf-extension/pull/1652) +- [Fix App trace and Heap Trace](https://github.com/espressif/vscode-esp-idf-extension/pull/1656) +- [Setup wizard misleading idf.py not found message fix](https://github.com/espressif/vscode-esp-idf-extension/pull/1642) +- [Clang and OpenOCD in PATH validation](https://github.com/espressif/vscode-esp-idf-extension/pull/1666) +- [Telemetry issues bugfixes](https://github.com/espressif/vscode-esp-idf-extension/pull/1675) +- [Fix openOCDRulesPath in addOpenOCDRules](https://github.com/espressif/vscode-esp-idf-extension/pull/1685) +- [Add contrainsts in pytest install step](https://github.com/espressif/vscode-esp-idf-extension/pull/1686) +- [Add double quoutes around gdbinit file path](https://github.com/espressif/vscode-esp-idf-extension/pull/1684) + + + + ## [1.10.1](https://github.com/espressif/vscode-esp-idf-extension/releases/tag/v1.10.1) ## Features and enhancements diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md new file mode 100644 index 000000000..2513d3e5b --- /dev/null +++ b/docs/TROUBLESHOOTING.md @@ -0,0 +1,7 @@ +# Documentation has moved to: + +# [English](https://docs.espressif.com/projects/vscode-esp-idf-extension/en/latest/troubleshooting.html) + +# 文档已移至 + +# [中文](https://docs.espressif.com/projects/vscode-esp-idf-extension/zh_CN/latest/troubleshooting.html) diff --git a/docs_espressif/en/debugproject.rst b/docs_espressif/en/debugproject.rst index 6fdfc57ae..39daf3b8e 100644 --- a/docs_espressif/en/debugproject.rst +++ b/docs_espressif/en/debugproject.rst @@ -144,6 +144,7 @@ You can modify the configuration to suit your needs. Let's describe the configur Some additional arguments you might use are: +- ``buildFlashMonitor``: (Default: false). Build, flash and launch IDF Monitor before starting the debug session. Use ``idf.monitorDelay`` to set a delay, in milliseconds, after starting the monitor (Default is ``1000``). - ``debugPort``: (Default: 43476) The port to launch the Eclipse CDT GDB Debug Adapter server. If not specified, it will use the default value of 43476. - ``runOpenOCD``: (Default: true). Run extension OpenOCD Server. - ``verifyAppBinBeforeDebug``: (Default: false) Verify that current ESP-IDF project binary is the same as binary in chip. diff --git a/docs_espressif/zh_CN/debugproject.rst b/docs_espressif/zh_CN/debugproject.rst index 7056da4f0..f07e811ac 100644 --- a/docs_espressif/zh_CN/debugproject.rst +++ b/docs_espressif/zh_CN/debugproject.rst @@ -89,6 +89,110 @@ 其中 ``0x20000`` 是分区表中使用的应用程序镜像偏移量。 +调试配置 +-------- + +要配置调试会话,请打开项目的 ``.vscode/launch.json`` 文件。此文件包含调试会话的配置。默认配置如下: + +.. code-block:: JSON + + { + "configurations": [ + { + "type": "gdbtarget", + "request": "attach", + "name": "Eclipse CDT GDB Adapter" + } + ] + } + +你可以根据需要修改配置。以下是配置选项的说明: + +- ``type``: 调试配置的类型。应设置为 ``gdbtarget``。 +- ``program``: 项目构建目录中的 ELF 文件,用于执行调试会话。可以使用命令 ``${command:espIdf.getProjectName}`` 查询扩展以查找当前构建目录的项目名称。 +- ``initCommands``: 用于初始化 GDB 和目标设备的 GDB 命令。默认值为 ``["set remote hardware-watchpoint-limit IDF_TARGET_CPU_WATCHPOINT_NUM", "mon reset halt", "maintenance flush register-cache"]``。 +- ``initialBreakpoint``: 当未定义 ``initCommands`` 时,此命令将在默认 ``initCommands`` 中添加指定函数名的硬件断点。例如 app_main(默认值)将在默认 initCommands 中添加 ``thb app_main``。如果设置为 ""(空字符串),则不会设置初始断点;如果未定义,则使用默认值 thb app_main。 +- ``gdb``: 要使用的 GDB 可执行文件。默认情况下,"${command:espIdf.getToolchainGdb}" 将查询扩展以查找当前 ESP-IDF 项目的 IDF_TARGET(esp32、esp32c6 等)对应的 ESP-IDF 工具链 GDB。 + +.. note:: + **IDF_TARGET_CPU_WATCHPOINT_NUM** 由扩展根据当前 ESP-IDF 项目的 ``IDF_TARGET`` (esp32、esp32c6 等)解析。 + +你可能使用的其他参数包括: + +- ``buildFlashMonitor``: (默认值:false)。在启动调试会话之前构建、烧录并启动 IDF Monitor。使用 ``idf.monitorDelay`` 设置启动监视器后的延迟时间(以毫秒为单位,默认值为 ``1000``)。 +- ``debugPort``: (默认值:43476)启动 Eclipse CDT GDB 调试适配器服务器的端口。如果未指定,将使用默认值 43476。 +- ``runOpenOCD``: (默认值:true)。运行扩展 OpenOCD 服务器。 +- ``verifyAppBinBeforeDebug``: (默认值:false)验证当前 ESP-IDF 项目二进制文件是否与芯片中的二进制文件相同。 +- ``logFile``: 用于记录与 gdb 交互的文件的绝对路径。示例:${workspaceFolder}/gdb.log。 +- ``verbose``: 生成详细的日志输出。 +- ``environment``: 应用于 ESP-IDF 调试适配器的环境变量。它将替换全局环境变量和扩展使用的环境变量。 + +.. code-block:: JSON + + { + "environment": { + "VAR": "Value" + } + } + +- ``imageAndSymbols`` : + +.. code-block:: JSON + + { + "imageAndSymbols": { + "symbolFileName": "如果指定,则在给定(可选)偏移量处加载的符号文件", + "symbolOffset": "如果指定了 symbolFileName,则使用的偏移量", + "imageFileName": "如果指定,则在给定(可选)偏移量处加载的镜像文件", + "imageOffset": "如果指定了 imageFileName,则使用的偏移量" + } + } + +- ``target``: 要附加的目标配置。指定如何连接到要调试的设备。通常 OpenOCD 将芯片作为端口 3333 上的远程目标公开。 + +.. code-block:: JSON + + { + "target": { + "type": "要执行的目标调试类型。传递给 -target-select(默认为 remote)", + "host": "要连接的目标主机(默认为 'localhost',如果设置了 parameters 则忽略)", + "port": "要连接的目标端口(默认为 serverPortRegExp 捕获的值,如果设置了 parameters 则忽略)", + "parameters": "目标类型的目标参数。通常是类似 localhost:12345 的内容。(默认为 `${host}:${port}`)", + "connectCommands": "替换所有先前的参数,指定用于建立连接的命令数组" + } + } + +下面显示了一个修改后的 launch.json 文件示例: + +.. code-block:: JSON + + { + "configurations": [ + { + "type": "gdbtarget", + "request": "attach", + "name": "Eclipse CDT GDB Adapter", + "program": "${workspaceFolder}/build/${command:espIdf.getProjectName}.elf", + "initCommands": [ + "set remote hardware-watchpoint-limit IDF_TARGET_CPU_WATCHPOINT_NUM", + "mon reset halt", + "maintenance flush register-cache" + ], + "gdb": "${command:espIdf.getToolchainGdb}", + "target": { + "connectCommands": [ + "set remotetimeout 20", + "-target-select extended-remote localhost:3333" + ] + } + } + ] + } + +虽然前面的示例明确使用了默认值,但可以根据需要进行自定义。 + +ESP-IDF VS Code 扩展的 package.json gdbtarget 调试器贡献中记录了其他较少使用的参数。 + 浏览代码、调用栈和线程 ---------------------- diff --git a/l10n/bundle.l10n.es.json b/l10n/bundle.l10n.es.json index 5b511990a..367edaec7 100644 --- a/l10n/bundle.l10n.es.json +++ b/l10n/bundle.l10n.es.json @@ -42,6 +42,7 @@ "ESP-Matter Python Requirements have been installed": "Se han instalado los requisitos de ESP-Matter Python", "PyTest python packages are already installed.": "Los paquetes de Python de PyTest ya están instalados.", "ESP-IDF: Building unit test app and flashing": "ESP-IDF: aplicación de prueba unitaria de construcción y flasheo", + "Flash encryption or secure boot is enabled on the sdkconfig. Erasing flash will permanently remove the encryption keys and may render the device unusable.": "El cifrado de flash o el arranque seguro está habilitado en el sdkconfig. Borrar el flash eliminará permanentemente las claves de cifrado y puede inutilizar el dispositivo.", "ESP-IDF vscode files have been added to the project.": "Se han agregado archivos ESP-IDF vscode al proyecto.", "ESP-IDF container files have been added to the project.": "Se han agregado archivos contenedores ESP-IDF al proyecto.", "Enter ESP-IDF component name": "Ingrese el nombre del componente ESP-IDF", @@ -267,5 +268,6 @@ "Learn More": "Más información", "Not Now": "Ahora no", "To install the pre-release version, click the 'Switch to Pre-Release Version' button.": "Para instalar la versión preliminar, haga clic en el botón 'Cambiar a versión preliminar'.", - "Got it": "Entendido" + "Got it": "Entendido", + "Flash encryption or secure boot is enabled on the device. Erasing flash will permanently remove the encryption keys and may render the device unusable.": "El cifrado de flash o el arranque seguro está habilitado en el dispositivo. Borrar el flash eliminará permanentemente las claves de cifrado y puede inutilizar el dispositivo." } diff --git a/l10n/bundle.l10n.pt.json b/l10n/bundle.l10n.pt.json index 4e395b295..c3ad03d1f 100644 --- a/l10n/bundle.l10n.pt.json +++ b/l10n/bundle.l10n.pt.json @@ -42,6 +42,7 @@ "ESP-Matter Python Requirements have been installed": "Os requisitos do ESP-Matter Python foram instalados", "PyTest python packages are already installed.": "Os pacotes PyTest python já estão instalados.", "ESP-IDF: Building unit test app and flashing": "ESP-IDF: Construindo aplicativo de teste de unidade e flashing", + "Flash encryption or secure boot is enabled on the sdkconfig. Erasing flash will permanently remove the encryption keys and may render the device unusable.": "A criptografia de flash ou inicialização segura está ativada no sdkconfig. Apagar o flash removerá permanentemente as chaves de criptografia e pode tornar o dispositivo inutilizável.", "ESP-IDF vscode files have been added to the project.": "Arquivos ESP-IDF vscode foram adicionados ao projeto.", "ESP-IDF container files have been added to the project.": "Arquivos contêiner ESP-IDF foram adicionados ao projeto.", "Enter ESP-IDF component name": "Insira o nome do componente ESP-IDF", @@ -268,5 +269,6 @@ "Learn More": "Saiba mais", "Not Now": "Agora não", "To install the pre-release version, click the 'Switch to Pre-Release Version' button.": "Para instalar a versão pré-lançamento, clique no botão 'Alternar para versão pré-lançamento'.", - "Got it": "Entendi" + "Got it": "Entendi", + "Flash encryption or secure boot is enabled on the device. Erasing flash will permanently remove the encryption keys and may render the device unusable.": "A criptografia de flash ou inicialização segura está ativada no dispositivo. Apagar o flash removerá permanentemente as chaves de criptografia e pode tornar o dispositivo inutilizável." } diff --git a/l10n/bundle.l10n.ru.json b/l10n/bundle.l10n.ru.json index 68ab1cdb1..f209b7eca 100644 --- a/l10n/bundle.l10n.ru.json +++ b/l10n/bundle.l10n.ru.json @@ -42,6 +42,7 @@ "ESP-Matter Python Requirements have been installed": "Требования Python ESP-Matter установлены", "PyTest python packages are already installed.": "Пакеты Python PyTest уже установлены.", "ESP-IDF: Building unit test app and flashing": "ESP-IDF: создание приложения модульного тестирования и перепрошивка", + "Flash encryption or secure boot is enabled on the sdkconfig. Erasing flash will permanently remove the encryption keys and may render the device unusable.": "На sdkconfig включено шифрование флэш-памяти или безопасная загрузка. Стирание флэш-памяти приведет к безвозвратному удалению ключей шифрования и может сделать устройство непригодным для использования.", "ESP-IDF vscode files have been added to the project.": "В проект добавлены файлы ESP-IDF vscode.", "ESP-IDF container files have been added to the project.": "В проект добавлены файлы-контейнеры ESP-IDF.", "Enter ESP-IDF component name": "Введите имя компонента ESP-IDF", @@ -268,5 +269,6 @@ "Learn More": "Узнать больше", "Not Now": "Не сейчас", "To install the pre-release version, click the 'Switch to Pre-Release Version' button.": "Для установки предварительной версии нажмите кнопку 'Переключиться на предварительную версию'.", - "Got it": "Понятно" + "Got it": "Понятно", + "Flash encryption or secure boot is enabled on the device. Erasing flash will permanently remove the encryption keys and may render the device unusable.": "На устройстве включено шифрование флэш-памяти или безопасная загрузка. Стирание флэш-памяти приведет к безвозвратному удалению ключей шифрования и может сделать устройство непригодным для использования." } diff --git a/l10n/bundle.l10n.zh-CN.json b/l10n/bundle.l10n.zh-CN.json index 5ceb75489..c56a015e0 100644 --- a/l10n/bundle.l10n.zh-CN.json +++ b/l10n/bundle.l10n.zh-CN.json @@ -42,6 +42,7 @@ "ESP-Matter Python Requirements have been installed": "已安装 ESP-Matter Python 依赖包", "PyTest python packages are already installed.": "已安装 PyTest Python 包。", "ESP-IDF: Building unit test app and flashing": "ESP-IDF:正在构建并烧录单元测试应用程序", + "Flash encryption or secure boot is enabled on the sdkconfig. Erasing flash will permanently remove the encryption keys and may render the device unusable.": "sdkconfig 上已启用闪存加密或安全启动。擦除闪存将永久删除加密密钥,可能导致设备无法使用。", "ESP-IDF vscode files have been added to the project.": "ESP-IDF VS Code 文件已添加到项目中。", "ESP-IDF container files have been added to the project.": "ESP-IDF 容器文件已添加到项目中。", "Enter ESP-IDF component name": "输入 ESP-IDF 组件名称", @@ -268,5 +269,6 @@ "Learn More": "了解更多", "Not Now": "暂不使用", "To install the pre-release version, click the 'Switch to Pre-Release Version' button.": "要安装预发布版本,请点击\"切换到预发布版本\"按钮。", - "Got it": "知道了" + "Got it": "知道了", + "Flash encryption or secure boot is enabled on the device. Erasing flash will permanently remove the encryption keys and may render the device unusable.": "设备上已启用闪存加密或安全启动。擦除闪存将永久删除加密密钥,可能导致设备无法使用。" } diff --git a/package.json b/package.json index 9e5e6918f..502089edf 100644 --- a/package.json +++ b/package.json @@ -1949,6 +1949,11 @@ "description": "Port for Debug Adapter server. Default: 43476", "default": 43476 }, + "buildFlashMonitor": { + "type": "boolean", + "description": "Build, Flash and Monitor the device before debugging", + "default": false + }, "runOpenOCD": { "type": "boolean", "description": "Run OpenOCD Server", @@ -2223,6 +2228,11 @@ "description": "Port for Debug Adapter server. Default: 43476", "default": 43476 }, + "buildFlashMonitor": { + "type": "boolean", + "description": "Build, Flash and Monitor the device before debugging", + "default": false + }, "runOpenOCD": { "type": "boolean", "description": "Run OpenOCD Server", diff --git a/src/build/buildCmd.ts b/src/build/buildCmd.ts index 08320a1c1..da50900a2 100644 --- a/src/build/buildCmd.ts +++ b/src/build/buildCmd.ts @@ -185,8 +185,8 @@ export async function buildCommand( false ); } + continueFlag = false; } - continueFlag = false; return continueFlag; } diff --git a/src/buildFlashMonitor/index.ts b/src/buildFlashMonitor/index.ts new file mode 100644 index 000000000..fd062bc5a --- /dev/null +++ b/src/buildFlashMonitor/index.ts @@ -0,0 +1,127 @@ +/* + * Project: ESP-IDF VSCode Extension + * File Created: Wednesday, 26th November 2025 10:54:44 am + * Copyright 2025 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + CancellationToken, + commands, + env, + l10n, + Progress, + ProgressLocation, + UIKind, + Uri, + window, +} from "vscode"; +import { openFolderCheck } from "../common/PreCheck"; +import { NotificationMode, readParameter } from "../idfConfiguration"; +import { PreCheck, shouldDisableMonitorReset } from "../utils"; +import { IDFWebCommandKeys } from "../cmdTreeView/cmdStore"; +import { isFlashEncryptionEnabled } from "../flash/verifyFlashEncryption"; +import { ESP } from "../config"; +import { IDFMonitor } from "../espIdf/monitor"; +import { buildCommand } from "../build/buildCmd"; +import { startFlashing } from "../flash/startFlashing"; +import { createNewIdfMonitor } from "../espIdf/monitor/command"; + +export async function buildFlashAndMonitor( + workspaceFolderUri: Uri, + noResetMonitor?: boolean +) { + await PreCheck.perform([openFolderCheck], async () => { + const notificationMode = readParameter( + "idf.notificationMode", + workspaceFolderUri + ) as string; + const currentProgressLocation = + notificationMode === NotificationMode.All || + notificationMode === NotificationMode.Notifications + ? ProgressLocation.Notification + : ProgressLocation.Window; + + await window.withProgress( + { + cancellable: true, + location: currentProgressLocation, + title: "ESP-IDF:", + }, + async ( + progress: Progress<{ message: string; increment: number }>, + cancelToken: CancellationToken + ) => { + progress.report({ message: "Building project...", increment: 20 }); + const flashType = readParameter("idf.flashType", workspaceFolderUri); + let canContinue = await buildCommand( + workspaceFolderUri, + cancelToken, + flashType + ); + if (!canContinue) { + return; + } + // Re route to ESP-IDF Web extension if using Codespaces or Browser + if (env.uiKind === UIKind.Web) { + commands.executeCommand(IDFWebCommandKeys.FlashAndMonitor); + return; + } + progress.report({ + message: "Flashing project into device...", + increment: 60, + }); + + let encryptPartitions = await isFlashEncryptionEnabled( + workspaceFolderUri + ); + + let partitionToUse = readParameter( + "idf.flashPartitionToUse", + workspaceFolderUri + ) as ESP.BuildType; + + if ( + partitionToUse && + !["app", "bootloader", "partition-table"].includes(partitionToUse) + ) { + partitionToUse = undefined; + } + + canContinue = await startFlashing( + workspaceFolderUri, + cancelToken, + flashType, + encryptPartitions, + partitionToUse + ); + if (!canContinue) { + return; + } + progress.report({ + message: "Launching monitor...", + increment: 10, + }); + if (IDFMonitor.terminal) { + IDFMonitor.terminal.sendText(ESP.CTRL_RBRACKET); + } + const noReset = + typeof noResetMonitor !== "undefined" + ? noResetMonitor + : await shouldDisableMonitorReset(workspaceFolderUri); + await createNewIdfMonitor(workspaceFolderUri, noReset); + } + ); + }); +} diff --git a/src/cdtDebugAdapter/debugConfProvider.ts b/src/cdtDebugAdapter/debugConfProvider.ts index e232ca5d6..591c6f7b2 100644 --- a/src/cdtDebugAdapter/debugConfProvider.ts +++ b/src/cdtDebugAdapter/debugConfProvider.ts @@ -35,9 +35,54 @@ import { Logger } from "../logger/logger"; import { getConfigValueFromSDKConfig, getToolchainPath } from "../utils"; import { createNewIdfMonitor } from "../espIdf/monitor/command"; import { ESP } from "../config"; +import { buildFlashAndMonitor } from "../buildFlashMonitor"; export class CDTDebugConfigurationProvider implements DebugConfigurationProvider { + public async resolveDebugConfigurationWithSubstitutedVariables( + folder: WorkspaceFolder | undefined, + debugConfiguration: DebugConfiguration, + token?: CancellationToken + ) { + if (!folder) { + const workspaceFolderUri = ESP.GlobalConfiguration.store.get( + ESP.GlobalConfiguration.SELECTED_WORKSPACE_FOLDER + ); + folder = workspace.getWorkspaceFolder(workspaceFolderUri); + if (!folder) { + folder = await window.showWorkspaceFolderPick({ + placeHolder: "Pick a workspace folder to start a debug session.", + }); + if (!folder) { + throw new Error("No folder was selected to start debug session"); + } + } + } + const useMonitorWithDebug = readParameter( + "idf.launchMonitorOnDebugSession", + folder + ); + if (debugConfiguration.buildFlashMonitor) { + await buildFlashAndMonitor(folder.uri, true); + } else if ( + debugConfiguration.sessionID !== "core-dump.debug.session.ws" && + debugConfiguration.sessionID !== "gdbstub.debug.session.ws" && + useMonitorWithDebug + ) { + await createNewIdfMonitor(folder.uri, true); + } + const openOCDManager = OpenOCDManager.init(); + if ( + !openOCDManager.isRunning() && + debugConfiguration.sessionID !== "core-dump.debug.session.ws" && + debugConfiguration.sessionID !== "gdbstub.debug.session.ws" && + debugConfiguration.sessionID !== "qemu.debug.session" && + debugConfiguration.runOpenOCD !== false + ) { + await openOCDManager.start(); + } + return debugConfiguration; + } public async resolveDebugConfiguration( folder: WorkspaceFolder | undefined, config: DebugConfiguration, @@ -170,27 +215,6 @@ export class CDTDebugConfigurationProvider ); } } - const useMonitorWithDebug = readParameter( - "idf.launchMonitorOnDebugSession", - folder - ); - if ( - config.sessionID !== "core-dump.debug.session.ws" && - config.sessionID !== "gdbstub.debug.session.ws" && - useMonitorWithDebug - ) { - await createNewIdfMonitor(folder.uri, true); - } - const openOCDManager = OpenOCDManager.init(); - if ( - !openOCDManager.isRunning() && - config.sessionID !== "core-dump.debug.session.ws" && - config.sessionID !== "gdbstub.debug.session.ws" && - config.sessionID !== "qemu.debug.session" && - config.runOpenOCD !== false - ) { - await openOCDManager.start(); - } } catch (error) { const msg = error.message ? error.message diff --git a/src/common/PreCheck.ts b/src/common/PreCheck.ts new file mode 100644 index 000000000..f389ea2ce --- /dev/null +++ b/src/common/PreCheck.ts @@ -0,0 +1,44 @@ +/* + * Project: ESP-IDF VSCode Extension + * File Created: Wednesday, 26th November 2025 10:51:20 am + * Copyright 2025 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { l10n } from "vscode"; +import { PreCheck, PreCheckInput } from "../utils"; + +const openFolderFirstMsg = l10n.t("Open a folder first."); +const cmdNotForWebIdeMsg = l10n.t( + "Selected command is not available in {envName}", + { envName: "Codespaces" } +); +const cmdNotDockerContainerMsg = l10n.t( + "Selected command is not available in {envName}", + { envName: "Docker container" } +); +export const openFolderCheck = [ + PreCheck.isWorkspaceFolderOpen, + openFolderFirstMsg, +] as PreCheckInput; + +export const webIdeCheck = [ + PreCheck.notUsingWebIde, + cmdNotForWebIdeMsg, +] as PreCheckInput; + +export const isNotDockerContainerCheck = [ + PreCheck.isNotDockerContainer, + cmdNotDockerContainerMsg, +] as PreCheckInput; \ No newline at end of file diff --git a/src/espIdf/debugAdapter/checkPyReqs.ts b/src/espIdf/debugAdapter/checkPyReqs.ts index 99ca49c36..10a7f2e7a 100644 --- a/src/espIdf/debugAdapter/checkPyReqs.ts +++ b/src/espIdf/debugAdapter/checkPyReqs.ts @@ -25,6 +25,7 @@ import { getVirtualEnvPythonPath } from "../../pythonManager"; export async function checkDebugAdapterRequirements(workspaceFolder: Uri) { const idfPath = readParameter("idf.espIdfPath", workspaceFolder); + const espIdfToolsPath = readParameter("idf.espIdfToolsPath", workspaceFolder); const pythonBinPath = await getVirtualEnvPythonPath(workspaceFolder); let requirementsPath = join( extensionContext.extensionPath, @@ -40,6 +41,7 @@ export async function checkDebugAdapterRequirements(workspaceFolder: Uri) { checkResult = await startPythonReqsProcess( pythonBinPath, idfPath, + espIdfToolsPath, requirementsPath ); } catch (error) { diff --git a/src/espIdf/menuconfig/Menu.ts b/src/espIdf/menuconfig/Menu.ts index 0fe8f2b8e..43f846cf1 100644 --- a/src/espIdf/menuconfig/Menu.ts +++ b/src/espIdf/menuconfig/Menu.ts @@ -34,4 +34,5 @@ export class Menu { public value: any; public dependsOn: string; public isMenuconfig: boolean; + public default: any; } diff --git a/src/espIdf/menuconfig/MenuconfigPanel.ts b/src/espIdf/menuconfig/MenuconfigPanel.ts index d12f12a2d..858cda2d3 100644 --- a/src/espIdf/menuconfig/MenuconfigPanel.ts +++ b/src/espIdf/menuconfig/MenuconfigPanel.ts @@ -137,55 +137,65 @@ export class MenuConfigPanel { JSON.parse(message.updated_value) as Menu ); break; + case "resetElement": + ConfserverProcess.resetElementById(message.id); + break; + case "resetElementChildren": + ConfserverProcess.resetElementChildren(message.children); + break; case "setDefault": - const changesNotSavedMessage = vscode.l10n.t( - "This action will delete your project sdkconfig. Continue?" - ); - const yesMsg = vscode.l10n.t("Save"); - const noMsg = vscode.l10n.t("Discard"); - const isModal = process.platform !== "win32" ? true : false; - const selected = await vscode.window.showInformationMessage( - changesNotSavedMessage, - { modal: isModal }, - { title: yesMsg, isCloseAffordance: false }, - { title: noMsg, isCloseAffordance: true } - ); - if (selected.title === yesMsg) { - const notificationMode = readParameter( - "idf.notificationMode", - this.curWorkspaceFolder - ) as string; - const ProgressLocation = - notificationMode === NotificationMode.All || - notificationMode === NotificationMode.Notifications - ? vscode.ProgressLocation.Notification - : vscode.ProgressLocation.Window; - vscode.window.withProgress( - { - cancellable: true, - location: ProgressLocation, - title: "ESP-IDF: SDK Configuration editor", - }, - async ( - progress: vscode.Progress<{ - message: string; - increment: number; - }> - ) => { - try { - await ConfserverProcess.setDefaultValues( - extensionPath, - progress - ); - } catch (error) { - Logger.errorNotify( - error.message, - error, - "MenuConfigPanel setDefaultValues" - ); - } - } + if (ConfserverProcess.confserverVersion >= 3) { + ConfserverProcess.resetElementById("all"); + } else { + const changesNotSavedMessage = vscode.l10n.t( + "This action will delete your project sdkconfig. Continue?" + ); + const yesMsg = vscode.l10n.t("Save"); + const noMsg = vscode.l10n.t("Discard"); + const isModal = process.platform !== "win32" ? true : false; + const selected = await vscode.window.showInformationMessage( + changesNotSavedMessage, + { modal: isModal }, + { title: yesMsg, isCloseAffordance: false }, + { title: noMsg, isCloseAffordance: true } ); + if (selected.title === yesMsg) { + const notificationMode = readParameter( + "idf.notificationMode", + this.curWorkspaceFolder + ) as string; + const ProgressLocation = + notificationMode === NotificationMode.All || + notificationMode === NotificationMode.Notifications + ? vscode.ProgressLocation.Notification + : vscode.ProgressLocation.Window; + vscode.window.withProgress( + { + cancellable: true, + location: ProgressLocation, + title: "ESP-IDF: SDK Configuration editor", + }, + async ( + progress: vscode.Progress<{ + message: string; + increment: number; + }> + ) => { + try { + await ConfserverProcess.setDefaultValues( + extensionPath, + progress + ); + } catch (error) { + Logger.errorNotify( + error.message, + error, + "MenuConfigPanel setDefaultValues" + ); + } + } + ); + } } break; case "saveChanges": @@ -206,6 +216,7 @@ export class MenuConfigPanel { MenuConfigPanel.currentPanel.panel.webview.postMessage({ command: "load_initial_values", menus: initialValues, + version: ConfserverProcess.confserverVersion, }); break; default: diff --git a/src/espIdf/menuconfig/confServerProcess.ts b/src/espIdf/menuconfig/confServerProcess.ts index 1d076be68..083d9ad74 100644 --- a/src/espIdf/menuconfig/confServerProcess.ts +++ b/src/espIdf/menuconfig/confServerProcess.ts @@ -138,6 +138,16 @@ export class ConfserverProcess { return ConfserverProcess.instance.kconfigsMenus; } + public static resetElementById(id: string) { + let resetValueRequest = `{"version": 3, "reset": [ "${id}" ]}\n`; + ConfserverProcess.sendUpdatedValue(resetValueRequest); + } + + public static resetElementChildren(children: string[]) { + let resetValueRequest = `{"version": 3, "reset": [ "${children.join("\", \"")}" ]}\n`; + ConfserverProcess.sendUpdatedValue(resetValueRequest); + } + public static setUpdatedValue(updatedValue: Menu) { let newValueRequest: string; switch (updatedValue.type) { @@ -206,10 +216,9 @@ export class ConfserverProcess { progress.report({ increment: 10, message: "Deleting current values..." }); ConfserverProcess.instance.areValuesSaved = true; const currWorkspace = ConfserverProcess.instance.workspaceFolder; - await delConfigFile(currWorkspace); const guiconfigEspPath = - idfConf.readParameter("idf.espIdfPath", currWorkspace) || - process.env.IDF_PATH; + idfConf.readParameter("idf.espIdfPath", currWorkspace) || + process.env.IDF_PATH; const idfPyPath = path.join(guiconfigEspPath, "tools", "idf.py"); const modifiedEnv = await appendIdfAndToolsToPath(currWorkspace); const pythonBinPath = await getVirtualEnvPythonPath(currWorkspace); @@ -223,14 +232,14 @@ export class ConfserverProcess { } reconfigureArgs.push("-C", currWorkspace.fsPath); const sdkconfigDefaults = - (idfConf.readParameter("idf.sdkconfigDefaults") as string[]) || []; - + (idfConf.readParameter("idf.sdkconfigDefaults") as string[]) || []; + if (reconfigureArgs.indexOf("SDKCONFIG") === -1) { reconfigureArgs.push( `-DSDKCONFIG='${ConfserverProcess.instance.configFile}'` ); } - + if ( reconfigureArgs.indexOf("SDKCONFIG_DEFAULTS") === -1 && sdkconfigDefaults && @@ -241,6 +250,8 @@ export class ConfserverProcess { ); } reconfigureArgs.push("reconfigure"); + await delConfigFile(currWorkspace); + const getSdkconfigProcess = spawn(pythonBinPath, reconfigureArgs, { env: modifiedEnv, }); @@ -294,6 +305,7 @@ export class ConfserverProcess { MenuConfigPanel.currentPanel.dispose(); } } + public static confserverVersion: number = 2; private static instance: ConfserverProcess; private static progress: vscode.Progress<{ @@ -408,6 +420,10 @@ export class ConfserverProcess { this.kconfigsMenus.push(resConfig); } + if (jsonValues && jsonValues.version) { + ConfserverProcess.confserverVersion = jsonValues.version; + } + MenuConfigPanel.createOrShow( this.extensionPath, this.workspaceFolder, diff --git a/src/espIdf/menuconfig/kconfigMenuLoader.ts b/src/espIdf/menuconfig/kconfigMenuLoader.ts index 9632a3bbe..74abb4a1a 100644 --- a/src/espIdf/menuconfig/kconfigMenuLoader.ts +++ b/src/espIdf/menuconfig/kconfigMenuLoader.ts @@ -26,7 +26,7 @@ import { Menu, menuType } from "./Menu"; export class KconfigMenuLoader { public static updateValues( config: Menu, - values: { values: {}; visible: {}; ranges: {} } + values: { values: {}; visible: {}; ranges: {}; defaults?: {} } ): Menu { const newConfig: Menu = config; if ( @@ -38,6 +38,12 @@ export class KconfigMenuLoader { ? values.values[newConfig.name].toString(16) : values.values[newConfig.name]; } + if (values.defaults && values.defaults.hasOwnProperty(newConfig.name)) { + newConfig.default = + newConfig.type === menuType.hex + ? values.defaults[newConfig.name].toString(16) + : values.defaults[newConfig.name]; + } if (values.visible.hasOwnProperty(newConfig.id)) { newConfig.isVisible = values.visible[newConfig.id]; } @@ -95,6 +101,7 @@ export class KconfigMenuLoader { dependsOn: config.depends_on, children: [], value: null, + default: null, }; for (const child of config.children) { const childMenu: Menu = this.mapJsonToMenuObject(child); diff --git a/src/espIdf/monitor/command.ts b/src/espIdf/monitor/command.ts index b975ed6c0..3e9e22276 100644 --- a/src/espIdf/monitor/command.ts +++ b/src/espIdf/monitor/command.ts @@ -138,14 +138,10 @@ export async function createNewIdfMonitor( } IDFMonitor.start(); if (noReset) { - const idfPath = readParameter("idf.espIdfPath", workspaceFolder) as string; - const idfVersion = await utils.getEspIdfFromCMake(idfPath); - if (idfVersion <= "5.0") { - const monitorDelay = readParameter( - "idf.monitorDelay", - workspaceFolder - ) as number; - await utils.sleep(monitorDelay); - } + const monitorDelay = readParameter( + "idf.monitorDelay", + workspaceFolder + ) as number; + await utils.sleep(monitorDelay); } } diff --git a/src/espIdf/serial/serialPort.ts b/src/espIdf/serial/serialPort.ts index ac0ae756e..cc794c6eb 100644 --- a/src/espIdf/serial/serialPort.ts +++ b/src/espIdf/serial/serialPort.ts @@ -153,6 +153,9 @@ export class SerialPort { const portMatch = line.match(/Serial port\s+(\S+)/); if (portMatch) { currentPort = portMatch[1]; + if (currentPort.endsWith(":")) { + currentPort = currentPort.slice(0, -1); + } testedPorts++; progress.report({ message: vscode.l10n.t( @@ -167,7 +170,9 @@ export class SerialPort { } // Look for "Chip is" lines to identify successful connections - const chipMatch = line.match(/Chip is\s+([^(]+)/); + const chipMatch = + line.match(/Connected to\s+([^\s]+)\s+on/) || + line.match(/Chip is\s+([^\s(]+)/); if (chipMatch && currentPort) { const chipType = chipMatch[1] .trim() diff --git a/src/espIdf/size/idfSize.ts b/src/espIdf/size/idfSize.ts index 8becd00af..de1ec3948 100644 --- a/src/espIdf/size/idfSize.ts +++ b/src/espIdf/size/idfSize.ts @@ -63,7 +63,7 @@ export class IDFSize { utils.compareVersion(version, "5.3.0") >= 0 ? ["--format", "json2"] : utils.compareVersion(version, "5.1.0") >= 0 - ? ["--format", "json"] + ? ["--format", "json"] : ["--json"]; const overview = await this.idfCommandInvoker([ "idf_size.py", @@ -125,6 +125,7 @@ export class IDFSize { ); const buffOut = await spawn(pythonBinPath, args, { cwd: idfPath, + silent: true, }); const buffStr = buffOut.toString(); const buffObj = JSON.parse(buffStr); diff --git a/src/extension.ts b/src/extension.ts index fb8c33c23..0f5091cdc 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -40,10 +40,7 @@ import { AppTraceTreeDataProvider } from "./espIdf/tracing/tree/appTraceTreeData import * as idfConf from "./idfConfiguration"; import { Logger } from "./logger/logger"; import { OutputChannel } from "./logger/outputChannel"; -import { - showInfoNotificationWithAction, - showInfoNotificationWithMultipleActions, -} from "./logger/utils"; +import { showInfoNotificationWithAction } from "./logger/utils"; import * as utils from "./utils"; import { PreCheck, shouldDisableMonitorReset } from "./utils"; import { @@ -109,14 +106,7 @@ import { writeTextReport } from "./support/writeReport"; import { getNewProjectArgs } from "./newProject/newProjectInit"; import { NewProjectPanel } from "./newProject/newProjectPanel"; import { buildCommand } from "./build/buildCmd"; -import { verifyCanFlash } from "./flash/flashCmd"; -import { - isFlashEncryptionEnabled, - FlashCheckResultType, - checkFlashEncryption, -} from "./flash/verifyFlashEncryption"; -import { flashCommand } from "./flash/uartFlash"; -import { jtagFlashCommand } from "./flash/jtagCmd"; +import { isFlashEncryptionEnabled } from "./flash/verifyFlashEncryption"; import { createNewIdfMonitor } from "./espIdf/monitor/command"; import { KconfigLangClient } from "./kconfig"; import { configureProjectWithGcov } from "./coverage/configureProject"; @@ -145,11 +135,7 @@ import { PeripheralBaseNode } from "./espIdf/debugAdapter/nodes/base"; import { ExtensionConfigStore } from "./common/store"; import { projectConfigurationPanel } from "./project-conf/projectConfPanel"; import { ProjectConfigStore } from "./project-conf"; -import { - clearPreviousIdfSetups, - getPreviousIdfSetups, - loadIdfSetupsFromEspIdfJson, -} from "./setup/existingIdfSetups"; +import { clearPreviousIdfSetups } from "./setup/existingIdfSetups"; import { getEspRainmaker } from "./rainmaker/download/espRainmakerDownload"; import { UnitTest } from "./espIdf/unitTest/adapter"; import { @@ -167,11 +153,7 @@ import { checkDebugAdapterRequirements } from "./espIdf/debugAdapter/checkPyReqs import { CDTDebugConfigurationProvider } from "./cdtDebugAdapter/debugConfProvider"; import { CDTDebugAdapterDescriptorFactory } from "./cdtDebugAdapter/server"; import { IdfReconfigureTask } from "./espIdf/reconfigure/task"; -import { - ErrorHintProvider, - ErrorHintTreeItem, - HintHoverProvider, -} from "./espIdf/hints/index"; +import { ErrorHintProvider, HintHoverProvider } from "./espIdf/hints/index"; import { installWebsocketClient } from "./espIdf/monitor/checkWebsocketClient"; import { TroubleshootingPanel } from "./support/troubleshootPanel"; import { @@ -184,7 +166,6 @@ import { createCommandDictionary, IDFWebCommandKeys, } from "./cmdTreeView/cmdStore"; -import { IdfSetup } from "./views/setup/types"; import { asyncRemoveEspIdfSettings } from "./uninstall"; import { clearSelectedProjectConfiguration, @@ -201,6 +182,14 @@ import { OpenOCDErrorMonitor } from "./espIdf/hints/openocdhint"; import { updateHintsStatusBarItem } from "./statusBar"; import { activateLanguageTool, deactivateLanguageTool } from "./langTools"; import { readSerialPort } from "./idfConfiguration"; +import { + openFolderCheck, + isNotDockerContainerCheck, + webIdeCheck, +} from "./common/PreCheck"; +import { buildFlashAndMonitor } from "./buildFlashMonitor"; +import { selectFlashMethod, startFlashing } from "./flash/startFlashing"; +import { jtagEraseFlashCommand } from "./flash/eraseFlashJtag"; // Global variables shared by commands let workspaceRoot: vscode.Uri; @@ -250,28 +239,6 @@ let wsServer: WSServer; // Precheck methods and their messages -const openFolderFirstMsg = vscode.l10n.t("Open a folder first."); -const cmdNotForWebIdeMsg = vscode.l10n.t( - "Selected command is not available in {envName}", - { envName: "Codespaces" } -); -const cmdNotDockerContainerMsg = vscode.l10n.t( - "Selected command is not available in {envName}", - { envName: "Docker container" } -); -const openFolderCheck = [ - PreCheck.isWorkspaceFolderOpen, - openFolderFirstMsg, -] as utils.PreCheckInput; -const webIdeCheck = [ - PreCheck.notUsingWebIde, - cmdNotForWebIdeMsg, -] as utils.PreCheckInput; -const isNotDockerContainerCheck = [ - PreCheck.isNotDockerContainer, - cmdNotDockerContainerMsg, -] as utils.PreCheckInput; - const minIdfVersionCheck = async function ( minVersion: string, workspace: vscode.Uri @@ -602,8 +569,30 @@ export async function activate(context: vscode.ExtensionContext) { openOCDManager.stop(); } debugAdapterManager.stop(); + if (IDFMonitor.terminal) { + IDFMonitor.terminal.sendText(ESP.CTRL_RBRACKET); + } }); + const kconfigMenusWatcher = vscode.workspace.createFileSystemWatcher( + "**/config/kconfig_menus.json", + true, + false, + false + ); + context.subscriptions.push( + kconfigMenusWatcher.onDidChange(async (e) => { + if (ConfserverProcess.exists()) { + ConfserverProcess.dispose(); + } + }), + kconfigMenusWatcher.onDidDelete(async (e) => { + if (ConfserverProcess.exists()) { + ConfserverProcess.dispose(); + } + }) + ); + const sdkconfigWatcher = vscode.workspace.createFileSystemWatcher( "**/sdkconfig", false, @@ -705,7 +694,7 @@ export async function activate(context: vscode.ExtensionContext) { notificationMode === idfConf.NotificationMode.Notifications ? vscode.ProgressLocation.Notification : vscode.ProgressLocation.Window; - vscode.window.withProgress( + await vscode.window.withProgress( { cancellable: true, location: ProgressLocation, @@ -721,28 +710,81 @@ export async function activate(context: vscode.ExtensionContext) { cancelToken: vscode.CancellationToken ) => { try { - const port = await readSerialPort(this.currentWorkspace, false); - if (!port) { - return Logger.warnNotify( + let flashType = idfConf.readParameter( + "idf.flashType", + workspaceRoot + ) as ESP.FlashType; + if (!flashType) { + flashType = await selectFlashMethod(workspaceRoot); + } + if (IDFMonitor.terminal) { + IDFMonitor.terminal.sendText(ESP.CTRL_RBRACKET); + const monitorDelay = idfConf.readParameter( + "idf.monitorDelay", + workspaceRoot + ) as number; + await utils.sleep(monitorDelay); + } + const isEncrypted = await isFlashEncryptionEnabled(workspaceRoot); + + const secureBoot = await utils.getConfigValueFromSDKConfig( + "CONFIG_SECURE_BOOT", + workspaceRoot + ); + const isSecureBootEnabled = secureBoot === "y"; + if (isEncrypted || isSecureBootEnabled) { + Logger.warnNotify( vscode.l10n.t( - "No serial port found for current IDF_TARGET: {0}", - await getIdfTargetFromSdkconfig(this.currentWorkspace) + "Flash encryption or secure boot is enabled on the sdkconfig. Erasing flash will permanently remove the encryption keys and may render the device unusable." ) ); + return; } - const eraseFlashTask = new EraseFlashTask(workspaceRoot); - await eraseFlashTask.eraseFlash(port); - await TaskManager.runTasks(); - if (!cancelToken.isCancellationRequested) { - EraseFlashTask.isErasing = false; - const msg = "Erase flash done"; - OutputChannel.appendLineAndShow(msg, "Erase flash"); + if (flashType === ESP.FlashType.JTAG) { + OutputChannel.appendLine( + "Erasing flash via JTAG...", + "Erase flash" + ); + await jtagEraseFlashCommand(workspaceRoot); + const msg = + "JTAG erase flash finished. Check Output channel to see results."; + OutputChannel.appendLine(msg, "Erase flash"); Logger.infoNotify(msg); + } else { + cancelToken.onCancellationRequested(() => { + TaskManager.cancelTasks(); + TaskManager.disposeListeners(); + EraseFlashTask.isErasing = false; + return; + }); + const port = await readSerialPort(workspaceRoot, false); + if (!port) { + Logger.warnNotify( + vscode.l10n.t( + "No serial port found for current IDF_TARGET: {0}", + await getIdfTargetFromSdkconfig(workspaceRoot) + ) + ); + return; + } + const eraseFlashTask = new EraseFlashTask(workspaceRoot); + await eraseFlashTask.eraseFlash(port); + await TaskManager.runTasks(); + if (!cancelToken.isCancellationRequested) { + EraseFlashTask.isErasing = false; + const msg = "⚡️ Erase flash done"; + OutputChannel.appendLine(msg, "Erase flash"); + Logger.infoNotify(msg); + OutputChannel.appendLine( + "Flash memory content has been erased." + ); + Logger.infoNotify("Flash memory content has been erased."); + } + TaskManager.disposeListeners(); } - TaskManager.disposeListeners(); - OutputChannel.appendLine("Flash memory content has been erased."); - Logger.infoNotify("Flash memory content has been erased."); } catch (error) { + EraseFlashTask.isErasing = false; + TaskManager.disposeListeners(); Logger.errorNotify(error.message, error, "extension eraseFlash"); } } @@ -2095,7 +2137,9 @@ export async function activate(context: vscode.ExtensionContext) { registerIDFCommand("espIdf.flashAndEncryptDevice", () => flash(true)); registerIDFCommand("espIdf.buildDevice", build); registerIDFCommand("espIdf.monitorDevice", createMonitor); - registerIDFCommand("espIdf.buildFlashMonitor", buildFlashAndMonitor); + registerIDFCommand("espIdf.buildFlashMonitor", () => + buildFlashAndMonitor(workspaceRoot) + ); registerIDFCommand("espIdf.monitorQemu", createQemuMonitor); registerIDFCommand("espIdf.buildApp", () => @@ -3859,7 +3903,7 @@ export async function activate(context: vscode.ExtensionContext) { registerIDFCommand("espIdf.selectFlashMethodAndFlash", () => { PreCheck.perform([openFolderCheck, webIdeCheck], async () => { - await selectFlashMethod(); + await selectFlashMethod(workspaceRoot); }); }); @@ -4299,6 +4343,7 @@ const flash = ( } if ( await startFlashing( + workspaceRoot, cancelToken, flashType, encryptPartitions, @@ -4352,256 +4397,6 @@ function createQemuMonitor() { }); } -const buildFlashAndMonitor = async (runMonitor: boolean = true) => { - PreCheck.perform([openFolderCheck], async () => { - const notificationMode = idfConf.readParameter( - "idf.notificationMode", - workspaceRoot - ) as string; - const ProgressLocation = - notificationMode === idfConf.NotificationMode.All || - notificationMode === idfConf.NotificationMode.Notifications - ? vscode.ProgressLocation.Notification - : vscode.ProgressLocation.Window; - - await vscode.window.withProgress( - { - cancellable: true, - location: ProgressLocation, - title: vscode.l10n.t("ESP-IDF: Building project"), - }, - async ( - progress: vscode.Progress<{ message: string; increment: number }>, - cancelToken: vscode.CancellationToken - ) => { - progress.report({ message: "Building project...", increment: 20 }); - const flashType = idfConf.readParameter("idf.flashType", workspaceRoot); - let canContinue = await buildCommand( - workspaceRoot, - cancelToken, - flashType - ); - if (!canContinue) { - return; - } - // Re route to ESP-IDF Web extension if using Codespaces or Browser - if (vscode.env.uiKind === vscode.UIKind.Web) { - vscode.commands.executeCommand(IDFWebCommandKeys.FlashAndMonitor); - return; - } - progress.report({ - message: "Flashing project into device...", - increment: 60, - }); - - let encryptPartitions = await isFlashEncryptionEnabled(workspaceRoot); - - let partitionToUse = idfConf.readParameter( - "idf.flashPartitionToUse", - workspaceRoot - ) as ESP.BuildType; - - if ( - partitionToUse && - !["app", "bootloader", "partition-table"].includes(partitionToUse) - ) { - partitionToUse = undefined; - } - - canContinue = await startFlashing( - cancelToken, - flashType, - encryptPartitions, - partitionToUse - ); - if (!canContinue) { - return; - } - if (runMonitor) { - progress.report({ - message: "Launching monitor...", - increment: 10, - }); - if (IDFMonitor.terminal) { - IDFMonitor.terminal.sendText(ESP.CTRL_RBRACKET); - } - await createMonitor(); - } - } - ); - }); -}; - -async function selectFlashMethod() { - let curflashType = idfConf.readParameter( - "idf.flashType", - workspaceRoot - ) as ESP.FlashType; - let newFlashType = (await vscode.window.showQuickPick( - Object.keys(ESP.FlashType), - { - ignoreFocusOut: true, - placeHolder: vscode.l10n.t( - "Select flash method, you can modify the choice later from 'settings.json' (idf.flashType)" - ), - } - )) as ESP.FlashType; - if (!newFlashType) { - return curflashType; - } - await idfConf.writeParameter( - "idf.flashType", - newFlashType, - vscode.ConfigurationTarget.WorkspaceFolder, - workspaceRoot - ); - vscode.window.showInformationMessage( - `Flash method changed to ${newFlashType}.` - ); - return newFlashType; -} - -async function startFlashing( - cancelToken: vscode.CancellationToken, - flashType: ESP.FlashType, - encryptPartitions: boolean, - partitionToUse?: ESP.BuildType -): Promise { - if (!flashType) { - flashType = await selectFlashMethod(); - } - - if (IDFMonitor.terminal) { - IDFMonitor.terminal.sendText(ESP.CTRL_RBRACKET); - const monitorDelay = idfConf.readParameter( - "idf.monitorDelay", - workspaceRoot - ) as number; - await utils.sleep(monitorDelay); - } - - if (encryptPartitions) { - const encryptionValidationResult = await checkFlashEncryption( - flashType, - workspaceRoot - ); - if (!encryptionValidationResult.success) { - if ( - encryptionValidationResult.resultType === - FlashCheckResultType.ErrorEfuseNotSet - ) { - encryptPartitions = false; - } else { - return; - } - } - } - - const port = await idfConf.readSerialPort(workspaceRoot, false); - if (!port) { - Logger.warnNotify( - vscode.l10n.t( - "No serial port found for current IDF_TARGET: {0}", - await getIdfTargetFromSdkconfig(workspaceRoot) - ) - ); - return false; - } - const flashBaudRate = idfConf.readParameter( - "idf.flashBaudRate", - workspaceRoot - ); - const canFlash = await verifyCanFlash( - flashBaudRate, - port, - flashType, - workspaceRoot - ); - if (!canFlash) { - return false; - } - - if (flashType === ESP.FlashType.JTAG) { - const currOpenOcdVersion = await openOCDManager.version(); - const openOCDVersionIsValid = PreCheck.openOCDVersionValidator( - "v0.10.0-esp32-20201125", - currOpenOcdVersion - ); - if (!openOCDVersionIsValid) { - Logger.infoNotify( - `Minimum OpenOCD version v0.10.0-esp32-20201125 is required while you have ${currOpenOcdVersion} version installed` - ); - return; - } - - // If no adapter binding is set and multiple boards are connected, block JTAG flash to avoid - // flashing the wrong device. User should select a connected board via Set Target first. - const storedSerial = getStoredAdapterSerial(workspaceRoot); - const customExtraVars = idfConf.readParameter( - "idf.customExtraVars", - workspaceRoot - ) as { [key: string]: string }; - const storedLocation = - customExtraVars && customExtraVars["OPENOCD_USB_ADAPTER_LOCATION"] - ? customExtraVars["OPENOCD_USB_ADAPTER_LOCATION"] - : undefined; - - if (!storedSerial && !storedLocation) { - try { - const devkitsCmd = new DevkitsCommand(workspaceRoot); - const scriptPath = await devkitsCmd.getScriptPath(currOpenOcdVersion); - if (scriptPath) { - const devkitsOutput = await devkitsCmd.runDevkitsScript( - currOpenOcdVersion, - { silent: true } - ); - if (devkitsOutput) { - const parsed = JSON.parse(devkitsOutput); - const boards = - parsed && Array.isArray(parsed.boards) ? parsed.boards : []; - if (boards.length > 1) { - const msg = vscode.l10n.t( - "Multiple connected boards were detected, but no OpenOCD adapter is selected (serial/location). To avoid flashing the wrong device, set the target for the connected board and rebuild." - ); - await showInfoNotificationWithAction( - msg, - vscode.l10n.t("Set Target"), - () => - vscode.commands.executeCommand(CommandKeys.SetEspressifTarget) - ); - return false; - } - } - } - } catch (e) { - // Best-effort only: detection failures must not block JTAG flashing. - Logger.info( - `Skipping connected-board detection before JTAG flash: ${ - e && (e as any).message ? (e as any).message : e - }` - ); - } - } - - return await jtagFlashCommand(workspaceRoot); - } else { - const idfPathDir = idfConf.readParameter( - "idf.espIdfPath", - workspaceRoot - ) as string; - return await flashCommand( - cancelToken, - flashBaudRate, - idfPathDir, - port, - workspaceRoot, - flashType, - encryptPartitions, - partitionToUse - ); - } -} - async function createEspIdfTerminal( extensionPath: string, terminalName: string, @@ -4877,7 +4672,7 @@ class IdfDebugConfigurationProvider "Build" ); if (startBuild === "Build") { - await buildFlashAndMonitor(false); + await buildFlashAndMonitor(workspaceRoot, false); return; } } diff --git a/src/flash/eraseFlashJtag.ts b/src/flash/eraseFlashJtag.ts new file mode 100644 index 000000000..33e065c01 --- /dev/null +++ b/src/flash/eraseFlashJtag.ts @@ -0,0 +1,90 @@ +/* + * Project: ESP-IDF VSCode Extension + * File Created: Friday, 5th December 2025 1:14:59 pm + * Copyright 2025 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Uri } from "vscode"; +import { OutputChannel } from "../logger/outputChannel"; +import { OpenOCDManager } from "../espIdf/openOcd/openOcdManager"; +import { readParameter } from "../idfConfiguration"; +import { Logger } from "../logger/logger"; +import { TCLClient } from "../espIdf/openOcd/tcl/tclClient"; +import { PreCheck } from "../utils"; + +export async function jtagEraseFlashCommand(workspaceFolder: Uri) { + const openOCDManager = OpenOCDManager.init(); + const currOpenOcdVersion = await openOCDManager.version(); + const openOCDVersionIsValid = PreCheck.openOCDVersionValidator( + "v0.10.0-esp32-20201125", + currOpenOcdVersion + ); + if (!openOCDVersionIsValid) { + Logger.infoNotify( + `Minimum OpenOCD version v0.10.0-esp32-20201125 is required while you have ${currOpenOcdVersion} version installed` + ); + return; + } + const isOpenOCDLaunched = await openOCDManager.promptUserToLaunchOpenOCDServer(); + if (!isOpenOCDLaunched) { + const errStr = + "Can't perform JTAG flash, because OpenOCD server is not running!"; + OutputChannel.appendLineAndShow(errStr, "Flash"); + Logger.warnNotify(errStr); + return false; + } + const host = readParameter("openocd.tcl.host", workspaceFolder); + const port = readParameter("openocd.tcl.port", workspaceFolder); + const client = new TCLClient({ host, port }); + await eraseFlashTelnetCommand( + client, + "halt; flash erase_sector 0 0 last; reset" + ); +} + +export async function eraseFlashTelnetCommand( + client: TCLClient, + command: string, + ...args: string[] +) { + const fullCommand = `${command} ${args.map((arg) => `"${arg}"`).join(" ")}`; + + return new Promise((resolve, reject) => { + client + .on("response", (data: Buffer) => { + const response = data + .toString() + .replace(TCLClient.DELIMITER, "") + .trim(); + if (response === "") { + return resolve(response); + } + if (response.indexOf("erased sectors ") === -1) { + return reject( + `Failed to erase flash from the device (JTAG), please try again [got response: '${response}', expecting: 'erased sectors ']` + ); + } + + //Flash successful when response includes erased sectors ... + return resolve(response); + }) + .on("error", (err) => { + reject( + "Failed to erase flash (via JTAG), due to some unknown error in tcl, please try to relaunch open-ocd" + ); + }) + .sendCommand(fullCommand); + }); +} diff --git a/src/flash/eraseFlashTask.ts b/src/flash/eraseFlashTask.ts index 4b3bb88e8..de31c90ac 100644 --- a/src/flash/eraseFlashTask.ts +++ b/src/flash/eraseFlashTask.ts @@ -39,9 +39,6 @@ import { TaskManager } from "../taskManager"; import { ESP } from "../config"; import { getVirtualEnvPythonPath } from "../pythonManager"; import { IDFMonitor } from "../espIdf/monitor"; -import { Logger } from "../logger/logger"; -import { OutputChannel } from "../logger/outputChannel"; -import { getIdfTargetFromSdkconfig } from "../workspaceConfig"; import { OutputCapturingExecution } from "../taskManager/customExecution"; export class EraseFlashTask { @@ -50,7 +47,6 @@ export class EraseFlashTask { private flashScriptPath: string; private idfPathDir: string; private modifiedEnv: { [key: string]: string }; - private processOptions: ProcessExecutionOptions; constructor(workspaceUri: Uri) { this.currentWorkspace = workspaceUri; @@ -106,10 +102,6 @@ export class EraseFlashTask { : TaskRevealKind.Silent; this.modifiedEnv = await appendIdfAndToolsToPath(this.currentWorkspace); - this.processOptions = { - cwd: process.cwd(), - env: this.modifiedEnv, - }; const eraseExecution = this._eraseExecution(pythonBinPath, port); const erasePresentationOptions = { @@ -119,7 +111,7 @@ export class EraseFlashTask { panel: TaskPanelKind.Shared, } as TaskPresentationOptions; - await TaskManager.addTask( + TaskManager.addTask( { type: "esp-idf", command: "ESP-IDF Erase Flash", @@ -134,13 +126,17 @@ export class EraseFlashTask { return eraseExecution; } - private async _eraseExecution(pythonBinPath: string, port: string) { + private _eraseExecution(pythonBinPath: string, port: string) { this.erasing(true); const args = [this.flashScriptPath, "-p", port, "erase_flash"]; + const processOptions = { + cwd: this.currentWorkspace.fsPath || process.cwd(), + env: this.modifiedEnv, + }; return OutputCapturingExecution.create( pythonBinPath, args, - this.processOptions + processOptions ); } } diff --git a/src/flash/jtagCmd.ts b/src/flash/jtagCmd.ts index 7d9eb208c..09a722603 100644 --- a/src/flash/jtagCmd.ts +++ b/src/flash/jtagCmd.ts @@ -81,9 +81,6 @@ export async function jtagFlashCommandMain(workspace: Uri) { const msg = "⚡️ Flashed Successfully (JTAG)"; OutputChannel.appendLineAndShow(msg, "Flash"); Logger.infoNotify(msg); - const closingOpenOCDMsg = "Closing OpenOCD server connection..."; - OutputChannel.appendLineAndShow(closingOpenOCDMsg, "Flash"); - OpenOCDManager.init().stop(); } export async function jtagFlashCommand(workspace: Uri) { diff --git a/src/flash/startFlashing.ts b/src/flash/startFlashing.ts new file mode 100644 index 000000000..f713ac30c --- /dev/null +++ b/src/flash/startFlashing.ts @@ -0,0 +1,157 @@ +/* + * Project: ESP-IDF VSCode Extension + * File Created: Wednesday, 26th November 2025 11:34:53 am + * Copyright 2025 Espressif Systems (Shanghai) CO LTD + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + CancellationToken, + ConfigurationTarget, + l10n, + Uri, + window, +} from "vscode"; +import { + readParameter, + readSerialPort, + writeParameter, +} from "../idfConfiguration"; +import { ESP } from "../config"; +import { IDFMonitor } from "../espIdf/monitor"; +import { PreCheck, sleep } from "../utils"; +import { + checkFlashEncryption, + FlashCheckResultType, +} from "./verifyFlashEncryption"; +import { Logger } from "../logger/logger"; +import { getIdfTargetFromSdkconfig } from "../workspaceConfig"; +import { verifyCanFlash } from "./flashCmd"; +import { OpenOCDManager } from "../espIdf/openOcd/openOcdManager"; +import { jtagFlashCommand } from "./jtagCmd"; +import { flashCommand } from "./uartFlash"; + +export async function selectFlashMethod(workspaceFolderUri: Uri) { + let curflashType = readParameter( + "idf.flashType", + workspaceFolderUri + ) as ESP.FlashType; + let newFlashType = (await window.showQuickPick(Object.keys(ESP.FlashType), { + ignoreFocusOut: true, + placeHolder: l10n.t( + "Select flash method, you can modify the choice later from 'settings.json' (idf.flashType)" + ), + })) as ESP.FlashType; + if (!newFlashType) { + return curflashType; + } + await writeParameter( + "idf.flashType", + newFlashType, + ConfigurationTarget.WorkspaceFolder, + workspaceFolderUri + ); + window.showInformationMessage(`Flash method changed to ${newFlashType}.`); + return newFlashType; +} + +export async function startFlashing( + workspaceFolderUri: Uri, + cancelToken: CancellationToken, + flashType: ESP.FlashType, + encryptPartitions: boolean, + partitionToUse?: ESP.BuildType +): Promise { + if (!flashType) { + flashType = await selectFlashMethod(workspaceFolderUri); + } + + if (IDFMonitor.terminal) { + IDFMonitor.terminal.sendText(ESP.CTRL_RBRACKET); + const monitorDelay = readParameter( + "idf.monitorDelay", + workspaceFolderUri + ) as number; + await sleep(monitorDelay); + } + + if (encryptPartitions) { + const encryptionValidationResult = await checkFlashEncryption( + flashType, + workspaceFolderUri + ); + if (!encryptionValidationResult.success) { + if ( + encryptionValidationResult.resultType === + FlashCheckResultType.ErrorEfuseNotSet + ) { + encryptPartitions = false; + } else { + return; + } + } + } + + const port = await readSerialPort(workspaceFolderUri, false); + if (!port) { + Logger.warnNotify( + l10n.t( + "No serial port found for current IDF_TARGET: {0}", + await getIdfTargetFromSdkconfig(workspaceFolderUri) + ) + ); + return false; + } + const flashBaudRate = readParameter("idf.flashBaudRate", workspaceFolderUri); + const canFlash = await verifyCanFlash( + flashBaudRate, + port, + flashType, + workspaceFolderUri + ); + if (!canFlash) { + return false; + } + + if (flashType === ESP.FlashType.JTAG) { + const openOCDManager = OpenOCDManager.init(); + const currOpenOcdVersion = await openOCDManager.version(); + const openOCDVersionIsValid = PreCheck.openOCDVersionValidator( + "v0.10.0-esp32-20201125", + currOpenOcdVersion + ); + if (!openOCDVersionIsValid) { + Logger.infoNotify( + `Minimum OpenOCD version v0.10.0-esp32-20201125 is required while you have ${currOpenOcdVersion} version installed` + ); + return; + } + return await jtagFlashCommand(workspaceFolderUri); + } else { + const idfPathDir = readParameter( + "idf.espIdfPath", + workspaceFolderUri + ) as string; + return await flashCommand( + cancelToken, + flashBaudRate, + idfPathDir, + port, + workspaceFolderUri, + flashType, + encryptPartitions, + partitionToUse + ); + } +} diff --git a/src/flash/uartFlash.ts b/src/flash/uartFlash.ts index 6d2365574..dfd4ba766 100644 --- a/src/flash/uartFlash.ts +++ b/src/flash/uartFlash.ts @@ -84,9 +84,11 @@ export async function uartFlashCommandMain( const flashResult = await TaskManager.runTasksWithBoolean(); if (!cancelToken.isCancellationRequested) { - const msg = "Flash Done ⚡️"; - OutputChannel.appendLineAndShow(msg, "Flash"); - Logger.infoNotify(msg); + if (flashResult) { + const msg = "Flash Done ⚡️"; + OutputChannel.appendLineAndShow(msg, "Flash"); + Logger.infoNotify(msg); + } TaskManager.disposeListeners(); } FlashTask.isFlashing = false; diff --git a/src/newProject/newProjectPanel.ts b/src/newProject/newProjectPanel.ts index 55e5cddb9..dcf76c13f 100644 --- a/src/newProject/newProjectPanel.ts +++ b/src/newProject/newProjectPanel.ts @@ -195,6 +195,7 @@ export class NewProjectPanel { serialPortList: newProjectArgs.serialPortList, openOcdConfigFiles: defConfigFiles, templates: newProjectArgs.templates, + pathSep: path.sep, }); } break; diff --git a/src/setup/setupInit.ts b/src/setup/setupInit.ts index 3887d7d1e..b19458826 100644 --- a/src/setup/setupInit.ts +++ b/src/setup/setupInit.ts @@ -107,8 +107,13 @@ export async function checkPreviousInstall( gitPath = gitPathFromJson; } existingIdfSetups = await loadIdfSetupsFromEspIdfJson(toolsPath); - if (process.env.IDF_TOOLS_PATH && toolsPath !== process.env.IDF_TOOLS_PATH) { - const systemIdfSetups = await loadIdfSetupsFromEspIdfJson(process.env.IDF_TOOLS_PATH); + if ( + process.env.IDF_TOOLS_PATH && + toolsPath !== process.env.IDF_TOOLS_PATH + ) { + const systemIdfSetups = await loadIdfSetupsFromEspIdfJson( + process.env.IDF_TOOLS_PATH + ); existingIdfSetups = [...existingIdfSetups, ...systemIdfSetups]; } } @@ -169,10 +174,7 @@ export async function getSetupInitialValues( pythonVersions && pythonVersions.length > 0; - const canAccessCMake = await utils.isBinInPath( - "cmake", - process.env - ); + const canAccessCMake = await utils.isBinInPath("cmake", process.env); if (canAccessCMake === "") { setupInitArgs.onReqPkgs = setupInitArgs.onReqPkgs @@ -180,10 +182,7 @@ export async function getSetupInitialValues( : ["cmake"]; } - const canAccessNinja = await utils.isBinInPath( - "ninja", - process.env - ); + const canAccessNinja = await utils.isBinInPath("ninja", process.env); if (canAccessNinja === "") { setupInitArgs.onReqPkgs = setupInitArgs.onReqPkgs @@ -242,17 +241,11 @@ export async function isCurrentInstallValid(workspaceFolder: Uri) { ); let extraReqPaths = []; if (process.platform !== "win32") { - const canAccessCMake = await utils.isBinInPath( - "cmake", - process.env - ); + const canAccessCMake = await utils.isBinInPath("cmake", process.env); if (!canAccessCMake) { extraReqPaths.push("cmake"); } - const canAccessNinja = await utils.isBinInPath( - "ninja", - process.env - ); + const canAccessNinja = await utils.isBinInPath("ninja", process.env); if (!canAccessNinja) { extraReqPaths.push("ninja"); } @@ -290,7 +283,7 @@ export async function isCurrentInstallValid(workspaceFolder: Uri) { workspaceFolder ) as string; } - const isPyEnvValid = await checkPyVenv(pythonBinPath, espIdfPath); + const isPyEnvValid = await checkPyVenv(pythonBinPath, espIdfPath, toolsPath); return isPyEnvValid; } diff --git a/src/setup/setupValidation/espIdfSetup.ts b/src/setup/setupValidation/espIdfSetup.ts index ee888c4ce..dd107dea1 100644 --- a/src/setup/setupValidation/espIdfSetup.ts +++ b/src/setup/setupValidation/espIdfSetup.ts @@ -109,7 +109,11 @@ export async function checkIdfSetup( ); } - const pyEnvReqs = await checkPyVenv(virtualEnvPython, setupConf.idfPath); + const pyEnvReqs = await checkPyVenv( + virtualEnvPython, + setupConf.idfPath, + setupConf.toolsPath + ); return pyEnvReqs; } catch (error) { const msg = diff --git a/src/setup/setupValidation/pythonEnv.ts b/src/setup/setupValidation/pythonEnv.ts index 3c94cd13c..83ff6c747 100644 --- a/src/setup/setupValidation/pythonEnv.ts +++ b/src/setup/setupValidation/pythonEnv.ts @@ -20,7 +20,7 @@ import { pathExists } from "fs-extra"; import { join } from "path"; import { startPythonReqsProcess } from "../../utils"; -export async function checkPyVenv(pyVenvPath: string, espIdfPath: string) { +export async function checkPyVenv(pyVenvPath: string, espIdfPath: string, espIdfToolsPath: string): Promise { const pyExists = await pathExists(pyVenvPath); if (!pyExists) { return false; @@ -43,6 +43,7 @@ export async function checkPyVenv(pyVenvPath: string, espIdfPath: string) { const reqsResults = await startPythonReqsProcess( pyVenvPath, espIdfPath, + espIdfToolsPath, requirements ); if (reqsResults.indexOf("are not satisfied") > -1) { diff --git a/src/support/checkEspIdfRequirements.ts b/src/support/checkEspIdfRequirements.ts index 49a8e8ec9..6c53f5d88 100644 --- a/src/support/checkEspIdfRequirements.ts +++ b/src/support/checkEspIdfRequirements.ts @@ -27,10 +27,18 @@ export async function checkEspIdfRequirements( ) { try { let requirementsPath: string; - requirementsPath = join(reportedResult.configurationSettings.espIdfPath, "tools", "requirements", "requirements.core.txt"); + requirementsPath = join( + reportedResult.configurationSettings.espIdfPath, + "tools", + "requirements", + "requirements.core.txt" + ); const coreRequirementsExists = await pathExists(requirementsPath); if (!coreRequirementsExists) { - requirementsPath = join(reportedResult.configurationSettings.espIdfPath, "requirements.txt"); + requirementsPath = join( + reportedResult.configurationSettings.espIdfPath, + "requirements.txt" + ); const requirementsExists = await pathExists(requirementsPath); if (!requirementsExists) { throw new Error("Requirements doesn't exists."); @@ -63,9 +71,23 @@ export async function checkRequirements( Object.assign({}, process.env) ); modifiedEnv.IDF_PATH = reportedResult.configurationSettings.espIdfPath; + const majorMinorMatches = reportedResult.espIdfVersion.result.match(/([0-9]+\.[0-9]+).*/); + const espIdfVersion = + majorMinorMatches && majorMinorMatches.length > 0 + ? majorMinorMatches[1] + : "x.x"; + const constrainsFile = join( + reportedResult.configurationSettings.toolsPath, + `espidf.constraints.v${espIdfVersion}.txt` + ); + let checkPyArgs = [checkPythonDepsScript, "-r", requirementsPath]; + const constrainsFileExists = await pathExists(constrainsFile); + if (constrainsFileExists) { + checkPyArgs = checkPyArgs.concat(["--constraint", constrainsFile]); + } const requirementsResult = await execChildProcess( reportedResult.configurationSettings.pythonBinPath, - [checkPythonDepsScript, "-r", requirementsPath], + checkPyArgs, context.extensionPath, { env: modifiedEnv, cwd: context.extensionPath } ); diff --git a/src/taskManager.ts b/src/taskManager.ts index efddbbfcf..73a81da8a 100644 --- a/src/taskManager.ts +++ b/src/taskManager.ts @@ -43,7 +43,16 @@ export class TaskManager { | vscode.CustomExecution, problemMatchers: string | string[], presentationOptions: vscode.TaskPresentationOptions - ) { + ): void { + // Check if a task with the same taskId already exists + const existingTaskIndex = TaskManager.tasks.findIndex( + (task) => task.definition.taskId === taskDefinition.taskId + ); + if (existingTaskIndex !== -1) { + // Task with this taskId already exists, skip adding + return; + } + const newTask: vscode.Task = new vscode.Task( taskDefinition, scope, @@ -54,19 +63,6 @@ export class TaskManager { ); newTask.presentationOptions = presentationOptions; TaskManager.tasks.push(newTask); - return new Promise((resolve, reject) => { - const taskEndListener = vscode.tasks.onDidEndTask(async (e) => { - if ( - e.execution && - e.execution.task.definition.taskId.indexOf( - newTask.definition.taskId - ) !== -1 - ) { - return resolve(); - } - }); - TaskManager.disposables.push(taskEndListener); - }); } public static disposeListeners() { @@ -74,6 +70,7 @@ export class TaskManager { disposable.dispose(); } TaskManager.disposables = []; + TaskManager.tasks = []; } public static cancelTasks() { @@ -101,7 +98,6 @@ export class TaskManager { lastExecution.task.definition.taskId ) !== -1 ) { - // Store the result regardless of success/failure const taskResult = { taskId: lastExecution.task.definition.taskId, exitCode: e.exitCode, @@ -109,7 +105,17 @@ export class TaskManager { }; TaskManager.taskResults.push(taskResult); + // Remove the completed task from the array (regardless of success or failure) + const taskIndex = TaskManager.tasks.findIndex( + (task) => + task.definition.taskId === lastExecution.task.definition.taskId + ); + if (taskIndex !== -1) { + TaskManager.tasks.splice(taskIndex, 1); + } + if (e.exitCode !== 0) { + e.execution.terminate(); this.cancelTasks(); this.disposeListeners(); return reject( @@ -119,8 +125,8 @@ export class TaskManager { ); } e.execution.terminate(); - TaskManager.tasks.splice(0, 1); if (TaskManager.tasks.length === 0) { + TaskManager.tasks = []; return resolve(); } else { lastExecution = await vscode.tasks.executeTask( diff --git a/src/utils.ts b/src/utils.ts index 0defc5451..81df26f5b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1406,6 +1406,7 @@ export async function isBinInPath( export async function startPythonReqsProcess( pythonBinPath: string, espIdfPath: string, + espIdfToolsPath: string, requirementsPath: string ) { const reqFilePath = path.join( @@ -1417,9 +1418,24 @@ export async function startPythonReqsProcess( Object.assign({}, process.env) ); modifiedEnv.IDF_PATH = espIdfPath; + const fullEspIdfVersion = await getEspIdfFromCMake(espIdfPath); + const majorMinorMatches = fullEspIdfVersion.match(/([0-9]+\.[0-9]+).*/); + const espIdfVersion = + majorMinorMatches && majorMinorMatches.length > 0 + ? majorMinorMatches[1] + : "x.x"; + const constrainsFile = path.join( + espIdfToolsPath, + `espidf.constraints.v${espIdfVersion}.txt` + ); + const constrainsFileExists = await pathExists(constrainsFile); + let checkPyArgs = [reqFilePath, "-r", requirementsPath]; + if (constrainsFileExists) { + checkPyArgs = checkPyArgs.concat(["--constraint", constrainsFile]); + } return execChildProcess( pythonBinPath, - [reqFilePath, "-r", requirementsPath], + checkPyArgs, extensionContext.extensionPath, OutputChannel.init(), { env: modifiedEnv, cwd: extensionContext.extensionPath } diff --git a/src/views/menuconfig/Menuconfig.vue b/src/views/menuconfig/Menuconfig.vue index 1be91034a..b7ba42946 100644 --- a/src/views/menuconfig/Menuconfig.vue +++ b/src/views/menuconfig/Menuconfig.vue @@ -46,7 +46,9 @@ function filterItems(items: Menu[], searchString: string) { const filteredChildren = filterItems(item.children, searchString); if (filteredChildren.length > 0) { const newItem = Object.assign({}, item); - newItem.children = filteredChildren; + if (item.type !== "choice") { + newItem.children = filteredChildren; + } filteredItems.push(newItem); } } @@ -71,7 +73,9 @@ function onScroll() { const configList = document.querySelector(".config-list") as HTMLElement; if (!configList) return; - const sections = Array.from(document.querySelectorAll(".submenu.form-group")) as HTMLElement[]; + const sections = Array.from( + document.querySelectorAll(".submenu.form-group") + ) as HTMLElement[]; if (sections.length === 0) return; const scrollTop = configList.scrollTop; @@ -102,28 +106,28 @@ const handleScroll = (event) => { function handleMenuSelect(value: string) { store.selectedMenu = value; - const secNew = document.querySelector('#' + value) as HTMLElement; + const secNew = document.getElementById(value); if (secNew) { - secNew.scrollIntoView({ behavior: 'auto', block: 'start' }); + secNew.scrollIntoView({ behavior: "auto", block: "start" }); } } function handleMouseDown(e: MouseEvent) { isDragging.value = true; - document.body.style.cursor = 'col-resize'; - document.body.style.userSelect = 'none'; + document.body.style.cursor = "col-resize"; + document.body.style.userSelect = "none"; e.preventDefault(); } function handleMouseMove(e: MouseEvent) { if (!isDragging.value) return; - - const mainElement = document.getElementById('main'); + + const mainElement = document.getElementById("main"); if (!mainElement) return; const mainRect = mainElement.getBoundingClientRect(); const newWidth = e.clientX - mainRect.left; - + if (newWidth >= minTreeWidth && newWidth <= maxTreeWidth) { treeWidth.value = newWidth; } @@ -132,8 +136,8 @@ function handleMouseMove(e: MouseEvent) { function handleMouseUp() { if (!isDragging.value) return; isDragging.value = false; - document.body.style.cursor = ''; - document.body.style.userSelect = ''; + document.body.style.cursor = ""; + document.body.style.userSelect = ""; } onMounted(() => { @@ -142,8 +146,8 @@ onMounted(() => { if (scrollableDiv) { scrollableDiv.addEventListener("scroll", handleScroll); } - window.addEventListener('mousemove', handleMouseMove); - window.addEventListener('mouseup', handleMouseUp); + window.addEventListener("mousemove", handleMouseMove); + window.addEventListener("mouseup", handleMouseUp); }); onUnmounted(() => { @@ -151,8 +155,8 @@ onUnmounted(() => { if (scrollableDiv) { scrollableDiv.removeEventListener("scroll", handleScroll); } - window.removeEventListener('mousemove', handleMouseMove); - window.removeEventListener('mouseup', handleMouseUp); + window.removeEventListener("mousemove", handleMouseMove); + window.removeEventListener("mouseup", handleMouseUp); }); @@ -160,13 +164,20 @@ onUnmounted(() => {
-
+
-
{ \ No newline at end of file diff --git a/src/views/menuconfig/components/StringInput.vue b/src/views/menuconfig/components/StringInput.vue index a9c8f009b..cde164f8a 100644 --- a/src/views/menuconfig/components/StringInput.vue +++ b/src/views/menuconfig/components/StringInput.vue @@ -1,14 +1,16 @@