Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,23 @@ private String generatePartOfConfigOptionsForVoltage()
}

private String generatePartOfConfigOptionsForBoard()
{
var result = new StringBuilder();
for (Object config : resolveBoardConfigFiles())
{
result.append(String.format("-f %s ", config)); //$NON-NLS-1$
}
return result.toString();
}

/**
* Resolves the OpenOCD board configuration files for the active launch target. These config files register the
* board-specific OpenOCD commands (e.g. {@code program_esp_bins}). An empty result means no usable board is
* selected for the active target.
*
* @return the list of board config files, never {@code null}
*/
private List<String> resolveBoardConfigFiles()
{
var parser = new EspConfigParser();
ILaunchTarget activeILaunchTarget = getActiveLaunchTarget().orElseGet(() -> ILaunchTarget.NULL_TARGET);
Expand All @@ -76,17 +93,20 @@ private String generatePartOfConfigOptionsForBoard()
int idx = board.lastIndexOf(" [usb://"); //$NON-NLS-1$
String boardKey = (idx != -1) ? board.substring(0, idx) : board;
List<Board> boards = parser.getBoardsForTarget(targetName);
List<String> boardConfigs = boards.stream().filter(b -> b.name().equals(boardKey)).findFirst()
.map(Board::config_files).orElse(List.of());
var result = new StringBuilder();
if (boardConfigs != null)
{
for (Object config : boardConfigs)
{
result.append(String.format("-f %s ", config)); //$NON-NLS-1$
}
}
return result.toString();
return boards.stream().filter(b -> b.name().equals(boardKey)).findFirst().map(Board::config_files)
.orElse(List.of());
}

/**
* Checks whether a board configuration is resolvable for the active launch target. When this returns
* {@code false}, OpenOCD would start without a board configuration and board-specific commands such as
* {@code program_esp_bins} would not be available, so the debug session cannot succeed.
*
* @return {@code true} if a board is selected and its OpenOCD config files were found, {@code false} otherwise
*/
public static boolean isBoardConfigResolvable()
{
return !new JtagVariableResolver().resolveBoardConfigFiles().isEmpty();
}

}
6 changes: 6 additions & 0 deletions bundles/com.espressif.idf.debug.gdbjtag.openocd/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@
id="com.espressif.idf.debug.gdbjtag.openocd.openocdStatusHandler"
plugin="com.espressif.idf.debug.gdbjtag.openocd">
</statusHandler>
<statusHandler
class="com.espressif.idf.debug.gdbjtag.openocd.ui.BoardNotSelectedStatusHandler"
code="6001"
id="com.espressif.idf.debug.gdbjtag.openocd.boardNotSelectedStatusHandler"
plugin="com.espressif.idf.debug.gdbjtag.openocd">
</statusHandler>
</extension>
<extension
point="org.eclipse.debug.ui.launchConfigurationTabs">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,15 @@
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.IStatusHandler;
import org.eclipse.debug.core.model.ISourceLocator;
import org.eclipse.embedcdt.core.StringUtils;
import org.eclipse.embedcdt.debug.gdbjtag.core.DebugUtils;
import org.eclipse.embedcdt.debug.gdbjtag.core.dsf.AbstractGnuMcuLaunchConfigurationDelegate;
import org.eclipse.embedcdt.debug.gdbjtag.core.dsf.GnuMcuServerServicesLaunchSequence;

import com.espressif.idf.core.logging.Logger;
import com.espressif.idf.core.variable.JtagVariableResolver;
import com.espressif.idf.debug.gdbjtag.openocd.Activator;
import com.espressif.idf.debug.gdbjtag.openocd.Configuration;
import com.espressif.idf.debug.gdbjtag.openocd.ConfigurationAttributes;
Expand All @@ -80,6 +82,12 @@ public class LaunchConfigurationDelegate extends AbstractGnuMcuLaunchConfigurati

private static final ThreadLocal<LaunchOptions> pendingLaunchOptions = new ThreadLocal<>();

