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
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -707,9 +707,11 @@
"default": "default",
"enum": [
"default",
"codelldb"
"codelldb",
"lldb-dap",
"gdb-dap"
],
"description": "The Debugging Configuration Type, .e.g default|codelldb"
"description": "The Debugging Configuration Type, .e.g default|codelldb|lldb-dap|gdb-dap"
},
"xmake.customDebugConfig": {
"type": "object",
Expand Down
3 changes: 2 additions & 1 deletion src/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {log} from './log';
import * as path from 'path';
import { config } from './config';
import * as process from './process';
import * as utils from './utils';

// get lua keyword list
function getLuaKeywordList(): Promise<string> {
Expand All @@ -19,7 +20,7 @@ function getLuaKeywordList(): Promise<string> {
// get xmake command list
function getXMakeCommandList(): Promise<string> {
return new Promise(async function (resolve, reject) {
let getApisScript = path.join(__dirname, `../../assets/apis.lua`);
let getApisScript = utils.getAssetsScriptPath("apis.lua");
if (fs.existsSync(getApisScript)) {
let result = (await process.iorunv(config.executable, ["l", getApisScript], { "COLORTERM": "nocolor" },
config.workingDirectory)).stdout.trim();
Expand Down
63 changes: 62 additions & 1 deletion src/debugger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as os from 'os';
import * as encoding from 'encoding';
import * as process from './process';
import { log } from './log';
import * as utils from './utils';
import { config } from './config';

// the debugger class
Expand All @@ -27,7 +28,7 @@ export class Debugger implements vscode.Disposable {
// find gdb path
async findGdbPath() {
var gdbPath = null;
let findGdbScript = path.join(__dirname, `../../assets/find_gdb.lua`);
let findGdbScript = utils.getAssetsScriptPath("find_gdb.lua");
if (fs.existsSync(findGdbScript)) {
gdbPath = (await process.iorunv(config.executable, ["l", findGdbScript], {"COLORTERM": "nocolor"}, config.workingDirectory)).stdout.trim();
if (gdbPath) {
Expand Down Expand Up @@ -67,6 +68,18 @@ export class Debugger implements vscode.Disposable {
codelldb = true;
}

// uses lldb-dap debugger?
var lldbdap = false;
if (config.debugConfigType == "lldb-dap" && vscode.extensions.getExtension("llvm-vs-code-extensions.lldb-dap")) {
lldbdap = true;
}

// uses gdb-dap debugger?
var gdbdap = false;
if (config.debugConfigType == "gdb-dap" && vscode.extensions.getExtension("ms-vscode.cpptools")) {
gdbdap = true;
}

// get target run directory
if (!targetRunDir) {
targetRunDir = path.dirname(targetProgram);
Expand Down Expand Up @@ -106,6 +119,54 @@ export class Debugger implements vscode.Disposable {
env: envs,
externalConsole: false,
};
} else if (lldbdap) {
// LLDB DAP configuration
var envs = {};
for (let item of (targetRunEnvs as Array<Object>)) {
let map = item as Map<String, String>;
if (map) {
let name = map["name"];
let value = map["value"];
if (name && value) {
envs[name] = value;
}
}
}
debugConfig = {
name: `launch: ${targetName}`,
type: 'lldb-dap',
request: 'launch',
program: targetProgram,
args: args,
stopAtEntry: true,
cwd: targetRunDir,
env: envs,
externalConsole: false,
};
} else if (gdbdap) {
// GDB DAP configuration
var gdbEnvs = [];
for (let item of (targetRunEnvs as Array<Object>)) {
let map = item as Map<String, String>;
if (map) {
let name = map["name"];
let value = map["value"];
if (name && value) {
gdbEnvs.push({name: name, value: value});
}
}
}
Comment on lines +122 to +158

Choose a reason for hiding this comment

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

medium

There's significant code duplication in how environment variables are processed for codelldb, lldbdap, and gdbdap. The loops are very similar and can be refactored into a helper function to improve maintainability and reduce redundancy.

Additionally, the type assertion item as Map<String, String> is not accurate. The targetRunEnvs is an array of objects like { name: string, value: string }. Using item as { name: string, value: string } would be more precise. The use of String (capital S) is also unconventional for type annotations in TypeScript; string is preferred.

Consider creating a utility function to handle environment variable processing for different debugger formats.

debugConfig = {
name: `launch: ${targetName}`,
type: 'gdb',
request: 'launch',
program: targetProgram,
args: args,
stopAtEntry: true,
cwd: targetRunDir,
environment: gdbEnvs,
externalConsole: false,
};
} else {
// uses cpptools debugger
if (os.platform() == "darwin") {
Expand Down
24 changes: 12 additions & 12 deletions src/explorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as path from 'path';
import * as process from './process';
import { config } from './config';
import { log } from './log';
import * as utils from './utils';

// Different tree view items contain different data
// Following types store specific info that we should keep in each tree view item
Expand Down Expand Up @@ -76,36 +77,35 @@ class XMakeExplorerItem extends vscode.TreeItem {
// Set the icon depending on the type
// Each known file type is assigned a language specific icon
// If the file type is recognized then the default file icon is assigned
const resDirPath = [__dirname, "..", "..", "res"];

switch (info.type) {
case XMakeExplorerItemType.GROUP:
this.iconPath = {
dark: path.join(...resDirPath, "dark", "symbol-misc.svg"),
light: path.join(...resDirPath, "light", "symbol-misc.svg")
dark: utils.getResourcePath("dark/symbol-misc.svg"),
light: utils.getResourcePath("light/symbol-misc.svg")
}
break;
case XMakeExplorerItemType.TARGET:
if (info.kind === "binary")
this.iconPath = {
dark: path.join(...resDirPath, "dark", "window.svg"),
light: path.join(...resDirPath, "light", "window.svg")
dark: utils.getResourcePath("dark/window.svg"),
light: utils.getResourcePath("light/window.svg")
}
else if (info.kind === "shared")
this.iconPath = {
dark: path.join(...resDirPath, "dark", "gear.svg"),
light: path.join(...resDirPath, "light", "gear.svg")
dark: utils.getResourcePath("dark/gear.svg"),
light: utils.getResourcePath("light/gear.svg")
}
else if (info.kind == "static")
this.iconPath = {
dark: path.join(...resDirPath, "dark", "library.svg"),
light: path.join(...resDirPath, "light", "library.svg")
dark: utils.getResourcePath("dark/library.svg"),
light: utils.getResourcePath("light/library.svg")
}
else {
// Icon for phony target
this.iconPath = {
dark: path.join(...resDirPath, "dark", "archive.svg"),
light: path.join(...resDirPath, "light", "archive.svg")
dark: utils.getResourcePath("dark/archive.svg"),
light: utils.getResourcePath("light/archive.svg")
}
}
break;
Expand Down Expand Up @@ -801,7 +801,7 @@ export class XMakeExplorer implements vscode.Disposable {
// Helper function to read all the information used by the explorer from xmake.lua
private async readInfo() {
try {
const getExplorerTargetsScript = path.join(__dirname, `../../assets/explorer.lua`);
const getExplorerTargetsScript = utils.getAssetsScriptPath("explorer.lua");
if (fs.existsSync(getExplorerTargetsScript)) {
const infoJson = (await process.iorunv(config.executable, ["l", getExplorerTargetsScript], { "COLORTERM": "nocolor" }, config.workingDirectory)).stdout.trim();
const info = JSON.parse(infoJson);
Expand Down
115 changes: 83 additions & 32 deletions src/launchDebugger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
import { WorkspaceFolder } from 'vscode';
import * as process from './process';

import { config as settings } from './config';
import { log } from './log';
import * as utils from './utils';
import { config } from './config';
import { Option } from './option';
import * as process from './process';

/**
* integrated: Use integrated terminal in VSCode
Expand Down Expand Up @@ -39,9 +38,9 @@ interface TargetInformations {

async function getDebuggableTargets(): Promise<Array<string>> {
let targets = "";
let getTargetsPathScript = path.join(__dirname, `../../assets/debuggable_targets.lua`);
let getTargetsPathScript = utils.getAssetsScriptPath("debuggable_targets.lua");
if (fs.existsSync(getTargetsPathScript)) {
targets = (await process.iorunv(settings.executable, ["l", getTargetsPathScript], { "COLORTERM": "nocolor" }, settings.workingDirectory)).stdout.trim();
targets = (await process.iorunv(config.executable, ["l", getTargetsPathScript], { "COLORTERM": "nocolor" }, config.workingDirectory)).stdout.trim();
}
if (targets) {
return process.getAnnotatedJSON(targets)[0];
Expand All @@ -55,12 +54,25 @@ async function getDebuggableTargets(): Promise<Array<string>> {
* @returns TargetInformations
*/
async function getInformations(targetName: string): Promise<TargetInformations> {
let getTargetInformationsScript = path.join(__dirname, `../../assets/target_informations.lua`);
let getTargetInformationsScript = utils.getAssetsScriptPath("target_informations.lua");
if (fs.existsSync(getTargetInformationsScript)) {
let targetInformations = (await process.iorunv(settings.executable, ["l", getTargetInformationsScript, targetName], { "COLORTERM": "nocolor" }, settings.workingDirectory)).stdout.trim();
if (targetInformations) {
return process.getAnnotatedJSON(targetInformations)[0];
try {
const result = await process.iorunv(config.executable, ["l", getTargetInformationsScript, targetName], { "COLORTERM": "nocolor" }, config.workingDirectory);
const targetInformations = result.stdout.trim();

if (targetInformations) {
const parsed = process.getAnnotatedJSON(targetInformations);
if (parsed && parsed.length > 0) {
return parsed[0];
}
}

log.error(`Failed to get target informations for ${targetName}. Result: ${targetInformations}`);
} catch (error) {
log.error(`Error executing target_informations.lua: ${error}`);
}
} else {
log.error(`target_informations.lua script not found at ${getTargetInformationsScript}`);
}

return null;
Expand All @@ -72,9 +84,9 @@ async function getInformations(targetName: string): Promise<TargetInformations>
*/
async function findGdbPath(): Promise<string> {
let gdbPath = "";
let findGdbScript = path.join(__dirname, `../../assets/find_gdb.lua`);
let findGdbScript = utils.getAssetsScriptPath("find_gdb.lua");
if (fs.existsSync(findGdbScript)) {
gdbPath = (await process.iorunv(settings.executable, ["l", findGdbScript], { "COLORTERM": "nocolor" }, settings.workingDirectory)).stdout.trim();
gdbPath = (await process.iorunv(config.executable, ["l", findGdbScript], { "COLORTERM": "nocolor" }, config.workingDirectory)).stdout.trim();
if (gdbPath) {
gdbPath = gdbPath.split('\n')[0].trim();
}
Expand Down Expand Up @@ -158,7 +170,7 @@ class XmakeConfigurationProvider implements vscode.DebugConfigurationProvider {
* @param token
* @returns the modified config to cpptols or codelldb
*/
public async resolveDebugConfiguration(folder: WorkspaceFolder | undefined, config: XmakeDebugConfiguration, token?: vscode.CancellationToken): Promise<vscode.DebugConfiguration> {
public async resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: XmakeDebugConfiguration, token?: vscode.CancellationToken): Promise<vscode.DebugConfiguration> {

// If target is not set, resolve it with the status
if (!config.target) {
Expand All @@ -180,7 +192,7 @@ class XmakeConfigurationProvider implements vscode.DebugConfigurationProvider {

// Set the program path
if (!(targetInformations.path && fs.existsSync(targetInformations.path))) {
await vscode.window.showErrorMessage('The target program not found!');
await vscode.window.showErrorMessage("The target program not found! Please build the project first.");
return { name: "Error: Target not found", type: "cppdbg", request: "launch" }; // Return a fake config
}
config.program = targetInformations.path;
Expand All @@ -199,24 +211,24 @@ class XmakeConfigurationProvider implements vscode.DebugConfigurationProvider {
if (!config.args) {
let args = [];

if (config.target in settings.debuggingTargetsArguments)
args = settings.debuggingTargetsArguments[config.target];
else if ("default" in settings.debuggingTargetsArguments)
args = settings.debuggingTargetsArguments["default"];
else if (config.target in settings.runningTargetsArguments)
if (config.target in config.debuggingTargetsArguments)
args = config.debuggingTargetsArguments[config.target];
else if ("default" in config.debuggingTargetsArguments)
args = config.debuggingTargetsArguments["default"];
else if (config.target in config.runningTargetsArguments)
args = config.runningTargetsArguments[config.target];
else if ("default" in settings.runningTargetsArguments)
args = settings.runningTargetsArguments["default"];
else if ("default" in config.runningTargetsArguments)
args = config.runningTargetsArguments["default"];

config.args = args;
}

// Get xmake env and merge it with config envs
const sep = os.platform() == "win32" ? ';' : ':'
let xmakeEnvs = targetInformations.envs;
if (settings.envBehaviour === 'override') {
if (config.envBehaviour === 'override') {
config.env = { ...xmakeEnvs, ...config.env };
} else if (settings.envBehaviour === 'merge' && config.env !== undefined) {
} else if (config.envBehaviour === 'merge' && config.env !== undefined) {
// Merge behaviour between xmake envs and launch.json envs
for (const key in xmakeEnvs) {
// If the key exist in debug envs
Expand All @@ -242,7 +254,7 @@ class XmakeConfigurationProvider implements vscode.DebugConfigurationProvider {
}

// Switch to lldb if needed
if (settings.debugConfigType == "codelldb") {
if (config.debugConfigType == "codelldb") {
config.type = 'lldb';
config.stopOnEntry = config.stopAtEntry;
// Code LLDB doesn't support newExternal
Expand All @@ -256,6 +268,39 @@ class XmakeConfigurationProvider implements vscode.DebugConfigurationProvider {
config.program = `${targetInformations.name}.exe`;
}
}

// Switch to LLDB DAP if needed
if (config.debugConfigType == "lldb-dap") {
config.type = 'lldb-dap';
config.stopOnEntry = config.stopOnEntry;
// LLDB DAP doesn't support newExternal
if (config.terminal == 'newExternal') {
config.terminal = 'external';
}

// LLDB DAP use program key for search a running process
if (config.request == 'attach') {
config.stopOnEntry = false;
config.program = `${targetInformations.name}.exe`;
}
}
Comment on lines +272 to +286

Choose a reason for hiding this comment

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

high

This block has a couple of issues:

  1. The assignment config.stopOnEntry = config.stopOnEntry; is redundant and can be removed for clarity.
  2. Hardcoding the .exe extension for the program name in attach mode will fail on non-Windows platforms. You should use path.basename(targetInformations.path) to get the platform-correct executable name.

I've suggested the changes for the lldb-dap block; please apply similar changes for the gdb-dap block.

        // Switch to LLDB DAP if needed
        if (config.debugConfigType == "lldb-dap") {
            config.type = 'lldb-dap';
            // LLDB DAP doesn't support newExternal
            if (config.terminal == 'newExternal') {
                config.terminal = 'external';
            }

            // LLDB DAP use program key for search a running process
            if (config.request == 'attach') {
                config.stopOnEntry = false;
                config.program = path.basename(targetInformations.path);
            }
        }


// Switch to GDB DAP if needed
if (config.debugConfigType == "gdb-dap") {
config.type = 'gdb';
config.stopOnEntry = config.stopOnEntry;
// GDB DAP doesn't support newExternal
if (config.terminal == 'newExternal') {
config.terminal = 'external';
}

// GDB DAP use program key for search a running process
if (config.request == 'attach') {
config.stopOnEntry = false;
config.program = `${targetInformations.name}.exe`;
}
}
Comment on lines +288 to +302

Choose a reason for hiding this comment

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

high

Similar to the lldb-dap block, this block has a redundant assignment and hardcodes the .exe extension.

  1. The assignment config.stopOnEntry = config.stopOnEntry; is redundant.
  2. Hardcoding .exe for attach mode will fail on non-Windows platforms. Please use path.basename(targetInformations.path) instead.
        // Switch to GDB DAP if needed
        if (config.debugConfigType == "gdb-dap") {
            config.type = 'gdb';
            // GDB DAP doesn't support newExternal
            if (config.terminal == 'newExternal') {
                config.terminal = 'external';
            }

            // GDB DAP use program key for search a running process
            if (config.request == 'attach') {
                config.stopOnEntry = false;
                config.program = path.basename(targetInformations.path);
            }
        }


// Set MIMode for macos
if (os.platform() == 'darwin') {
config.MIMode = "lldb";
Expand Down Expand Up @@ -285,7 +330,7 @@ class XmakeConfigurationProvider implements vscode.DebugConfigurationProvider {
config = { ...config, ...setupCommands };

// Merge the custom debug config with actual config
config = { ...config, ...settings.customDebugConfig };
config = { ...config, ...config.customDebugConfig };

return config;
}
Expand All @@ -294,20 +339,26 @@ class XmakeConfigurationProvider implements vscode.DebugConfigurationProvider {
export function initDebugger(context: vscode.ExtensionContext, option: Option) {
const cpptools = vscode.extensions.getExtension("ms-vscode.cpptools");
const codelldb = vscode.extensions.getExtension("vadimcn.vscode-lldb");
const lldbdap = vscode.extensions.getExtension("llvm-vs-code-extensions.lldb-dap");

if (!cpptools && !codelldb) {
log.error("Neither CppTools or Code LLDB is installed");
vscode.window.showErrorMessage("Neither CppTools or Code LLDB is installed. Install one of this extension to debug");
if (!cpptools && !codelldb && !lldbdap) {
log.error("No debugging extensions are installed");
vscode.window.showErrorMessage("No debugging extensions found. Please install CppTools, CodeLLDB, or LLDB DAP extension to debug");
return;
}

if (!codelldb && settings.debugConfigType == 'codelldb') {
vscode.window.showErrorMessage("Code LLDB is not installed. Install it first to use the debugger.");
// Check if the configured debugger is available
if (config.debugConfigType == "lldb-dap" && !lldbdap) {
log.info("LLDB DAP is configured but not available, falling back to available debuggers");
}
if (config.debugConfigType == "codelldb" && !codelldb) {
log.info("CodeLLDB is configured but not available, falling back to available debuggers");
}

// Activate the two extensions
// Activate all available debugging extensions
cpptools?.activate();
codelldb?.activate()
codelldb?.activate();
lldbdap?.activate();

const provider = new XmakeConfigurationProvider(option);
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('xmake', provider, vscode.DebugConfigurationProviderTriggerKind.Dynamic));
Expand Down
Loading