Skip to content
Open
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 @@ -53,12 +53,14 @@
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
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.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.ui.Messages;
Expand All @@ -79,6 +81,12 @@ public class LaunchConfigurationDelegate extends AbstractGnuMcuLaunchConfigurati
private final static String NON_STOP_FIRST_VERSION = "6.8.50"; //$NON-NLS-1$
private final int STATUS_DLL_NOT_FOUND = -1073741515;

/**
* 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 @@ -736,12 +744,56 @@ protected IPath checkBinaryDetails(final ILaunchConfiguration config) throws Cor
new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Missing mandatory configuration. "
+ "Fill-in the 'Config options:' field in the Debugger tab.")); //$NON-NLS-1$
}

// 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);
}
}

IPath path = super.checkBinaryDetails(config);
return path;
}

/**
* 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$
}
catch (CoreException e)
{
Activator.log(e);
}
return false;
}

/**
* Get a custom launch sequence, that inserts a GDB server starter.
*/
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