/**
* Status code used to route the "no board selected" failure through {@code BoardNotSelectedStatusHandler}. It must
* match the {@code code} of the corresponding {@code statusHandler} extension in plugin.xml.
*/
public static final int BOARD_NOT_SELECTED_STATUS_CODE = 6001;

ILaunchConfiguration fConfig = null;
@SuppressWarnings("unused")
private boolean fIsNonStopSession = false;
Expand Down Expand Up @@ -606,10 +614,54 @@ protected IPath checkBinaryDetails(final ILaunchConfiguration config) throws Cor
if (configOptions.isEmpty())
throw new CoreException(
new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Missing mandatory configuration."));

// Abort early with a clear, actionable message when no board is selected for the active target. Otherwise
// OpenOCD would start without a board configuration and fail later with the cryptic
// "invalid command name \"program_esp_bins\"" error.
if (!isBoardConfigured(config))
{
IStatus status = new Status(IStatus.OK, Activator.PLUGIN_ID, BOARD_NOT_SELECTED_STATUS_CODE,
"No board is selected for the debug target.", null); //$NON-NLS-1$
IStatusHandler handler = DebugPlugin.getDefault().getStatusHandler(status);
if (handler != null)
{
handler.handleStatus(status, null);
}
throw new DebugException(Status.OK_STATUS);
}
Comment on lines +618 to +631

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validate board selection for attach launches too.

checkBinaryDetails() is called only under if (!attach) (Lines 339-340). An attach launch with “Start OpenOCD locally” enabled therefore bypasses this guard and can still start OpenOCD without a board configuration. Extract this check and run it whenever the server is started, before the attach branch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/LaunchConfigurationDelegate.java`
around lines 618 - 631, Extract the board-selection validation currently
embedded in checkBinaryDetails() into a reusable check, then invoke it whenever
the server is started, before branching on attach versus non-attach launches.
Ensure attach launches with local OpenOCD enabled also call
isBoardConfigured(config) and report the existing BOARD_NOT_SELECTED_STATUS_CODE
status before starting the server, while preserving the current behavior for
other launch paths.

}
return super.checkBinaryDetails(config);
}

/**
* Determines whether a board configuration is available for the given launch configuration. A board is considered
* configured when it is resolvable from the active launch target, or when the (possibly manually edited) resolved
* Config options already reference a board configuration file. Without either, OpenOCD would start without the
* board-specific commands (e.g. {@code program_esp_bins}) and the debug session would fail.
*
* @param config the debug launch configuration
* @return {@code true} if a board configuration is available, {@code false} otherwise
*/
private boolean isBoardConfigured(ILaunchConfiguration config)
{
if (JtagVariableResolver.isBoardConfigResolvable())
{
return true;
}

// Fall back to inspecting the resolved Config options for a manually configured board file.
try
{
String resolvedOptions = Configuration.resolveAll(Configuration.getGdbServerOtherConfig(config), config);
return resolvedOptions != null && resolvedOptions.contains("board/"); //$NON-NLS-1$
}
Comment on lines +652 to +657

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Accept Windows paths in manual board-config detection.

Both fallbacks require the literal board/. A valid manually configured Windows path such as ...\board\esp32.cfg is treated as unconfigured and blocked. Normalize separators before matching.

  • bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/LaunchConfigurationDelegate.java#L652-L657: normalize resolvedOptions before checking for board/.
  • bundles/com.espressif.idf.launch.serial.core/src/com/espressif/idf/launch/serial/internal/SerialFlashLaunchConfigDelegate.java#L297-L305: normalize resolved before checking for board/.
Proposed fix
- return resolvedOptions != null && resolvedOptions.contains("board/");
+ return resolvedOptions != null
+         && resolvedOptions.replace('\\', '/').contains("board/");
- return resolved != null && resolved.contains("board/");
+ return resolved != null && resolved.replace('\\', '/').contains("board/");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Fall back to inspecting the resolved Config options for a manually configured board file.
try
{
String resolvedOptions = Configuration.resolveAll(Configuration.getGdbServerOtherConfig(config), config);
return resolvedOptions != null && resolvedOptions.contains("board/"); //$NON-NLS-1$
}
// Fall back to inspecting the resolved Config options for a manually configured board file.
try
{
String resolvedOptions = Configuration.resolveAll(Configuration.getGdbServerOtherConfig(config), config);
return resolvedOptions != null
&& resolvedOptions.replace('\\', '/').contains("board/"); //$NON-NLS-1$
}
📍 Affects 2 files
  • bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/LaunchConfigurationDelegate.java#L652-L657 (this comment)
  • bundles/com.espressif.idf.launch.serial.core/src/com/espressif/idf/launch/serial/internal/SerialFlashLaunchConfigDelegate.java#L297-L305
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@bundles/com.espressif.idf.debug.gdbjtag.openocd/src/com/espressif/idf/debug/gdbjtag/openocd/dsf/LaunchConfigurationDelegate.java`
around lines 652 - 657, Normalize path separators in the resolved manual
board-configuration options before checking for the “board/” marker. Apply this
change in LaunchConfigurationDelegate around Configuration.resolveAll and in
SerialFlashLaunchConfigDelegate around the resolved value, preserving the
existing detection behavior while accepting Windows backslash paths.

catch (CoreException e)
{
Activator.log(e);
}
return false;
}

@Override
protected Sequence getServicesSequence(DsfSession session, ILaunch launch, IProgressMonitor progressMonitor)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*******************************************************************************
* Copyright 2026 Espressif Systems (Shanghai) PTE LTD. All rights reserved.
* Use is subject to license terms.
*******************************************************************************/
package com.espressif.idf.debug.gdbjtag.openocd.ui;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.debug.core.IStatusHandler;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.launchbar.core.ILaunchBarManager;
import org.eclipse.launchbar.core.target.ILaunchTarget;
import org.eclipse.launchbar.ui.target.ILaunchTargetUIManager;
import org.eclipse.swt.widgets.Display;

import com.espressif.idf.core.logging.Logger;
import com.espressif.idf.debug.gdbjtag.openocd.Activator;

/**
* Shows a clear, actionable dialog when a debug session is started without a board selected for the active launch
* target. Confirming the dialog opens the launch target editor so the user can select a board.
*/
public class BoardNotSelectedStatusHandler implements IStatusHandler
{
@Override
public Object handleStatus(IStatus status, Object source) throws CoreException
{
Display.getDefault().asyncExec(() -> {
boolean isYes = MessageDialog.openConfirm(Display.getDefault().getActiveShell(),
Messages.BoardNotSelectedDialog_title, Messages.BoardNotSelectedDialog_message);
if (isYes)
{
editActiveLaunchTarget();
}
});
return null;
}

private void editActiveLaunchTarget()
{
try
{
ILaunchBarManager launchBarManager = Activator.getService(ILaunchBarManager.class);
ILaunchTargetUIManager targetUIManager = Activator.getService(ILaunchTargetUIManager.class);
if (launchBarManager == null || targetUIManager == null)
{
return;
}
ILaunchTarget target = launchBarManager.getActiveLaunchTarget();
if (target != null)
{
targetUIManager.editLaunchTarget(target);
}
}
catch (Exception e)
{
Logger.log(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ public class Messages
public static String ServerTimeoutErrorDialog_message;
public static String ServerTimeoutErrorDialog_customAreaMessage;

public static String BoardNotSelectedDialog_title;
public static String BoardNotSelectedDialog_message;

// ------------------------------------------------------------------------

static
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,3 +419,7 @@ OpenOCDConsole_ErrorGuideMessage=Please refer to the troubleshooting guide below
ServerTimeoutErrorDialog_customAreaMessage=To increase timeout time visit <a>the Espressif Preference Page</a>.
ServerTimeoutErrorDialog_message=Starting OpenOCD timed out. Try to increase the `GDB server launch timeout`
ServerTimeoutErrorDialog_title=Problem Occurred

## Board Not Selected Dialog ##
BoardNotSelectedDialog_title=No board selected
BoardNotSelectedDialog_message=No board is selected. Edit the launch target to select a board?
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public class Messages extends NLS
public static String SerialFlashLaunch_Resume;
public static String SerialPortNotFoundTitle;
public static String SerialPortNotFoundMsg;
public static String BoardNotSelectedTitle;
public static String BoardNotSelectedMsg;
static
{
// initialize resource bundle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import com.espressif.idf.core.util.IDFUtil;
import com.espressif.idf.core.util.RecheckConfigsHelper;
import com.espressif.idf.core.util.StringUtil;
import com.espressif.idf.core.variable.JtagVariableResolver;
import com.espressif.idf.launch.serial.util.ESPFlashUtil;
import com.espressif.idf.terminal.connector.serial.connector.SerialSettings;
import com.espressif.idf.terminal.connector.serial.launcher.SerialLauncherDelegate;
Expand Down Expand Up @@ -105,6 +106,13 @@ public void launch(ILaunchConfiguration configuration, String mode, ILaunch laun
}
if (ESPFlashUtil.isJtag())
{
// Abort early with a clear message when no board is selected. Otherwise OpenOCD would run without a board
// configuration and fail with the cryptic "invalid command name \"program_esp_bins\"" error.
if (!isBoardConfigured(configuration))
{
showBoardNotSelectedMessage(configuration);
return;
}
ESPFlashUtil.flashOverJtag(configuration, launch);
return;
}
Expand Down Expand Up @@ -271,6 +279,52 @@ private static void showMessage(ILaunchConfiguration configuration)
});
}

/**
* Determines whether a board configuration is available for JTAG flashing. A board is considered configured when it
* is resolvable from the active launch target, or when the (possibly manually edited) resolved JTAG flash arguments
* already reference a board configuration file.
*
* @param configuration the launch configuration
* @return {@code true} if a board configuration is available, {@code false} otherwise
*/
private boolean isBoardConfigured(ILaunchConfiguration configuration)
{
if (JtagVariableResolver.isBoardConfigResolvable())
{
return true;
}

// Fall back to inspecting the resolved JTAG flash arguments for a manually configured board file.
try
{
String arguments = configuration.getAttribute(IDFLaunchConstants.ATTR_JTAG_FLASH_ARGUMENTS,
StringUtil.EMPTY);
String resolved = VariablesPlugin.getDefault().getStringVariableManager()
.performStringSubstitution(arguments);
return resolved != null && resolved.contains("board/"); //$NON-NLS-1$
}
catch (CoreException e)
{
Logger.log(e);
}
return false;
}

private static void showBoardNotSelectedMessage(ILaunchConfiguration configuration)
{
Display.getDefault().asyncExec(() -> {
boolean isYes = MessageDialog.openConfirm(Display.getDefault().getActiveShell(),
com.espressif.idf.launch.serial.internal.Messages.BoardNotSelectedTitle,
com.espressif.idf.launch.serial.internal.Messages.BoardNotSelectedMsg);
if (isYes)
{
ILaunchTargetUIManager targetUIManager = Activator.getService(ILaunchTargetUIManager.class);
ILaunchTargetManager launchTargetManager = Activator.getService(ILaunchTargetManager.class);
targetUIManager.editLaunchTarget(launchTargetManager.getDefaultLaunchTarget(configuration));
}
});
}

@Override
public boolean buildForLaunch(ILaunchConfiguration configuration, String mode, ILaunchTarget target,
IProgressMonitor monitor) throws CoreException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@
SerialFlashLaunch_Pause=Pausing serial port
SerialFlashLaunch_Resume=Resuming serial port
SerialPortNotFoundTitle=Serial port not found
SerialPortNotFoundMsg=The serial port was not found. Please select it first and flash the project again.
SerialPortNotFoundMsg=The serial port was not found. Please select it first and flash the project again.
BoardNotSelectedTitle=No board selected
BoardNotSelectedMsg=No board is selected. Edit the launch target to select a board?
Loading