Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8309a022d9 | ||
|
|
acaa8d116f | ||
|
|
18a108a7d2 | ||
|
|
998b2570b8 | ||
|
|
23f4d67ed8 | ||
|
|
e7c7ccd362 | ||
|
|
01cfad93a2 | ||
|
|
6532902f76 | ||
|
|
d225eeb592 | ||
|
|
10563fad14 | ||
|
|
803e8a9c22 | ||
|
|
c5899fa663 | ||
|
|
9f99738cb5 | ||
|
|
9a3da2b74f | ||
|
|
0e2ddc6954 | ||
|
|
58f5ae3e57 | ||
|
|
f487c9be4e | ||
|
|
c48a4fb0e0 | ||
|
|
54bfc004a5 | ||
|
|
b603db62f9 | ||
|
|
0aa3fc8804 | ||
|
|
94f05866a4 | ||
|
|
5e17eeeeae | ||
|
|
c983a011bd | ||
|
|
30b5be6540 | ||
|
|
a433293530 | ||
|
|
9313999c8e | ||
|
|
ba4d4131cd | ||
|
|
e2d7deb912 | ||
|
|
22439cccde | ||
|
|
e9b2659e1e |
@@ -134,6 +134,10 @@ export async function main() {
|
||||
packageJSON.contributes.debuggers[1].configurationAttributes.launch = schemaJSON.definitions.CppvsdbgLaunchOptions;
|
||||
packageJSON.contributes.debuggers[1].configurationAttributes.attach = schemaJSON.definitions.CppvsdbgAttachOptions;
|
||||
|
||||
// cpplldb
|
||||
packageJSON.contributes.debuggers[2].configurationAttributes.launch = schemaJSON.definitions.CpplldbLaunchOptions;
|
||||
packageJSON.contributes.debuggers[2].configurationAttributes.attach = schemaJSON.definitions.CpplldbAttachOptions;
|
||||
|
||||
let content: string = JSON.stringify(packageJSON, null, 4);
|
||||
|
||||
// We use '\u200b' (unicode zero-length space character) to break VS Code's URL detection regex for URLs that are examples. This process will
|
||||
|
||||
+1412
-15
File diff suppressed because it is too large
Load Diff
@@ -979,6 +979,69 @@
|
||||
"c_cpp.debuggers.vsCodeCommand.description": "VS Code command to be invoked. Can be a command in VS Code or an active extension.",
|
||||
"c_cpp.debuggers.vsCodeCommand.command.description": "VS Code command to be invoked.",
|
||||
"c_cpp.debuggers.vsCodeCommand.args.description": "Arguments to the VS Code command.",
|
||||
"c_cpp.debuggers.cpplldb.debuggerPath.description": {
|
||||
"message": "Full path to the LLDB-DAP debugger executable. If not specified, the extension will search for the debugger in the system PATH.",
|
||||
"comment": [
|
||||
"{Locked=\"LLDB-DAP\"} {Locked=\"PATH\"}"
|
||||
]
|
||||
},
|
||||
"c_cpp.debuggers.cpplldb.debuggerArgs.description": {
|
||||
"message": "Additional arguments for the LLDB-DAP debugger.",
|
||||
"comment": [
|
||||
"{Locked=\"LLDB-DAP\"}"
|
||||
]
|
||||
},
|
||||
"c_cpp.debuggers.cpplldb.externalConsole.description": "If true, launch the external console for the debugger.",
|
||||
"c_cpp.debuggers.cpplldb.disableAslr.description": "Disable Address Space Layout Randomization (if supported).",
|
||||
"c_cpp.debuggers.cpplldb.disableStdio.description": "Disables the standard input/output streams during debugging.",
|
||||
"c_cpp.debuggers.cpplldb.expandShellArguments.description": "Expands program arguments as a shell would (does not actually launch the program in a shell).",
|
||||
"c_cpp.debuggers.cpplldb.detachOnError.description": "Detach the debugger when an error occurs.",
|
||||
"c_cpp.debuggers.cpplldb.debuggerRoot.description": "The directory where relative object files can be located.",
|
||||
"c_cpp.debuggers.cpplldb.targetTriple.description": "Overrides the target triple of the target program.",
|
||||
"c_cpp.debuggers.cpplldb.platformName.description": "Overrides the platform name of the target program.",
|
||||
"c_cpp.debuggers.cpplldb.initCommands.description": "LLDB commands executed upon debugger startup prior to creating the LLDB target.",
|
||||
"c_cpp.debuggers.cpplldb.preRunCommands.description": "LLDB commands executed just before launching/attaching, after the LLDB target has been created.",
|
||||
"c_cpp.debuggers.cpplldb.postRunCommands.description": "LLDB commands executed after launching when it's in a stopped state prior to any automatic continuation.",
|
||||
"c_cpp.debuggers.cpplldb.launchCommands.description": "Custom LLDB commands that are executed instead of launching a process.",
|
||||
"c_cpp.debuggers.cpplldb.stopCommands.description": "LLDB commands executed each time the program stops.",
|
||||
"c_cpp.debuggers.cpplldb.exitCommands.description": "LLDB commands executed when the program exits.",
|
||||
"c_cpp.debuggers.cpplldb.terminateCommands.description": "LLDB commands executed when the debugging session ends.",
|
||||
"c_cpp.debuggers.cpplldb.enableAutoVariableSummaries.description": "Enable auto-generated summaries for variables when no summaries exist for a given type. This feature can cause performance delays in large projects when viewing variables.",
|
||||
"c_cpp.debuggers.cpplldb.displayExtendedBacktrace.description": "Enable language-specific extended backtraces.",
|
||||
"c_cpp.debuggers.cpplldb.enableSyntheticChildDebugging.description": "If a variable is displayed using synthetic children, also display the actual contents of the variable at the end under an entry. This is useful when creating synthetic child plug-ins as it lets you see the actual contents of the variable.",
|
||||
"c_cpp.debuggers.cpplldb.commandEscapePrefix.description": "The escape prefix character to use for executing regular LLDB commands in the Debug Console, instead of printing variables.",
|
||||
"c_cpp.debuggers.cpplldb.customFrameFormat.markdownDescription": {
|
||||
"message": "When specified, stack frames will have descriptions generated based on the provided format. See https://lldb.llvm.org/use/formatting.html.",
|
||||
"comment": [
|
||||
"{Locked=\"https://lldb.llvm.org/use/formatting.html\"}"
|
||||
]
|
||||
},
|
||||
"c_cpp.debuggers.cpplldb.customThreadFormat.markdownDescription": {
|
||||
"message": "When specified, threads will have descriptions generated based on the provided format. See https://lldb.llvm.org/use/formatting.html.",
|
||||
"comment": [
|
||||
"{Locked=\"https://lldb.llvm.org/use/formatting.html\"}"
|
||||
]
|
||||
},
|
||||
"c_cpp.debuggers.cpplldb.attachCommands.description": "Custom LLDB commands that are executed instead of attaching to a process ID or to a process by name.",
|
||||
"c_cpp.debuggers.cpplldb.waitFor.markdownDescription": {
|
||||
"message": "Wait for the process to launch by looking for a process with a basename that matches the value specified in `program`.",
|
||||
"comment": [
|
||||
"{Locked=\"`program`\"}"
|
||||
]
|
||||
},
|
||||
"c_cpp.debuggers.cpplldb.coreFile.description": "Path to the core file to debug.",
|
||||
"c_cpp.debuggers.cpplldb.gdbRemotePort.markdownDescription": {
|
||||
"message": "TCP/IP port to attach to a remote system. Specifying both `processId` and `port` is an error.",
|
||||
"comment": [
|
||||
"{Locked=\"`processId`\"} {Locked=\"`port`\"}"
|
||||
]
|
||||
},
|
||||
"c_cpp.debuggers.cpplldb.gdbRemoteHost.markdownDescription": {
|
||||
"message": "The hostname to connect to a remote system. The default hostname is `localhost`.",
|
||||
"comment": [
|
||||
"{Locked=\"`localhost`\"}"
|
||||
]
|
||||
},
|
||||
"c_cpp.taskDefinitions.name.description": "The name of the task.",
|
||||
"c_cpp.taskDefinitions.command.description": "The path to either a compiler or script that performs compilation.",
|
||||
"c_cpp.taskDefinitions.args.description": "Additional arguments to pass to the compiler or compilation script.",
|
||||
|
||||
@@ -28,6 +28,10 @@ class RefreshButton implements vscode.QuickInputButton {
|
||||
|
||||
export interface AttachItem extends vscode.QuickPickItem {
|
||||
id?: string;
|
||||
name?: string;
|
||||
pid?: string;
|
||||
commandLine?: string;
|
||||
fullPath?: string;
|
||||
}
|
||||
|
||||
// We should not await on this function.
|
||||
|
||||
@@ -12,6 +12,7 @@ import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as util from '../common';
|
||||
import { executableName, ProcessReturnType, spawnChildProcess } from '../common-remote-safe';
|
||||
import * as debugUtils from './utils';
|
||||
|
||||
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
|
||||
@@ -21,6 +22,28 @@ export interface AttachItemsProvider {
|
||||
getAttachItems(token?: vscode.CancellationToken): Promise<AttachItem[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a process matches the specified filter criteria.
|
||||
*
|
||||
* This function checks if the given process name or its full path matches
|
||||
* the provided filter string. It also normalizes the filter string to handle
|
||||
* relative paths and compares the executable name derived from the filter
|
||||
* with the executable name of the process.
|
||||
*
|
||||
* @param filterTo - The filter string to match against. This can be a process name,
|
||||
* an executable name, or a file path.
|
||||
* @param processName - The name of the process to check.
|
||||
* @param fullPath - The full path of the process executable, if available.
|
||||
* @returns `true` if the process matches the filter criteria; otherwise, `false`.
|
||||
*/
|
||||
export function processMatches(filterTo: string, processName: string, fullPath: string | undefined) {
|
||||
const normalized = path.resolve(filterTo);
|
||||
const executable = executableName(filterTo);
|
||||
|
||||
// Filter out the processes that do not somehow match.
|
||||
return processName === filterTo || executableName(processName) === executable || fullPath === filterTo || fullPath === normalized;
|
||||
}
|
||||
|
||||
export class AttachPicker {
|
||||
constructor(private attachItemsProvider: AttachItemsProvider) { }
|
||||
|
||||
@@ -28,6 +51,23 @@ export class AttachPicker {
|
||||
public async ShowAttachEntries(token?: vscode.CancellationToken): Promise<string | undefined> {
|
||||
return showQuickPick(() => this.attachItemsProvider.getAttachItems(token));
|
||||
}
|
||||
|
||||
/** Shows the Attach QuickPick, but if a valid filterTo process name/path is passed in, it only shows those entries. */
|
||||
public async ShowAttachEntriesFiltered(filterTo?: string, token?: vscode.CancellationToken): Promise<string | undefined> {
|
||||
return showQuickPick(async () => {
|
||||
|
||||
const items = await this.attachItemsProvider.getAttachItems(token);
|
||||
if (filterTo) {
|
||||
// Filter out the processes that do not somehow match.
|
||||
const filtered = items.filter(each => processMatches(filterTo, each.label, each.fullPath));
|
||||
if (filtered.length > 0) {
|
||||
// Only filter the list if we have actual matches.
|
||||
return filtered;
|
||||
}
|
||||
}
|
||||
return items;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoteAttachPicker {
|
||||
@@ -177,18 +217,17 @@ export class RemoteAttachPicker {
|
||||
return 0;
|
||||
}
|
||||
return aLower < bLower ? -1 : 1;
|
||||
})
|
||||
.map(p => p.toAttachItem());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async getRemoteProcessesExtendedRemote(miDebuggerPath: string, miDebuggerServerAddress: string): Promise<AttachItem[]> {
|
||||
const args: string[] = [`-ex "target extended-remote ${miDebuggerServerAddress}"`, '-ex "info os processes"', '-batch'];
|
||||
let processListOutput: util.ProcessReturnType = await util.spawnChildProcess(miDebuggerPath, args);
|
||||
let processListOutput: ProcessReturnType = await spawnChildProcess(miDebuggerPath, args);
|
||||
// The device may not be responsive for a while during the restart after image deploy. Retry 5 times.
|
||||
for (let i: number = 0; i < 5 && !processListOutput.succeeded && processListOutput.outputError.length === 0; i++) {
|
||||
processListOutput = await util.spawnChildProcess(miDebuggerPath, args);
|
||||
processListOutput = await spawnChildProcess(miDebuggerPath, args);
|
||||
}
|
||||
|
||||
if (!processListOutput.succeeded) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import { promisify } from 'util';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as util from '../common';
|
||||
import { executableName, ProcessReturnType, spawnChildProcess } from '../common-remote-safe';
|
||||
import { isWindows } from '../constants';
|
||||
import { expandAllStrings, ExpansionOptions, ExpansionVars } from '../expand';
|
||||
import { CppBuildTask, CppBuildTaskDefinition, cppBuildTaskProvider } from '../LanguageServer/cppBuildTaskProvider';
|
||||
@@ -21,8 +22,9 @@ import * as logger from '../logger';
|
||||
import { PlatformInformation } from '../platform';
|
||||
import { rsync, scp, ssh } from '../SSH/commands';
|
||||
import * as Telemetry from '../telemetry';
|
||||
import { AttachItemsProvider, AttachPicker, RemoteAttachPicker } from './attachToProcess';
|
||||
import { ConfigMenu, ConfigMode, ConfigSource, CppDebugConfiguration, DebuggerEvent, DebuggerType, DebugType, IConfiguration, IConfigurationSnippet, isDebugLaunchStr, MIConfigurations, PipeTransportConfigurations, TaskStatus, WindowsConfigurations, WSLConfigurations } from './configurations';
|
||||
import { AttachItemsProvider, AttachPicker, processMatches, RemoteAttachPicker } from './attachToProcess';
|
||||
import { ConfigMenu, ConfigMode, ConfigSource, Configuration, CppDebugConfiguration, DebuggerEvent, DebuggerType, DebugType, IConfigurationSnippet, isDebugLaunchStr, LldbDapConfigurations, MIConfigurations, PipeTransportConfigurations, TaskStatus, WindowsConfigurations, WSLConfigurations } from './configurations';
|
||||
import { findLldbDap, translateToLldbDap } from './lldb-dap';
|
||||
import { NativeAttachItemsProviderFactory } from './nativeAttach';
|
||||
import { Environment, ParsedEnvironmentFile } from './ParsedEnvironmentFile';
|
||||
import * as debugUtils from './utils';
|
||||
@@ -48,17 +50,13 @@ const globAsync: (pattern: string, options?: glob.IOptions | undefined) => Promi
|
||||
*/
|
||||
export class DebugConfigurationProvider implements vscode.DebugConfigurationProvider {
|
||||
|
||||
private type: DebuggerType;
|
||||
private assetProvider: IConfigurationAssetProvider;
|
||||
// Keep a list of tasks detected by cppBuildTaskProvider.
|
||||
private static detectedBuildTasks: CppBuildTask[] = [];
|
||||
private static detectedCppBuildTasks: CppBuildTask[] = [];
|
||||
private static detectedCBuildTasks: CppBuildTask[] = [];
|
||||
protected static recentBuildTaskLabel: string;
|
||||
|
||||
public constructor(assetProvider: IConfigurationAssetProvider, type: DebuggerType) {
|
||||
this.assetProvider = assetProvider;
|
||||
this.type = type;
|
||||
public constructor(private assetProvider: ConfigurationAssetProvider, private type: DebuggerType) {
|
||||
}
|
||||
|
||||
public static ClearDetectedBuildTasks(): void {
|
||||
@@ -236,7 +234,7 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
|
||||
if (config.type === DebuggerType.cppvsdbg) {
|
||||
// Fail if cppvsdbg type is running on non-Windows
|
||||
if (os.platform() !== 'win32') {
|
||||
void logger.getOutputChannelLogger().showWarningMessage(localize("debugger.not.available", "Debugger of type: '{0}' is only available on Windows. Use type: '{1}' on the current OS platform.", "cppvsdbg", "cppdbg"));
|
||||
void logger.getOutputChannelLogger().showWarningMessage(localize("debugger.not.available", "Debugger of type: '{0}' is only available on Windows. Use type: '{1}' or '{2}' on the current OS platform.", DebuggerType.cppvsdbg, DebuggerType.cppdbg, DebuggerType.cpplldb));
|
||||
return undefined; // Abort debugging silently.
|
||||
}
|
||||
|
||||
@@ -269,55 +267,57 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
|
||||
|
||||
this.resolveSourceFileMapVariables(config);
|
||||
|
||||
// Modify WSL config for OpenDebugAD7
|
||||
if (os.platform() === 'win32' &&
|
||||
config.pipeTransport &&
|
||||
config.pipeTransport.pipeProgram) {
|
||||
let replacedPipeProgram: string | undefined;
|
||||
const pipeProgramStr: string = config.pipeTransport.pipeProgram.toLowerCase().trim();
|
||||
if (config.type === DebuggerType.cppdbg) {
|
||||
// Modify WSL config for OpenDebugAD7
|
||||
if (os.platform() === 'win32' &&
|
||||
config.pipeTransport &&
|
||||
config.pipeTransport.pipeProgram) {
|
||||
let replacedPipeProgram: string | undefined;
|
||||
const pipeProgramStr: string = config.pipeTransport.pipeProgram.toLowerCase().trim();
|
||||
|
||||
// OpenDebugAD7 is a 32-bit process. Make sure the WSL pipe transport is using the correct program.
|
||||
replacedPipeProgram = debugUtils.ArchitectureReplacer.checkAndReplaceWSLPipeProgram(pipeProgramStr, debugUtils.ArchType.ia32);
|
||||
// OpenDebugAD7 is a 32-bit process. Make sure the WSL pipe transport is using the correct program.
|
||||
replacedPipeProgram = debugUtils.ArchitectureReplacer.checkAndReplaceWSLPipeProgram(pipeProgramStr, debugUtils.ArchType.ia32);
|
||||
|
||||
// If pipeProgram does not get replaced and there is a pipeCwd, concatenate with pipeProgramStr and attempt to replace.
|
||||
if (!replacedPipeProgram && !path.isAbsolute(pipeProgramStr) && config.pipeTransport.pipeCwd) {
|
||||
const pipeCwdStr: string = config.pipeTransport.pipeCwd.toLowerCase().trim();
|
||||
const newPipeProgramStr: string = path.join(pipeCwdStr, pipeProgramStr);
|
||||
// If pipeProgram does not get replaced and there is a pipeCwd, concatenate with pipeProgramStr and attempt to replace.
|
||||
if (!replacedPipeProgram && !path.isAbsolute(pipeProgramStr) && config.pipeTransport.pipeCwd) {
|
||||
const pipeCwdStr: string = config.pipeTransport.pipeCwd.toLowerCase().trim();
|
||||
const newPipeProgramStr: string = path.join(pipeCwdStr, pipeProgramStr);
|
||||
|
||||
replacedPipeProgram = debugUtils.ArchitectureReplacer.checkAndReplaceWSLPipeProgram(newPipeProgramStr, debugUtils.ArchType.ia32);
|
||||
replacedPipeProgram = debugUtils.ArchitectureReplacer.checkAndReplaceWSLPipeProgram(newPipeProgramStr, debugUtils.ArchType.ia32);
|
||||
}
|
||||
|
||||
if (replacedPipeProgram) {
|
||||
config.pipeTransport.pipeProgram = replacedPipeProgram;
|
||||
}
|
||||
}
|
||||
|
||||
if (replacedPipeProgram) {
|
||||
config.pipeTransport.pipeProgram = replacedPipeProgram;
|
||||
}
|
||||
}
|
||||
const macOSMIMode: string = config.osx?.MIMode ?? config.MIMode;
|
||||
const macOSMIDebuggerPath: string = config.osx?.miDebuggerPath ?? config.miDebuggerPath;
|
||||
|
||||
const macOSMIMode: string = config.osx?.MIMode ?? config.MIMode;
|
||||
const macOSMIDebuggerPath: string = config.osx?.miDebuggerPath ?? config.miDebuggerPath;
|
||||
const lldb_mi_10_x_path: string = path.join(util.extensionPath, "debugAdapters", "lldb-mi", "bin", "lldb-mi");
|
||||
|
||||
const lldb_mi_10_x_path: string = path.join(util.extensionPath, "debugAdapters", "lldb-mi", "bin", "lldb-mi");
|
||||
// Validate LLDB-MI
|
||||
if (os.platform() === 'darwin' && // Check for macOS
|
||||
fs.existsSync(lldb_mi_10_x_path) && // lldb-mi 10.x exists
|
||||
(!macOSMIMode || macOSMIMode === 'lldb') &&
|
||||
!macOSMIDebuggerPath // User did not provide custom lldb-mi
|
||||
) {
|
||||
const frameworkPath: string | undefined = this.getLLDBFrameworkPath();
|
||||
|
||||
// Validate LLDB-MI
|
||||
if (os.platform() === 'darwin' && // Check for macOS
|
||||
fs.existsSync(lldb_mi_10_x_path) && // lldb-mi 10.x exists
|
||||
(!macOSMIMode || macOSMIMode === 'lldb') &&
|
||||
!macOSMIDebuggerPath // User did not provide custom lldb-mi
|
||||
) {
|
||||
const frameworkPath: string | undefined = this.getLLDBFrameworkPath();
|
||||
if (!frameworkPath) {
|
||||
const moreInfoButton: string = localize("lldb.framework.install.xcode", "More Info");
|
||||
const LLDBFrameworkMissingMessage: string = localize("lldb.framework.not.found", "Unable to locate 'LLDB.framework' for lldb-mi. Please install XCode or XCode Command Line Tools.");
|
||||
|
||||
if (!frameworkPath) {
|
||||
const moreInfoButton: string = localize("lldb.framework.install.xcode", "More Info");
|
||||
const LLDBFrameworkMissingMessage: string = localize("lldb.framework.not.found", "Unable to locate 'LLDB.framework' for lldb-mi. Please install XCode or XCode Command Line Tools.");
|
||||
void vscode.window.showErrorMessage(LLDBFrameworkMissingMessage, moreInfoButton)
|
||||
.then(value => {
|
||||
if (value === moreInfoButton) {
|
||||
const helpURL: string = "https://aka.ms/vscode-cpptools/LLDBFrameworkNotFound";
|
||||
void vscode.env.openExternal(vscode.Uri.parse(helpURL));
|
||||
}
|
||||
});
|
||||
|
||||
void vscode.window.showErrorMessage(LLDBFrameworkMissingMessage, moreInfoButton)
|
||||
.then(value => {
|
||||
if (value === moreInfoButton) {
|
||||
const helpURL: string = "https://aka.ms/vscode-cpptools/LLDBFrameworkNotFound";
|
||||
void vscode.env.openExternal(vscode.Uri.parse(helpURL));
|
||||
}
|
||||
});
|
||||
|
||||
return undefined;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,18 +355,69 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
|
||||
processId = await remoteAttachPicker.ShowAttachEntries(config);
|
||||
} else {
|
||||
const attachItemsProvider: AttachItemsProvider = NativeAttachItemsProviderFactory.Get();
|
||||
const attacher: AttachPicker = new AttachPicker(attachItemsProvider);
|
||||
processId = await attacher.ShowAttachEntries(token);
|
||||
|
||||
// LLDB-DAP supports attaching to process by pid or by name.
|
||||
// If they do specify 'program', then we can check to see if there
|
||||
// is a process that matches, or they are using 'waitFor' (on Linux/macOS).
|
||||
if (config.program) {
|
||||
// If they specified 'program', get the tasks that match that.
|
||||
const items = (await attachItemsProvider.getAttachItems(token)).filter(each => processMatches(config.program, each.label, each.fullPath));
|
||||
|
||||
switch (items.length) {
|
||||
case 0:
|
||||
// No matching processes
|
||||
if (config.type === DebuggerType.cpplldb && config.waitFor) {
|
||||
if (isWindows) {
|
||||
// On Windows, waitFor on lldb-dap is not supported - so we'll let the user know.
|
||||
void logger.getOutputChannelLogger().showWarningMessage(localize("waitFor.not.supported", "The {0} debugger does not support '{1}' on Windows.", "LLDB-DAP", "waitFor"));
|
||||
|
||||
// Drop the waitFor and program (which will just have the picker show all processes).
|
||||
delete config.program;
|
||||
delete config.waitFor;
|
||||
}
|
||||
|
||||
// They did specify 'waitFor' so then let's get out fast and let lldb-dap handle it.
|
||||
logger.note(localize("waiting.for.process", "Waiting to attach process '{0}'", config.program));
|
||||
translateToLldbDap(config);
|
||||
Telemetry.logDebuggerEvent(config.request, { "debuggerType": config.type });
|
||||
return config;
|
||||
}
|
||||
|
||||
// Just let the picker show all processes.
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// If there is only a single process that matches, auto-select it.
|
||||
processId = config.processId = items[0].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If they didn't specify a processId, we'll show the picker.
|
||||
if (!processId) {
|
||||
// Show the picker to let the user select a process, but if they did specify a program, we'll narrow the list for them.
|
||||
const attacher: AttachPicker = new AttachPicker(attachItemsProvider);
|
||||
processId = await attacher.ShowAttachEntriesFiltered(config.program, token);
|
||||
}
|
||||
}
|
||||
|
||||
if (processId) {
|
||||
config.processId = processId;
|
||||
delete config.program;
|
||||
} else {
|
||||
void logger.getOutputChannelLogger().showErrorMessage("No process was selected.");
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Finish translating the config for lldb-dap.
|
||||
if (config.type === DebuggerType.cpplldb) {
|
||||
translateToLldbDap(config);
|
||||
Telemetry.logDebuggerEvent(config.request, { "debuggerType": config.type });
|
||||
return config;
|
||||
}
|
||||
|
||||
Telemetry.logDebuggerEvent(config.request, { "debuggerType": config.type, mimode: config.MIMode });
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -394,7 +445,7 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
|
||||
if (!command) {
|
||||
return false;
|
||||
}
|
||||
if (defaultTemplateConfig.name.startsWith("(Windows) ")) {
|
||||
if (defaultTemplateConfig?.name.startsWith("(Windows) ")) {
|
||||
if (command.startsWith("cl.exe")) {
|
||||
return true;
|
||||
}
|
||||
@@ -427,13 +478,17 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
|
||||
const newConfig: CppDebugConfiguration = { ...defaultTemplateConfig }; // Copy enumerables and properties
|
||||
newConfig.existing = false;
|
||||
|
||||
newConfig.name = configPrefix + compilerName + " " + this.buildAndDebugActiveFileStr();
|
||||
newConfig.name = `${configPrefix}${compilerName} [Debugger: ${newConfig.type}] ${this.buildAndDebugActiveFileStr()}`;
|
||||
newConfig.preLaunchTask = task.name;
|
||||
if (newConfig.type === DebuggerType.cppdbg) {
|
||||
newConfig.externalConsole = false;
|
||||
} else {
|
||||
newConfig.console = "integratedTerminal";
|
||||
switch (newConfig.type) {
|
||||
case DebuggerType.cppdbg:
|
||||
newConfig.externalConsole = false;
|
||||
break;
|
||||
case DebuggerType.cppvsdbg:
|
||||
newConfig.console = "integratedTerminal";
|
||||
break;
|
||||
}
|
||||
|
||||
// Extract the .exe path from the defined task.
|
||||
const definedExePath: string | undefined = util.findExePathInArgs(task.definition.args);
|
||||
newConfig.program = definedExePath ? definedExePath : util.defaultExePath();
|
||||
@@ -473,28 +528,27 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
|
||||
newConfig.type = DebuggerType.cppvsdbg;
|
||||
return newConfig;
|
||||
} else {
|
||||
debuggerName = "gdb";
|
||||
}
|
||||
if (isWindows) {
|
||||
debuggerName = debuggerName.endsWith(".exe") ? debuggerName : (debuggerName + ".exe");
|
||||
debuggerName = executableName("gdb");
|
||||
}
|
||||
const compilerDirname: string = path.dirname(compilerPath);
|
||||
const debuggerPath: string = path.join(compilerDirname, debuggerName);
|
||||
|
||||
// Check if debuggerPath exists.
|
||||
if (await util.checkFileExists(debuggerPath)) {
|
||||
newConfig.miDebuggerPath = debuggerPath;
|
||||
} else if (await util.whichAsync(debuggerName) !== undefined) {
|
||||
// Check if debuggerName exists on $PATH
|
||||
newConfig.miDebuggerPath = debuggerName;
|
||||
} else {
|
||||
// Try the usr path for non-Windows platforms.
|
||||
const usrDebuggerPath: string = path.join("/usr", "bin", debuggerName);
|
||||
if (!isWindows && await util.checkFileExists(usrDebuggerPath)) {
|
||||
newConfig.miDebuggerPath = usrDebuggerPath;
|
||||
if (newConfig.type === DebuggerType.cppdbg) {
|
||||
// Check if debuggerPath exists.
|
||||
if (await util.checkFileExists(debuggerPath)) {
|
||||
newConfig.miDebuggerPath = debuggerPath;
|
||||
} else if (await util.whichAsync(debuggerName) !== undefined) {
|
||||
// Check if debuggerName exists on $PATH
|
||||
newConfig.miDebuggerPath = debuggerName;
|
||||
} else {
|
||||
logger.getOutputChannelLogger().appendLine(localize('debugger.path.not.exists', "Unable to find the {0} debugger. The debug configuration for {1} is ignored.", `\"${debuggerName}\"`, compilerName));
|
||||
return undefined;
|
||||
// Try the usr path for non-Windows platforms.
|
||||
const usrDebuggerPath: string = path.join("/usr", "bin", debuggerName);
|
||||
if (!isWindows && await util.checkFileExists(usrDebuggerPath)) {
|
||||
newConfig.miDebuggerPath = usrDebuggerPath;
|
||||
} else {
|
||||
logger.getOutputChannelLogger().appendLine(localize('debugger.path.not.exists', "Unable to find the {0} debugger. The debug configuration for {1} is ignored.", `\"${debuggerName}\"`, compilerName));
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -890,11 +944,12 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
|
||||
await cppBuildTaskProvider.writeBuildTask(selectedConfig.preLaunchTask);
|
||||
}
|
||||
// Remove the extra properties that are not a part of the DebugConfiguration, as these properties will be written in launch.json.
|
||||
selectedConfig.detail = undefined;
|
||||
selectedConfig.taskStatus = undefined;
|
||||
selectedConfig.isDefault = undefined;
|
||||
selectedConfig.source = undefined;
|
||||
selectedConfig.debuggerEvent = undefined;
|
||||
// Use 'delete' instead of 'undefined' to remove the property from the object (undefined properties still exist!).
|
||||
delete selectedConfig.detail;
|
||||
delete selectedConfig.taskStatus;
|
||||
delete selectedConfig.isDefault;
|
||||
delete selectedConfig.source;
|
||||
delete selectedConfig.debuggerEvent;
|
||||
// Write debug configuration in launch.json file.
|
||||
await this.writeDebugConfig(selectedConfig, isExistingConfig, folder);
|
||||
Telemetry.logDebuggerEvent(DebuggerEvent.addConfigGear, { "configSource": ConfigSource.workspaceFolder, "configMode": ConfigMode.launchConfig, "cancelled": "false", "succeeded": "true" });
|
||||
@@ -934,10 +989,19 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
|
||||
}
|
||||
|
||||
// Get debug configurations for all debugger types.
|
||||
let configs: CppDebugConfiguration[] = await this.provideDebugConfigurationsForType(DebuggerType.cppdbg, folder);
|
||||
if (os.platform() === 'win32') {
|
||||
configs = configs.concat(await this.provideDebugConfigurationsForType(DebuggerType.cppvsdbg, folder));
|
||||
let configs: CppDebugConfiguration[] = [];
|
||||
if (isWindows) {
|
||||
// When on Windows - prefer cppvsdbg.
|
||||
configs.push(...await this.provideDebugConfigurationsForType(DebuggerType.cppvsdbg, folder));
|
||||
}
|
||||
|
||||
if (!isWindows && await findLldbDap()) {
|
||||
// Only include lldb-dap tasks if it's not Windows and lldb-dap is actually available.
|
||||
configs.push(...await this.provideDebugConfigurationsForType(DebuggerType.cpplldb, folder));
|
||||
}
|
||||
|
||||
configs.push(...await this.provideDebugConfigurationsForType(DebuggerType.cppdbg, folder));
|
||||
|
||||
if (onlyWorkspaceFolder) {
|
||||
configs = configs.filter(item => !item.configSource || item.configSource === ConfigSource.workspaceFolder);
|
||||
}
|
||||
@@ -1065,7 +1129,7 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
|
||||
return false;
|
||||
}
|
||||
|
||||
let scpResult: util.ProcessReturnType;
|
||||
let scpResult: ProcessReturnType;
|
||||
if (isScp) {
|
||||
scpResult = await scp(files, host, step.targetDir, config.scpPath, config.recursive, jumpHosts, cancellationToken);
|
||||
} else {
|
||||
@@ -1086,7 +1150,7 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
|
||||
const jumpHosts: util.ISshHostInfo[] = step.host.jumpHosts;
|
||||
const localForwards: util.ISshLocalForwardInfo[] = step.host.localForwards;
|
||||
const continueOn: string = step.continueOn;
|
||||
const sshResult: util.ProcessReturnType = await ssh(host, step.command, config.sshPath, jumpHosts, localForwards, continueOn, cancellationToken);
|
||||
const sshResult: ProcessReturnType = await ssh(host, step.command, config.sshPath, jumpHosts, localForwards, continueOn, cancellationToken);
|
||||
if (!sshResult.succeeded || cancellationToken?.isCancellationRequested) {
|
||||
return false;
|
||||
}
|
||||
@@ -1097,7 +1161,7 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
|
||||
void logger.getOutputChannelLogger().showErrorMessage(localize('missing.properties.shell', '"command" is required for shell steps.'));
|
||||
return false;
|
||||
}
|
||||
const taskResult: util.ProcessReturnType = await util.spawnChildProcess(step.command, undefined, step.continueOn);
|
||||
const taskResult: ProcessReturnType = await spawnChildProcess(step.command, undefined, step.continueOn);
|
||||
if (!taskResult.succeeded || cancellationToken?.isCancellationRequested) {
|
||||
void logger.getOutputChannelLogger().showErrorMessage(taskResult.output);
|
||||
return false;
|
||||
@@ -1113,13 +1177,26 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
|
||||
}
|
||||
}
|
||||
|
||||
export interface IConfigurationAssetProvider {
|
||||
getInitialConfigurations(debuggerType: DebuggerType): any;
|
||||
getConfigurationSnippets(): vscode.CompletionItem[];
|
||||
}
|
||||
export class ConfigurationAssetProvider {
|
||||
configurations: Configuration[] = [];
|
||||
|
||||
export class ConfigurationAssetProviderFactory {
|
||||
public static getConfigurationProvider(): IConfigurationAssetProvider {
|
||||
protected constructor() {
|
||||
}
|
||||
|
||||
public getInitialConfigurations(debuggerType: DebuggerType): any[] {
|
||||
return this.configurations.map(configuration => configuration.GetLaunchConfiguration())
|
||||
.filter(snippet => snippet.debuggerType === debuggerType && snippet.isInitialConfiguration)
|
||||
.map(snippet => snippet.body);
|
||||
}
|
||||
|
||||
public getConfigurationSnippets(): vscode.CompletionItem[] {
|
||||
return this.configurations.map(configuration => [
|
||||
convertConfigurationSnippetToCompletionItem(configuration.GetLaunchConfiguration()),
|
||||
convertConfigurationSnippetToCompletionItem(configuration.GetAttachConfiguration())]
|
||||
).flat();
|
||||
}
|
||||
|
||||
static getConfigurationAssetProvider(): ConfigurationAssetProvider {
|
||||
switch (os.platform()) {
|
||||
case 'win32':
|
||||
return new WindowsConfigurationProvider();
|
||||
@@ -1128,104 +1205,79 @@ export class ConfigurationAssetProviderFactory {
|
||||
case 'linux':
|
||||
return new LinuxConfigurationProvider();
|
||||
default:
|
||||
throw new Error(localize("unexpected.os", "Unexpected OS type"));
|
||||
return new ConfigurationAssetProvider(); // At least this will return empty results if the platform is not recognized.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class DefaultConfigurationProvider implements IConfigurationAssetProvider {
|
||||
configurations: IConfiguration[] = [];
|
||||
|
||||
public getInitialConfigurations(debuggerType: DebuggerType): any {
|
||||
const configurationSnippet: IConfigurationSnippet[] = [];
|
||||
|
||||
// Only launch configurations are initial configurations
|
||||
this.configurations.forEach(configuration => {
|
||||
configurationSnippet.push(configuration.GetLaunchConfiguration());
|
||||
});
|
||||
|
||||
const initialConfigurations: any = configurationSnippet.filter(snippet => snippet.debuggerType === debuggerType && snippet.isInitialConfiguration)
|
||||
.map(snippet => JSON.parse(snippet.bodyText));
|
||||
|
||||
// If configurations is empty, then it will only have an empty configurations array in launch.json. Users can still add snippets.
|
||||
return initialConfigurations;
|
||||
}
|
||||
|
||||
public getConfigurationSnippets(): vscode.CompletionItem[] {
|
||||
const completionItems: vscode.CompletionItem[] = [];
|
||||
|
||||
this.configurations.forEach(configuration => {
|
||||
completionItems.push(convertConfigurationSnippetToCompletionItem(configuration.GetLaunchConfiguration()));
|
||||
completionItems.push(convertConfigurationSnippetToCompletionItem(configuration.GetAttachConfiguration()));
|
||||
});
|
||||
|
||||
return completionItems;
|
||||
}
|
||||
}
|
||||
|
||||
class WindowsConfigurationProvider extends DefaultConfigurationProvider {
|
||||
class WindowsConfigurationProvider extends ConfigurationAssetProvider {
|
||||
private executable: string = "a.exe";
|
||||
private pipeProgram: string = "<" + localize("path.to.pipe.program", "full path to pipe program such as {0}", "plink.exe").replace(/"/g, '') + ">";
|
||||
private MIMode: string = 'gdb';
|
||||
private setupCommandsBlock: string = `"setupCommands": [
|
||||
{
|
||||
"description": "${localize("enable.pretty.printing", "Enable pretty-printing for {0}", "gdb").replace(/"/g, '')}",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
},
|
||||
{
|
||||
"description": "${localize("enable.intel.disassembly.flavor", "Set Disassembly Flavor to {0}", "Intel").replace(/"/g, '')}",
|
||||
"text": "-gdb-set disassembly-flavor intel",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]`;
|
||||
|
||||
private additionalProperties = {
|
||||
setupCommands: [
|
||||
{
|
||||
description: localize("enable.pretty.printing", "Enable pretty-printing for {0}", "gdb").replace(/"/g, ''),
|
||||
text: "-enable-pretty-printing",
|
||||
ignoreFailures: true
|
||||
},
|
||||
{
|
||||
description: localize("enable.intel.disassembly.flavor", "Set Disassembly Flavor to {0}", "Intel").replace(/"/g, ''),
|
||||
text: "-gdb-set disassembly-flavor intel",
|
||||
ignoreFailures: true
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.configurations = [
|
||||
new MIConfigurations(this.MIMode, this.executable, this.pipeProgram, this.setupCommandsBlock),
|
||||
new PipeTransportConfigurations(this.MIMode, this.executable, this.pipeProgram, this.setupCommandsBlock),
|
||||
new WindowsConfigurations(this.MIMode, this.executable, this.pipeProgram, this.setupCommandsBlock),
|
||||
new WSLConfigurations(this.MIMode, this.executable, this.pipeProgram, this.setupCommandsBlock)
|
||||
new WindowsConfigurations(this.executable, this.additionalProperties),
|
||||
new MIConfigurations('gdb', this.executable, this.additionalProperties),
|
||||
new PipeTransportConfigurations('gdb', this.executable, this.pipeProgram, this.additionalProperties),
|
||||
new WSLConfigurations('gdb', this.executable, this.additionalProperties),
|
||||
new LldbDapConfigurations(this.executable)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class OSXConfigurationProvider extends DefaultConfigurationProvider {
|
||||
private MIMode: string = 'lldb';
|
||||
class OSXConfigurationProvider extends ConfigurationAssetProvider {
|
||||
|
||||
private executable: string = "a.out";
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.configurations = [
|
||||
new LldbDapConfigurations(this.executable),
|
||||
new MIConfigurations('lldb', this.executable)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class LinuxConfigurationProvider extends ConfigurationAssetProvider {
|
||||
private additionalProperties = {
|
||||
setupCommands: [
|
||||
{
|
||||
description: localize("enable.pretty.printing", "Enable pretty-printing for {0}", "gdb").replace(/"/g, ''),
|
||||
text: "-enable-pretty-printing",
|
||||
ignoreFailures: true
|
||||
},
|
||||
{
|
||||
description: localize("enable.intel.disassembly.flavor", "Set Disassembly Flavor to {0}", "Intel").replace(/"/g, ''),
|
||||
text: "-gdb-set disassembly-flavor intel",
|
||||
ignoreFailures: true
|
||||
}
|
||||
]
|
||||
};
|
||||
private executable: string = "a.out";
|
||||
private pipeProgram: string = "/usr/bin/ssh";
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.configurations = [
|
||||
new MIConfigurations(this.MIMode, this.executable, this.pipeProgram)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class LinuxConfigurationProvider extends DefaultConfigurationProvider {
|
||||
private MIMode: string = 'gdb';
|
||||
private setupCommandsBlock: string = `"setupCommands": [
|
||||
{
|
||||
"description": "${localize("enable.pretty.printing", "Enable pretty-printing for {0}", "gdb").replace(/"/g, '')}",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
},
|
||||
{
|
||||
"description": "${localize("enable.intel.disassembly.flavor", "Set Disassembly Flavor to {0}", "Intel").replace(/"/g, '')}",
|
||||
"text": "-gdb-set disassembly-flavor intel",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]`;
|
||||
private executable: string = "a.out";
|
||||
private pipeProgram: string = "/usr/bin/ssh";
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.configurations = [
|
||||
new MIConfigurations(this.MIMode, this.executable, this.pipeProgram, this.setupCommandsBlock),
|
||||
new PipeTransportConfigurations(this.MIMode, this.executable, this.pipeProgram, this.setupCommandsBlock)
|
||||
new MIConfigurations('gdb', this.executable, this.additionalProperties),
|
||||
new PipeTransportConfigurations('gdb', this.executable, this.pipeProgram, this.additionalProperties),
|
||||
new LldbDapConfigurations(this.executable)
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1233,17 +1285,15 @@ class LinuxConfigurationProvider extends DefaultConfigurationProvider {
|
||||
function convertConfigurationSnippetToCompletionItem(snippet: IConfigurationSnippet): vscode.CompletionItem {
|
||||
const item: vscode.CompletionItem = new vscode.CompletionItem(snippet.label, vscode.CompletionItemKind.Module);
|
||||
|
||||
item.insertText = snippet.bodyText;
|
||||
item.insertText = JSON.stringify(snippet.body, null, '\t');
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
export class ConfigurationSnippetProvider implements vscode.CompletionItemProvider {
|
||||
private provider: IConfigurationAssetProvider;
|
||||
private snippets: vscode.CompletionItem[];
|
||||
|
||||
constructor(provider: IConfigurationAssetProvider) {
|
||||
this.provider = provider;
|
||||
constructor(private provider: ConfigurationAssetProvider) {
|
||||
this.snippets = this.provider.getConfigurationSnippets();
|
||||
}
|
||||
public resolveCompletionItem(item: vscode.CompletionItem, _token: vscode.CancellationToken): Thenable<vscode.CompletionItem> {
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import * as os from 'os';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { configPrefix } from '../LanguageServer/extension';
|
||||
import { isWindows } from '../constants';
|
||||
|
||||
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
|
||||
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
|
||||
|
||||
export function isDebugLaunchStr(str: string): boolean {
|
||||
return str.startsWith("(gdb) ") || str.startsWith("(lldb) ") || str.startsWith("(Windows) ");
|
||||
return str.startsWith("(gdb) ") || str.startsWith("(lldb") || str.startsWith("(Windows) ");
|
||||
}
|
||||
|
||||
export interface ConfigMenu extends vscode.QuickPickItem {
|
||||
@@ -22,6 +22,7 @@ export interface ConfigMenu extends vscode.QuickPickItem {
|
||||
export enum DebuggerType {
|
||||
cppvsdbg = "cppvsdbg",
|
||||
cppdbg = "cppdbg",
|
||||
cpplldb = "cpplldb",
|
||||
all = "all"
|
||||
}
|
||||
|
||||
@@ -70,147 +71,121 @@ export interface CppDebugConfiguration extends vscode.DebugConfiguration {
|
||||
export interface IConfigurationSnippet {
|
||||
label: string;
|
||||
description: string;
|
||||
bodyText: string;
|
||||
body: Record<string, any>;
|
||||
|
||||
// Internal
|
||||
isInitialConfiguration?: boolean;
|
||||
debuggerType: DebuggerType;
|
||||
}
|
||||
|
||||
export function indentJsonString(json: string, numTabs: number = 1): string {
|
||||
return json.split('\n').map(line => '\t'.repeat(numTabs) + line).join('\n').trim();
|
||||
function createLaunchBlock(name: string, type: string, executable: string): Record<string, any> {
|
||||
return {
|
||||
name: name,
|
||||
type: type,
|
||||
request: "launch",
|
||||
program: localize("enter.program.name", "enter program name, for example {0}", "${workspaceFolder}" + "/" + executable).replace(/"/g, ''),
|
||||
args: [],
|
||||
stopAtEntry: false,
|
||||
cwd: "${fileDirname}",
|
||||
environment: [],
|
||||
externalConsole: type === DebuggerType.cppdbg ? false : type === DebuggerType.cpplldb ? true : undefined,
|
||||
console: type === DebuggerType.cppvsdbg ? "externalTerminal" : undefined
|
||||
};
|
||||
}
|
||||
|
||||
function formatString(format: string, args: string[]): string {
|
||||
args.forEach((arg: string, index: number) => {
|
||||
format = format.replace("{" + index + "}", arg);
|
||||
});
|
||||
return format;
|
||||
function createAttachBlock(name: string, type: string, executable: string): Record<string, any> {
|
||||
return {
|
||||
name: name,
|
||||
type: type,
|
||||
request: "attach",
|
||||
program: type === DebuggerType.cppdbg ? localize("enter.program.name", "enter program name, for example {0}", "${workspaceFolder}" + "/" + executable).replace(/"/g, '') : undefined
|
||||
};
|
||||
}
|
||||
|
||||
function createLaunchString(name: string, type: string, executable: string): string {
|
||||
return `"name": "${name}",
|
||||
"type": "${type}",
|
||||
"request": "launch",
|
||||
"program": "${localize("enter.program.name", "enter program name, for example {0}", "$\{workspaceFolder\}" + "/" + executable).replace(/"/g, '')}",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "$\{fileDirname\}",
|
||||
"environment": [],
|
||||
${ type === "cppdbg" ? `"externalConsole": false` : `"console": "externalTerminal"` }
|
||||
`;
|
||||
function createRemoteAttachBlock(name: string, type: string, executable: string): Record<string, any> {
|
||||
return {
|
||||
name: name,
|
||||
type: type,
|
||||
request: "attach",
|
||||
program: localize("enter.program.name", "enter program name, for example {0}", "${workspaceFolder}" + "/" + executable).replace(/"/g, ''),
|
||||
processId: "${command:pickRemoteProcess}"
|
||||
};
|
||||
}
|
||||
|
||||
function createAttachString(name: string, type: string, executable: string): string {
|
||||
return formatString(`
|
||||
"name": "${name}",
|
||||
"type": "${type}",
|
||||
"request": "attach",{0}
|
||||
`, [type === "cppdbg" ? `${os.EOL}"program": "${localize("enter.program.name", "enter program name, for example {0}", "$\{workspaceFolder\}" + "/" + executable).replace(/"/g, '')}",` : ""]);
|
||||
function createPipeTransportBlock(pipeProgram: string, debuggerProgram: string, pipeArgs: string[] = []): Record<string, any> {
|
||||
return {
|
||||
pipeTransport: {
|
||||
debuggerPath: `/usr/bin/${debuggerProgram}`,
|
||||
pipeProgram: pipeProgram,
|
||||
pipeArgs: pipeArgs,
|
||||
pipeCwd: ""
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createRemoteAttachString(name: string, type: string, executable: string): string {
|
||||
return `
|
||||
"name": "${name}",
|
||||
"type": "${type}",
|
||||
"request": "attach",
|
||||
"program": "${localize("enter.program.name", "enter program name, for example {0}", "$\{workspaceFolder\}" + "/" + executable).replace(/"/g, '')}",
|
||||
"processId": "$\{command:pickRemoteProcess\}"
|
||||
`;
|
||||
}
|
||||
|
||||
function createPipeTransportString(pipeProgram: string, debuggerProgram: string, pipeArgs: string[] = []): string {
|
||||
return `
|
||||
"pipeTransport": {
|
||||
\t"debuggerPath": "/usr/bin/${debuggerProgram}",
|
||||
\t"pipeProgram": "${pipeProgram}",
|
||||
\t"pipeArgs": ${JSON.stringify(pipeArgs)},
|
||||
\t"pipeCwd": ""
|
||||
}`;
|
||||
}
|
||||
|
||||
export interface IConfiguration {
|
||||
GetLaunchConfiguration(): IConfigurationSnippet;
|
||||
GetAttachConfiguration(): IConfigurationSnippet;
|
||||
}
|
||||
|
||||
abstract class Configuration implements IConfiguration {
|
||||
|
||||
public executable: string;
|
||||
public pipeProgram: string;
|
||||
public MIMode: string;
|
||||
public additionalProperties: string;
|
||||
|
||||
public miDebugger = "cppdbg";
|
||||
public windowsDebugger = "cppvsdbg";
|
||||
|
||||
constructor(MIMode: string, executable: string, pipeProgram: string, additionalProperties: string = "") {
|
||||
this.MIMode = MIMode;
|
||||
this.executable = executable;
|
||||
this.pipeProgram = pipeProgram;
|
||||
this.additionalProperties = additionalProperties;
|
||||
}
|
||||
|
||||
export abstract class Configuration {
|
||||
abstract GetLaunchConfiguration(): IConfigurationSnippet;
|
||||
abstract GetAttachConfiguration(): IConfigurationSnippet;
|
||||
}
|
||||
|
||||
/** Creates Configurations for an MI debugger */
|
||||
export class MIConfigurations extends Configuration {
|
||||
|
||||
constructor(public MIMode: string, public executable: string, public additionalProperties: Record<string, any> = {}) {
|
||||
super();
|
||||
}
|
||||
|
||||
public GetLaunchConfiguration(): IConfigurationSnippet {
|
||||
const name: string = `(${this.MIMode}) ${localize("launch.string", "Launch").replace(/"/g, '')}`;
|
||||
|
||||
const body: string = formatString(`{
|
||||
\t${indentJsonString(createLaunchString(name, this.miDebugger, this.executable))},
|
||||
\t"MIMode": "${this.MIMode}"{0}{1}
|
||||
}`, [this.miDebugger === "cppdbg" && os.platform() === "win32" ? `,${os.EOL}\t"miDebuggerPath": "/path/to/gdb"` : "",
|
||||
this.additionalProperties ? `,${os.EOL}\t${indentJsonString(this.additionalProperties)}` : ""]);
|
||||
|
||||
return {
|
||||
"label": configPrefix + name,
|
||||
"description": localize("launch.with", "Launch with {0}.", this.MIMode).replace(/"/g, ''),
|
||||
"bodyText": body.trim(),
|
||||
"isInitialConfiguration": true,
|
||||
"debuggerType": DebuggerType.cppdbg
|
||||
label: configPrefix + name,
|
||||
description: localize("launch.with", "Launch with {0}.", this.MIMode).replace(/"/g, ''),
|
||||
body: {
|
||||
...createLaunchBlock(name, DebuggerType.cppdbg, this.executable),
|
||||
MIMode: this.MIMode,
|
||||
miDebuggerPath: isWindows ? `<${localize("path.to.gdb", "path to gdb").replace(/"/g, '')}>` : undefined,
|
||||
...this.additionalProperties
|
||||
},
|
||||
isInitialConfiguration: true,
|
||||
debuggerType: DebuggerType.cppdbg
|
||||
};
|
||||
}
|
||||
|
||||
public GetAttachConfiguration(): IConfigurationSnippet {
|
||||
const name: string = `(${this.MIMode}) ${localize("attach.string", "Attach").replace(/"/g, '')}`;
|
||||
|
||||
const body: string = formatString(`{
|
||||
\t${indentJsonString(createAttachString(name, this.miDebugger, this.executable))}
|
||||
\t"MIMode": "${this.MIMode}"{0}{1}
|
||||
}`, [this.miDebugger === "cppdbg" && os.platform() === "win32" ? `,${os.EOL}\t"miDebuggerPath": "/path/to/gdb"` : "",
|
||||
this.additionalProperties ? `,${os.EOL}\t${indentJsonString(this.additionalProperties)}` : ""]);
|
||||
|
||||
return {
|
||||
"label": configPrefix + name,
|
||||
"description": localize("attach.with", "Attach with {0}.", this.MIMode).replace(/"/g, ''),
|
||||
"bodyText": body.trim(),
|
||||
"debuggerType": DebuggerType.cppdbg
|
||||
label: configPrefix + name,
|
||||
description: localize("attach.with", "Attach with {0}.", this.MIMode).replace(/"/g, ''),
|
||||
body: {
|
||||
...createAttachBlock(name, DebuggerType.cppdbg, this.executable),
|
||||
MIMode: this.MIMode,
|
||||
miDebuggerPath: isWindows ? `<${localize("path.to.gdb", "path to gdb").replace(/"/g, '')}>` : undefined,
|
||||
...this.additionalProperties
|
||||
},
|
||||
debuggerType: DebuggerType.cppdbg
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class PipeTransportConfigurations extends Configuration {
|
||||
|
||||
constructor(public MIMode: string, public executable: string, public pipeProgram: string, public additionalProperties: Record<string, any> = {}) {
|
||||
super();
|
||||
}
|
||||
public GetLaunchConfiguration(): IConfigurationSnippet {
|
||||
const name: string = `(${this.MIMode}) ${localize("pipe.launch", "Pipe Launch").replace(/"/g, '')}`;
|
||||
|
||||
const body: string = formatString(`
|
||||
{
|
||||
\t${indentJsonString(createLaunchString(name, this.miDebugger, this.executable))},
|
||||
\t${indentJsonString(createPipeTransportString(this.pipeProgram, this.MIMode))},
|
||||
\t"MIMode": "${this.MIMode}"{0}
|
||||
}`, [this.additionalProperties ? `,${os.EOL}\t${indentJsonString(this.additionalProperties)}` : ""]);
|
||||
|
||||
return {
|
||||
"label": configPrefix + name,
|
||||
"description": localize("pipe.launch.with", "Pipe Launch with {0}.", this.MIMode).replace(/"/g, ''),
|
||||
"bodyText": body.trim(),
|
||||
"debuggerType": DebuggerType.cppdbg
|
||||
label: configPrefix + name,
|
||||
description: localize("pipe.launch.with", "Pipe Launch with {0}.", this.MIMode).replace(/"/g, ''),
|
||||
body: {
|
||||
...createLaunchBlock(name, DebuggerType.cppdbg, this.executable),
|
||||
...createPipeTransportBlock(this.pipeProgram, this.MIMode),
|
||||
MIMode: this.MIMode,
|
||||
...this.additionalProperties
|
||||
},
|
||||
debuggerType: DebuggerType.cppdbg
|
||||
};
|
||||
|
||||
}
|
||||
@@ -218,38 +193,35 @@ export class PipeTransportConfigurations extends Configuration {
|
||||
public GetAttachConfiguration(): IConfigurationSnippet {
|
||||
const name: string = `(${this.MIMode}) ${localize("pipe.attach", "Pipe Attach").replace(/"/g, '')}`;
|
||||
|
||||
const body: string = formatString(`
|
||||
{
|
||||
\t${indentJsonString(createRemoteAttachString(name, this.miDebugger, this.executable))},
|
||||
\t${indentJsonString(createPipeTransportString(this.pipeProgram, this.MIMode))},
|
||||
\t"MIMode": "${this.MIMode}"{0}
|
||||
}`, [this.additionalProperties ? `,${os.EOL}\t${indentJsonString(this.additionalProperties)}` : ""]);
|
||||
return {
|
||||
"label": configPrefix + name,
|
||||
"description": localize("pipe.attach.with", "Pipe Attach with {0}.", this.MIMode).replace(/"/g, ''),
|
||||
"bodyText": body.trim(),
|
||||
"debuggerType": DebuggerType.cppdbg
|
||||
label: configPrefix + name,
|
||||
description: localize("pipe.attach.with", "Pipe Attach with {0}.", this.MIMode).replace(/"/g, ''),
|
||||
body: {
|
||||
...createRemoteAttachBlock(name, DebuggerType.cppdbg, this.executable),
|
||||
...createPipeTransportBlock(this.pipeProgram, this.MIMode),
|
||||
MIMode: this.MIMode,
|
||||
...this.additionalProperties
|
||||
},
|
||||
debuggerType: DebuggerType.cppdbg
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class WindowsConfigurations extends Configuration {
|
||||
constructor(public executable: string, public additionalProperties: Record<string, any> = {}) {
|
||||
super();
|
||||
}
|
||||
|
||||
public GetLaunchConfiguration(): IConfigurationSnippet {
|
||||
const name: string = `(Windows) ${localize("launch.string", "Launch").replace(/"/g, '')}`;
|
||||
|
||||
const body: string = `
|
||||
{
|
||||
\t${indentJsonString(createLaunchString(name, this.windowsDebugger, this.executable))}
|
||||
}`;
|
||||
|
||||
return {
|
||||
"label": configPrefix + name,
|
||||
"description": localize("launch.with.vs.debugger", "Launch with the Visual Studio C/C++ debugger.").replace(/"/g, ''),
|
||||
"bodyText": body.trim(),
|
||||
"isInitialConfiguration": true,
|
||||
"debuggerType": DebuggerType.cppvsdbg
|
||||
label: configPrefix + name,
|
||||
description: localize("launch.with.vs.debugger", "Launch with the Visual Studio C/C++ debugger.").replace(/"/g, ''),
|
||||
body: createLaunchBlock(name, DebuggerType.cppvsdbg, this.executable),
|
||||
isInitialConfiguration: true,
|
||||
debuggerType: DebuggerType.cppvsdbg
|
||||
};
|
||||
|
||||
}
|
||||
@@ -257,56 +229,85 @@ export class WindowsConfigurations extends Configuration {
|
||||
public GetAttachConfiguration(): IConfigurationSnippet {
|
||||
const name: string = `(Windows) ${localize("attach.string", "Attach").replace(/"/g, '')}`;
|
||||
|
||||
const body: string = `
|
||||
{
|
||||
\t${indentJsonString(createAttachString(name, this.windowsDebugger, this.executable))}
|
||||
}`;
|
||||
|
||||
return {
|
||||
"label": configPrefix + name,
|
||||
"description": localize("attach.with.vs.debugger", "Attach to a process with the Visual Studio C/C++ debugger.").replace(/"/g, ''),
|
||||
"bodyText": body.trim(),
|
||||
"debuggerType": DebuggerType.cppvsdbg
|
||||
label: configPrefix + name,
|
||||
description: localize("attach.with.vs.debugger", "Attach to a process with the Visual Studio C/C++ debugger.").replace(/"/g, ''),
|
||||
body: createAttachBlock(name, DebuggerType.cppvsdbg, this.executable),
|
||||
debuggerType: DebuggerType.cppvsdbg
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class WSLConfigurations extends Configuration {
|
||||
constructor(public MIMode: string, public executable: string, public additionalProperties: Record<string, any> = {}) {
|
||||
super();
|
||||
}
|
||||
// Detects if the current VSCode is 32-bit and uses the correct bash.exe
|
||||
public bashPipeProgram = process.arch === 'ia32' ? "${env:windir}\\\\sysnative\\\\bash.exe" : "${env:windir}\\\\system32\\\\bash.exe";
|
||||
|
||||
public GetLaunchConfiguration(): IConfigurationSnippet {
|
||||
const name: string = `(${this.MIMode}) ${localize("bash.on.windows.launch", "Bash on Windows Launch").replace(/"/g, '')}`;
|
||||
|
||||
const body: string = formatString(`
|
||||
{
|
||||
\t${indentJsonString(createLaunchString(name, this.miDebugger, this.executable))},
|
||||
\t${indentJsonString(createPipeTransportString(this.bashPipeProgram, this.MIMode, ["-c"]))}{0}
|
||||
}`, [this.additionalProperties ? `,${os.EOL}\t${indentJsonString(this.additionalProperties)}` : ""]);
|
||||
|
||||
return {
|
||||
"label": configPrefix + name,
|
||||
"description": localize("launch.bash.windows", "Launch in Bash on Windows using {0}.", this.MIMode).replace(/"/g, ''),
|
||||
"bodyText": body.trim(),
|
||||
"debuggerType": DebuggerType.cppdbg
|
||||
label: configPrefix + name,
|
||||
description: localize("launch.bash.windows", "Launch in Bash on Windows using {0}.", this.MIMode).replace(/"/g, ''),
|
||||
body: {
|
||||
...createLaunchBlock(name, DebuggerType.cppdbg, this.executable),
|
||||
...createPipeTransportBlock(this.bashPipeProgram, this.MIMode, ["-c"]),
|
||||
...this.additionalProperties
|
||||
},
|
||||
debuggerType: DebuggerType.cppdbg
|
||||
};
|
||||
}
|
||||
|
||||
public GetAttachConfiguration(): IConfigurationSnippet {
|
||||
const name: string = `(${this.MIMode}) ${localize("bash.on.windows.attach", "Bash on Windows Attach").replace(/"/g, '')}`;
|
||||
|
||||
const body: string = formatString(`
|
||||
{
|
||||
\t${indentJsonString(createRemoteAttachString(name, this.miDebugger, this.executable))},
|
||||
\t${indentJsonString(createPipeTransportString(this.bashPipeProgram, this.MIMode, ["-c"]))}{0}
|
||||
}`, [this.additionalProperties ? `,${os.EOL}\t${indentJsonString(this.additionalProperties)}` : ""]);
|
||||
|
||||
return {
|
||||
"label": configPrefix + name,
|
||||
"description": localize("remote.attach.bash.windows", "Attach to a remote process running in Bash on Windows using {0}.", this.MIMode).replace(/"/g, ''),
|
||||
"bodyText": body.trim(),
|
||||
"debuggerType": DebuggerType.cppdbg
|
||||
label: configPrefix + name,
|
||||
description: localize("remote.attach.bash.windows", "Attach to a remote process running in Bash on Windows using {0}.", this.MIMode).replace(/"/g, ''),
|
||||
body: {
|
||||
...createRemoteAttachBlock(name, DebuggerType.cppdbg, this.executable),
|
||||
...createPipeTransportBlock(this.bashPipeProgram, this.MIMode, ["-c"]),
|
||||
...this.additionalProperties
|
||||
},
|
||||
debuggerType: DebuggerType.cppdbg
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates Configurations for an LLDB-DAP debugger */
|
||||
export class LldbDapConfigurations extends Configuration {
|
||||
|
||||
constructor(public executable: string, public additionalProperties: Record<string, any> = {}) {
|
||||
super();
|
||||
}
|
||||
|
||||
public GetLaunchConfiguration(): IConfigurationSnippet {
|
||||
const name: string = `(lldb-dap) ${localize("launch.string", "Launch").replace(/"/g, '')}`;
|
||||
|
||||
return {
|
||||
label: configPrefix + name,
|
||||
description: localize("launch.with", "Launch with {0}.", "LLDB-DAP").replace(/"/g, ''),
|
||||
body: {
|
||||
...createLaunchBlock(name, DebuggerType.cpplldb, this.executable),
|
||||
...this.additionalProperties
|
||||
},
|
||||
isInitialConfiguration: true,
|
||||
debuggerType: DebuggerType.cpplldb
|
||||
};
|
||||
}
|
||||
|
||||
public GetAttachConfiguration(): IConfigurationSnippet {
|
||||
const name: string = `(lldb-dap) ${localize("attach.string", "Attach").replace(/"/g, '')}`;
|
||||
return {
|
||||
label: configPrefix + name,
|
||||
description: localize("attach.with", "Attach with {0}.", "LLDB-DAP").replace(/"/g, ''),
|
||||
body: {
|
||||
...createAttachBlock(name, DebuggerType.cpplldb, this.executable),
|
||||
...this.additionalProperties
|
||||
},
|
||||
debuggerType: DebuggerType.cpplldb
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as vscode from "vscode";
|
||||
import * as nls from 'vscode-nls';
|
||||
import { DebuggerType } from './configurations';
|
||||
import { findLldbDap, isValidLldbDap } from './lldb-dap';
|
||||
|
||||
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
|
||||
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
|
||||
@@ -40,7 +42,7 @@ export class CppvsdbgDebugAdapterDescriptorFactory extends AbstractDebugAdapterD
|
||||
|
||||
async createDebugAdapterDescriptor(_session: vscode.DebugSession, _executable?: vscode.DebugAdapterExecutable): Promise<vscode.DebugAdapterDescriptor | null> {
|
||||
if (os.platform() !== 'win32') {
|
||||
void vscode.window.showErrorMessage(localize("debugger.not.available", "Debugger type '{0}' is not available for non-Windows machines.", "cppvsdbg"));
|
||||
void vscode.window.showErrorMessage(localize("debugger.not.available", "Debugger type '{0}' is not available for non-Windows machines.", DebuggerType.cppvsdbg));
|
||||
return null;
|
||||
} else {
|
||||
return new vscode.DebugAdapterExecutable(
|
||||
@@ -50,3 +52,43 @@ export class CppvsdbgDebugAdapterDescriptorFactory extends AbstractDebugAdapterD
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Generates the command line for the LLDB-DAP debugger */
|
||||
export class CpplldbDebugAdapterDescriptorFactory extends AbstractDebugAdapterDescriptorFactory {
|
||||
async createDebugAdapterDescriptor(session: vscode.DebugSession, executable?: vscode.DebugAdapterExecutable): Promise<vscode.DebugAdapterDescriptor | null> {
|
||||
|
||||
// The adapter path can be specified in the launch.json entry.
|
||||
let adapter: string | undefined = session.configuration.debuggerPath || executable?.command;
|
||||
|
||||
if (adapter) {
|
||||
// Verify that the path is actually valid.
|
||||
if (!await isValidLldbDap(adapter)) {
|
||||
adapter = await findLldbDap();
|
||||
if (adapter) {
|
||||
void vscode.window.showErrorMessage(localize("debugger.path.not.available.falling.back", "The specified LLDB-DAP debuggerPath '{0}' is not valid, falling back to {1}.", session.configuration.debuggerPath, adapter));
|
||||
} else {
|
||||
void vscode.window.showErrorMessage(localize("debugger.path.not.available", "The specified LLDB-DAP debuggerPath '{0}' is not valid and no fallback was found.", session.configuration.debuggerPath));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!adapter) {
|
||||
adapter = await findLldbDap();
|
||||
}
|
||||
|
||||
if (!adapter) {
|
||||
void vscode.window.showErrorMessage(localize("lldbdap.debugger.not.available", "No LLDB-DAP debugger found. Please add it to the path, or set the debuggerPath property in the launch.json file."));
|
||||
return null;
|
||||
}
|
||||
|
||||
// Prepare the command to run the lldb-dap executable.
|
||||
const debuggerArgs = session.configuration.debuggerArgs || [];
|
||||
|
||||
// Future: add support for pipeTransport (so that the lldb-dap executable can be run on a remote machine or wsl).
|
||||
// Future: add support for --server mode (so that the lldb-dap executable can be run in server mode).
|
||||
|
||||
// Prepare the command to run the lldb-dap executable.
|
||||
return new vscode.DebugAdapterExecutable(adapter, [...debuggerArgs]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,13 @@ import { SshTargetsProvider, getActiveSshTarget, initializeSshTargets, selectSsh
|
||||
import { TargetLeafNode, setActiveSshTarget } from '../SSH/TargetsView/targetNodes';
|
||||
import { sshCommandToConfig } from '../SSH/sshCommandToConfig';
|
||||
import { getSshConfiguration, getSshConfigurationFiles, parseFailures, writeSshConfiguration } from '../SSH/sshHosts';
|
||||
import { pathAccessible } from '../common';
|
||||
import { pathAccessible } from '../common-remote-safe';
|
||||
import { instrument } from '../instrumentation';
|
||||
import { getSshChannel } from '../logger';
|
||||
import { AttachItemsProvider, AttachPicker, RemoteAttachPicker } from './attachToProcess';
|
||||
import { ConfigurationAssetProviderFactory, ConfigurationSnippetProvider, DebugConfigurationProvider, IConfigurationAssetProvider } from './configurationProvider';
|
||||
import { ConfigurationAssetProvider, ConfigurationSnippetProvider, DebugConfigurationProvider } from './configurationProvider';
|
||||
import { DebuggerType } from './configurations';
|
||||
import { CppdbgDebugAdapterDescriptorFactory, CppvsdbgDebugAdapterDescriptorFactory } from './debugAdapterDescriptorFactory';
|
||||
import { CppdbgDebugAdapterDescriptorFactory, CpplldbDebugAdapterDescriptorFactory, CppvsdbgDebugAdapterDescriptorFactory } from './debugAdapterDescriptorFactory';
|
||||
import { NativeAttachItemsProviderFactory } from './nativeAttach';
|
||||
|
||||
// The extension deactivate method is asynchronous, so we handle the disposables ourselves instead of using extensionContext.subscriptions.
|
||||
@@ -40,18 +40,20 @@ export async function initialize(context: vscode.ExtensionContext): Promise<void
|
||||
disposables.push(vscode.commands.registerCommand('extension.pickRemoteNativeProcess', (any) => remoteAttacher.ShowAttachEntries(any)));
|
||||
|
||||
// Activate ConfigurationProvider
|
||||
const assetProvider: IConfigurationAssetProvider = ConfigurationAssetProviderFactory.getConfigurationProvider();
|
||||
const assetProvider = ConfigurationAssetProvider.getConfigurationAssetProvider();
|
||||
|
||||
// Register DebugConfigurationProviders for "Run and Debug" in Debug Panel.
|
||||
// On Windows platforms, the cppvsdbg debugger will also be registered for initial configurations.
|
||||
let cppVsDebugProvider: DebugConfigurationProvider | null = null;
|
||||
if (os.platform() === 'win32') {
|
||||
cppVsDebugProvider = new DebugConfigurationProvider(assetProvider, DebuggerType.cppvsdbg);
|
||||
const cppVsDebugProvider = new DebugConfigurationProvider(assetProvider, DebuggerType.cppvsdbg);
|
||||
disposables.push(vscode.debug.registerDebugConfigurationProvider(DebuggerType.cppvsdbg, instrument(cppVsDebugProvider), vscode.DebugConfigurationProviderTriggerKind.Dynamic));
|
||||
}
|
||||
const cppDebugProvider: DebugConfigurationProvider = new DebugConfigurationProvider(assetProvider, DebuggerType.cppdbg);
|
||||
disposables.push(vscode.debug.registerDebugConfigurationProvider(DebuggerType.cppdbg, instrument(cppDebugProvider), vscode.DebugConfigurationProviderTriggerKind.Dynamic));
|
||||
|
||||
const cpplldbDebugProvider: DebugConfigurationProvider = new DebugConfigurationProvider(assetProvider, DebuggerType.cpplldb);
|
||||
disposables.push(vscode.debug.registerDebugConfigurationProvider(DebuggerType.cpplldb, instrument(cpplldbDebugProvider), vscode.DebugConfigurationProviderTriggerKind.Dynamic));
|
||||
|
||||
// Register DebugConfigurationProviders for "Run and Debug" play button.
|
||||
const debugProvider: DebugConfigurationProvider = new DebugConfigurationProvider(assetProvider, DebuggerType.all);
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
@@ -81,6 +83,7 @@ export async function initialize(context: vscode.ExtensionContext): Promise<void
|
||||
// Register Debug Adapters
|
||||
disposables.push(vscode.debug.registerDebugAdapterDescriptorFactory(DebuggerType.cppvsdbg, new CppvsdbgDebugAdapterDescriptorFactory(context)));
|
||||
disposables.push(vscode.debug.registerDebugAdapterDescriptorFactory(DebuggerType.cppdbg, new CppdbgDebugAdapterDescriptorFactory(context)));
|
||||
disposables.push(vscode.debug.registerDebugAdapterDescriptorFactory(DebuggerType.cpplldb, new CpplldbDebugAdapterDescriptorFactory(context)));
|
||||
|
||||
// SSH Targets View
|
||||
await initializeSshTargets();
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import { isMainThread, parentPort } from 'node:worker_threads';
|
||||
|
||||
import { appendLineAtLevel, executableName, findInPath, initialize, isExecutable, localize, searchFolders, spawnChildProcess } from '../common-remote-safe';
|
||||
import { isLinux, isMacOS, isWindows } from '../constants';
|
||||
import { TroubleshootingLldbDap } from '../links';
|
||||
import { ManualPromise } from '../Utility/Async/manualPromise';
|
||||
import { RemoteConnection, startRemoting } from '../Utility/Remoting/snare';
|
||||
|
||||
let remote: RemoteConnection | undefined;
|
||||
|
||||
/**
|
||||
* Base interface for debug configurations.
|
||||
* Represents a configuration used by the VS Code debugger.
|
||||
*/
|
||||
export interface DebugConfiguration {
|
||||
/**
|
||||
* The type of the debug session.
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* The name of the debug session.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The request type of the debug session.
|
||||
*/
|
||||
request: string;
|
||||
|
||||
/**
|
||||
* Additional debug type specific properties.
|
||||
*/
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* C++ specific debug configuration interface.
|
||||
* Extends the base debug configuration with C++ specific properties.
|
||||
*/
|
||||
export interface CppDebugConfiguration extends DebugConfiguration {
|
||||
/** Detailed description of the configuration. */
|
||||
detail?: string;
|
||||
|
||||
/** Status of the associated build task. */
|
||||
taskStatus?: any;
|
||||
|
||||
/**
|
||||
* Indicates whether this is the default debug configuration.
|
||||
* The debug configuration is considered as default if the prelaunch task is set as default.
|
||||
*/
|
||||
isDefault?: boolean;
|
||||
|
||||
/** Source of the configuration (e.g., auto-generated, user-defined). */
|
||||
configSource?: any;
|
||||
|
||||
/** Debugger-specific event information. */
|
||||
debuggerEvent?: any;
|
||||
|
||||
/** Type of debugging to perform. */
|
||||
debugType?: any;
|
||||
|
||||
/** Indicates whether this configuration represents an existing debug session. */
|
||||
existing?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum representing the supported debugger types in the C++ extension.
|
||||
*/
|
||||
export enum DebuggerType {
|
||||
/** Microsoft Visual Studio Debugger */
|
||||
cppvsdbg = "cppvsdbg",
|
||||
|
||||
/** GDB Debugger */
|
||||
cppdbg = "cppdbg",
|
||||
|
||||
/** LLDB Debugger with Debug Adapter Protocol support */
|
||||
cpplldb = "cpplldb",
|
||||
|
||||
/** Represents all debugger types */
|
||||
all = "all"
|
||||
}
|
||||
|
||||
/** Cached path to the lldb-dap executable once found. */
|
||||
let lldbDapPath: string | undefined;
|
||||
|
||||
/** Cached path to the xcrun tool on macOS. */
|
||||
let xcRunPath: string | undefined;
|
||||
|
||||
/**
|
||||
* On macOS, calls xcrun to find a given xtools binary (usually they don't expose the binary to the PATH).
|
||||
*
|
||||
* @param filename The name of the binary to find (e.g., lldb-vscode or lldb-dap).
|
||||
* @returns The path to the binary or undefined if it was not found.
|
||||
*/
|
||||
export async function xcRun(filename: string) {
|
||||
if (isMacOS) {
|
||||
try {
|
||||
xcRunPath ??= await findInPath('xcrun');
|
||||
if (xcRunPath) {
|
||||
const xcRun = await spawnChildProcess(xcRunPath, ['--find', filename]);
|
||||
|
||||
if (xcRun.succeeded) {
|
||||
const path = xcRun.output.trim();
|
||||
if (await isValidLldbDap(path)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore failure to run xcrun, or no results found.
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** Promise representing an ongoing search for the lldb-dap executable. */
|
||||
let searching: ManualPromise<string | undefined> | undefined;
|
||||
|
||||
/**
|
||||
* List of candidate file names and patterns to search for when looking for the lldb-dap executable.
|
||||
* Includes both string literals and regular expressions to match version-specific binaries.
|
||||
*/
|
||||
const candidates = [
|
||||
executableName('lldb-dap'),
|
||||
/^lldb-dap-\d+$|^lldb-dap-\d+\.exe$/,
|
||||
executableName('lldb-vscode'),
|
||||
/^lldb-vscode-\d+$|^lldb-vscode-\d+\.exe$/
|
||||
];
|
||||
|
||||
/**
|
||||
* The search implementation to find the viable lldb-dap executable.
|
||||
*
|
||||
* We absolutely prefer an lldb-dap binary that is in the path.
|
||||
* Failing that, we'll:
|
||||
* - Try xcrun on macOS to see if xtools can give it to us
|
||||
* - Try searching the secure well-known locations for a binary
|
||||
*
|
||||
* And if we can't find the actual lldb-dap binary, we'll try the same thing
|
||||
* for 'lldb-vscode' (the old name for the DAP binary)
|
||||
* and then 'lldb-dap-##' and 'lldb-vscode-##'. In practice, one might find a
|
||||
* binary with the LLVM major version number in it.
|
||||
*
|
||||
* @returns The path to the lldb-dap executable or undefined if it was not found.
|
||||
*/
|
||||
async function searchForLldbDap() {
|
||||
if (lldbDapPath) {
|
||||
return lldbDapPath;
|
||||
}
|
||||
const start = Date.now();
|
||||
// PATH binaries take priority.
|
||||
for (const candidate of candidates) {
|
||||
// First, search the environment PATH for the binary.
|
||||
lldbDapPath = await findInPath(candidate, isValidLldbDap);
|
||||
if (lldbDapPath) {
|
||||
appendLineAtLevel(6, `Discovered lldb-dap binary at '${lldbDapPath}' ${Date.now() - start} msec`);
|
||||
return lldbDapPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Well-known locations next.
|
||||
for (const candidate of candidates) {
|
||||
if (isMacOS) {
|
||||
// If that fails, use xcrun to find the path.
|
||||
if (typeof candidate === 'string') {
|
||||
lldbDapPath = await xcRun(candidate);
|
||||
if (lldbDapPath) {
|
||||
appendLineAtLevel(6, `Discovered lldb-dap binary for macOS at '${lldbDapPath}' ${Date.now() - start} msec`);
|
||||
return lldbDapPath;
|
||||
}
|
||||
}
|
||||
|
||||
// If we got this far, it's not in the PATH or via xcrun - but worry not,
|
||||
// we'll do a check of well known secured locations where it might be installed.
|
||||
lldbDapPath = await searchFolders(['/Applications', '/opt/homebrew'], candidate, isValidLldbDap, 8);
|
||||
if (lldbDapPath) {
|
||||
appendLineAtLevel(6, `Discovered lldb-dap binary for macOS at '${lldbDapPath}' ${Date.now() - start} msec`);
|
||||
return lldbDapPath;
|
||||
}
|
||||
}
|
||||
|
||||
if (isWindows) {
|
||||
// If we got this far, it's not in the PATH - but worry not,
|
||||
// we'll do a check of well known secured locations where it might be installed.
|
||||
lldbDapPath = await searchFolders(['c:/Program Files/LLVM', 'c:/Program Files/', 'C:/program files (x86)/'], candidate, isValidLldbDap);
|
||||
if (lldbDapPath) {
|
||||
appendLineAtLevel(6, `Discovered lldb-dap binary for Windows at '${lldbDapPath}' ${Date.now() - start} msec`);
|
||||
return lldbDapPath;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (isLinux) {
|
||||
// If we got this far, it's not in the PATH - but worry not,
|
||||
// we'll do a check of well known secured locations where it might be installed.
|
||||
lldbDapPath = await searchFolders(['/usr', '/opt'], candidate, isValidLldbDap, 8);
|
||||
if (lldbDapPath) {
|
||||
appendLineAtLevel(6, `Discovered lldb-dap binary for Linux at '${lldbDapPath}' ${Date.now() - start} msec`);
|
||||
return lldbDapPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
appendLineAtLevel(1, localize('lldb-dap.notfound', "Unable to find a working '{0}' adapter ({1} msec). See: {2}", 'lldb-dap', Date.now() - start, TroubleshootingLldbDap));
|
||||
return lldbDapPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls searchForLldbDap, but only runs one search at a time. Subsequent calls return the current search.
|
||||
*
|
||||
* @returns The path to the lldb-dap executable or undefined if it was not found.
|
||||
*/
|
||||
export async function findLldbDapImpl() {
|
||||
// If we already have a path, return it.
|
||||
if (lldbDapPath) {
|
||||
return lldbDapPath;
|
||||
}
|
||||
if (searching) {
|
||||
return searching;
|
||||
}
|
||||
|
||||
// Only run one search at a time, so if we are already searching, return that.
|
||||
searching = new ManualPromise<string | undefined>();
|
||||
searchForLldbDap().then((result: string | undefined): void => {
|
||||
if (searching) {
|
||||
searching.resolve(result);
|
||||
searching = undefined;
|
||||
}
|
||||
}).catch(error => {
|
||||
if (searching) {
|
||||
searching.reject(error);
|
||||
searching = undefined;
|
||||
}
|
||||
});
|
||||
return searching;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given path is a valid lldb-dap executable (actually runs it to check).
|
||||
*
|
||||
* @param lldbDap The path to the lldb-dap executable.
|
||||
* @returns True if the path is valid, false otherwise.
|
||||
*/
|
||||
export async function isValidLldbDap(lldbDap: string | undefined) {
|
||||
if (lldbDap) {
|
||||
if (await isExecutable(lldbDap)) {
|
||||
const proc = await spawnChildProcess(lldbDap, ['--help'], undefined, true);
|
||||
if (proc.succeeded && proc.output.includes('USAGE')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
appendLineAtLevel(6, localize('lldb-dap.not.valid', "The lldb-dap binary at '{0}' does not appear to be functional. See: {1}", lldbDap, TroubleshootingLldbDap));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isMainThread) {
|
||||
// If this is loaded in a worker thread, we'll set up the remoting interface.
|
||||
try {
|
||||
/** This is the SNARE remote call interface dispatcher that the worker thread supports. */
|
||||
remote = parentPort ? startRemoting(parentPort, {
|
||||
// These are the functions that this worker exposes to the parent thread.
|
||||
findLldbDapImpl
|
||||
}) : undefined; // If we're not in a worker thread - then we don't really have a remote interface.
|
||||
initialize(remote);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
appendLineAtLevel(6, `Error in worker thread: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import { resolve } from 'node:path';
|
||||
import { isMainThread } from 'node:worker_threads';
|
||||
import { appendLine, appendLineAtLevel, localize, log, note } from '../common-remote-safe';
|
||||
import { isWindows } from '../constants';
|
||||
import { RemoteConnection, startRemoting, startWorker } from '../Utility/Remoting/snare';
|
||||
import { CppDebugConfiguration, DebuggerType } from './configurations';
|
||||
import { findLldbDapImpl } from './lldb-dap-worker';
|
||||
export { isValidLldbDap } from './lldb-dap-worker';
|
||||
|
||||
/** Translates the cpplldb configuration to the lldb-dap configuration.
|
||||
* Note: this modifies the existing configuration object in place.
|
||||
*
|
||||
* @param config The cpplldb configuration to translate.
|
||||
* @returns The translated lldb-dap configuration.
|
||||
*/
|
||||
export function translateToLldbDap(config: CppDebugConfiguration) {
|
||||
// Adapt the cpplldb config to the lldb-dap config.
|
||||
if (config.type !== DebuggerType.cpplldb) {
|
||||
throw new Error(`Invalid config type ${config.type} for lldb-dap`);
|
||||
}
|
||||
|
||||
// Translate environment to env.
|
||||
if (config.environment) {
|
||||
// 'config.environment' is an array of { "name":<string>, "value":<string> } objects.
|
||||
// lldb-dap expects an object of { <name>:<value>, ... }.
|
||||
const env: { [key: string]: string } = {};
|
||||
for (const each of config.environment) {
|
||||
if (typeof each.name === 'string' && typeof each.value === 'string') {
|
||||
env[each.name] = each.value;
|
||||
}
|
||||
}
|
||||
delete config.environment;
|
||||
}
|
||||
|
||||
// Translate stopAtEntry to stopOnEntry.
|
||||
if (config.stopAtEntry !== undefined) {
|
||||
config.stopOnEntry = config.stopAtEntry;
|
||||
delete config.stopAtEntry;
|
||||
}
|
||||
|
||||
// Translate serverLaunchTimeout (msec) to timeout (seconds).
|
||||
if (config.serverLaunchTimeout !== undefined) {
|
||||
config.timeout = Math.floor(config.serverLaunchTimeout / 1000);
|
||||
delete config.serverLaunchTimeout;
|
||||
}
|
||||
|
||||
// Translate externalConsole to runInTerminal (!inverse).
|
||||
if (config.externalConsole !== undefined) {
|
||||
config.runInTerminal = !config.externalConsole;
|
||||
delete config.externalConsole;
|
||||
}
|
||||
|
||||
// TODO: translate sourceFileMap to sourcePath and sourceMap.
|
||||
|
||||
// Translate processId to pid.
|
||||
if (config.processId !== undefined) {
|
||||
config.pid = Number(config.processId);
|
||||
delete config.program;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/** Connection to the worker thread handling lldb-dap related operations. */
|
||||
let remote: RemoteConnection | undefined;
|
||||
|
||||
/** Cached path to the lldb-dap executable once found. */
|
||||
let lldbDapExecutable: string | undefined;
|
||||
|
||||
/**
|
||||
* Finds the path to the lldb-dap executable.
|
||||
*
|
||||
* This function uses a worker thread if available, otherwise calls the implementation directly.
|
||||
* The result is cached for subsequent calls.
|
||||
*
|
||||
* @returns A promise that resolves to the path of the lldb-dap executable, or undefined if not found.
|
||||
*/
|
||||
export async function findLldbDap() {
|
||||
return lldbDapExecutable ??= remote ? await remote.request('findLldbDapImpl') : findLldbDapImpl();
|
||||
}
|
||||
|
||||
// This code must only run in the main thread.
|
||||
if (isMainThread && !remote) {
|
||||
try {
|
||||
// Find the entry point for the worker thread.
|
||||
const file = resolve(__dirname.substring(0, __dirname.lastIndexOf('dist')), "dist", "src", "Debugger", 'lldb-dap-worker.js');
|
||||
// Create the worker and connection.
|
||||
remote = startRemoting(startWorker(file), {
|
||||
// These are the functions that the main thread exposes to the worker thread.
|
||||
log,
|
||||
note,
|
||||
localize,
|
||||
appendLine,
|
||||
appendLineAtLevel
|
||||
});
|
||||
|
||||
if (!isWindows) {
|
||||
// If we are not on Windows, we'll start it searching for the lldb-dap executable as early as possible.
|
||||
// Mainly, because the LLDB-DAP debugger isn't a common standalone debugger for Windows.
|
||||
void findLldbDap();
|
||||
}
|
||||
} catch (e) {
|
||||
// If we fail to start the worker, we can still use the implementation directly.
|
||||
appendLineAtLevel(6, "Failed to start the worker thread for LLDB-DAP remote calls. Falling back to direct calls.");
|
||||
remote = undefined;
|
||||
}
|
||||
}
|
||||
@@ -5,25 +5,44 @@
|
||||
|
||||
import * as child_process from 'child_process';
|
||||
import * as os from 'os';
|
||||
import { normalize } from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { findPowerShell } from '../common';
|
||||
import { isMacOS } from '../constants';
|
||||
import { AttachItem } from './attachQuickPick';
|
||||
import { AttachItemsProvider } from './attachToProcess';
|
||||
|
||||
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
|
||||
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
|
||||
|
||||
export class Process {
|
||||
constructor(public name: string, public pid?: string, public commandLine?: string) { }
|
||||
export class Process implements AttachItem {
|
||||
get label() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public toAttachItem(): AttachItem {
|
||||
return {
|
||||
label: this.name,
|
||||
description: this.pid,
|
||||
detail: this.commandLine,
|
||||
id: this.pid
|
||||
};
|
||||
get description() {
|
||||
// If the fullPath is known and different than the process name, put that in the description after the pid
|
||||
// to let the user can find the actual process they are looking for.
|
||||
return this.fullPath && this.fullPath !== this.name ? `${this.pid} [${this.fullPath}]` : this.pid;
|
||||
}
|
||||
|
||||
get detail() {
|
||||
return this.commandLine;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.pid;
|
||||
}
|
||||
|
||||
constructor(public name: string, public pid?: string, public commandLine?: string, public fullPath?: string) {
|
||||
if (this.fullPath) {
|
||||
// If we have a full path, clean it up.
|
||||
if (this.fullPath?.startsWith('"') && this.fullPath.endsWith('"')) {
|
||||
this.fullPath = this.fullPath.slice(1, -1);
|
||||
}
|
||||
this.fullPath = normalize(this.fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,8 +81,7 @@ abstract class NativeAttachItemsProvider implements AttachItemsProvider {
|
||||
}
|
||||
return aLower < bLower ? -1 : 1;
|
||||
});
|
||||
const attachItems: AttachItem[] = processEntries.map(p => p.toAttachItem());
|
||||
return attachItems;
|
||||
return processEntries;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,33 +111,26 @@ export class PsAttachItemsProvider extends NativeAttachItemsProvider {
|
||||
// QuickPick UI in VSCode.
|
||||
|
||||
protected async getInternalProcessEntries(token?: vscode.CancellationToken): Promise<Process[]> {
|
||||
let processCmd: string = '';
|
||||
switch (os.platform()) {
|
||||
case 'darwin':
|
||||
processCmd = PsProcessParser.psDarwinCommand;
|
||||
break;
|
||||
return PsProcessParser.ParseProcessFromPs(await spawnChildProcess(PsProcessParser.psDarwinCommand, token));
|
||||
case 'linux':
|
||||
processCmd = PsProcessParser.psLinuxCommand;
|
||||
break;
|
||||
return PsProcessParser.ParseProcessFromPs(await spawnChildProcess(PsProcessParser.psLinuxCommand, token));
|
||||
default:
|
||||
throw new Error(localize("os.not.supported", 'Operating system "{0}" not supported.', os.platform()));
|
||||
}
|
||||
const processes: string = await spawnChildProcess(processCmd, token);
|
||||
return PsProcessParser.ParseProcessFromPs(processes);
|
||||
}
|
||||
}
|
||||
|
||||
export class PsProcessParser {
|
||||
private static get secondColumnCharacters(): number { return 50; }
|
||||
private static get commColumnTitle(): string { return Array(PsProcessParser.secondColumnCharacters).join("a"); }
|
||||
// the BSD version of ps uses '-c' to have 'comm' only output the executable name and not
|
||||
// the full path. The Linux version of ps has 'comm' to only display the name of the executable
|
||||
// Use a large fixed width - the default on macOS is quite small.
|
||||
static fixedWidth = ''.padEnd(512, 'a');
|
||||
|
||||
// Note that comm on Linux systems is truncated to 16 characters:
|
||||
// https://bugzilla.redhat.com/show_bug.cgi?id=429565
|
||||
// Since 'args' contains the full path to the executable, even if truncated, searching will work as desired.
|
||||
public static get psLinuxCommand(): string { return `ps axww -o pid=,comm=${PsProcessParser.commColumnTitle},args=`; }
|
||||
public static get psDarwinCommand(): string { return `ps axww -o pid=,comm=${PsProcessParser.commColumnTitle},args= -c`; }
|
||||
public static get psToyboxCommand(): string { return `ps -A -o pid=,comm=${PsProcessParser.commColumnTitle},args=`; }
|
||||
public static get psLinuxCommand(): string { return `ps axww -o pid=,exe=${this.fixedWidth},args=${this.fixedWidth}`; }
|
||||
public static get psDarwinCommand(): string { return `ps axww -o pid=,comm=${this.fixedWidth},args=${this.fixedWidth}`; }
|
||||
public static get psToyboxCommand(): string { return `ps -A -o pid=,comm=${this.fixedWidth},args=${this.fixedWidth}`; }
|
||||
|
||||
// Only public for tests.
|
||||
public static ParseProcessFromPs(processes: string): Process[] {
|
||||
@@ -147,21 +158,40 @@ export class PsProcessParser {
|
||||
}
|
||||
|
||||
private static parseLineFromPs(line: string): Process | undefined {
|
||||
// Explanation of the regex:
|
||||
// - any leading whitespace
|
||||
// - PID
|
||||
// - whitespace
|
||||
// - executable name --> this is PsAttachItemsProvider.secondColumnCharacters - 1 because ps reserves one character
|
||||
// for the whitespace separator
|
||||
// - whitespace
|
||||
// - args (might be empty)
|
||||
const psEntry: RegExp = new RegExp(`^\\s*([0-9]+)\\s+(.{${PsProcessParser.secondColumnCharacters - 1}})\\s+(.*)$`);
|
||||
const psEntry = isMacOS ?
|
||||
// On macOS, we're using fixed-width columns, so we have to use a fixed-width regex.
|
||||
// <start>whitespace(NUMBERS)whitespace(FIXED-WIDTH)whitespace(EVERYTHING-ELSE)<end>
|
||||
new RegExp(`^\\s*([0-9]+)\\s+(.{${PsProcessParser.fixedWidth.length - 1}})\\s+(.*)$`) :
|
||||
|
||||
// On Linux, column widths cannot be guaranteed - but we do get escaped spaces in the command line.
|
||||
// <start>whitespace(NUMBERS)whitespace(NOTWHITESPACE)whitespace(EVERYTHING-ELSE)<end>
|
||||
/^\s*(\d+)\s+((?:\\\s|\S)+)\s+([\s\S]*)$/;
|
||||
|
||||
const matches: RegExpExecArray | null = psEntry.exec(line);
|
||||
if (matches && matches.length === 4) {
|
||||
const pid: string = matches[1].trim();
|
||||
const executable: string = matches[2].trim();
|
||||
const cmdline: string = matches[3].trim();
|
||||
return new Process(executable, pid, cmdline);
|
||||
let fullPath: string = matches[2].trim();
|
||||
const rawCommandLine = matches[3].trim();
|
||||
|
||||
// Trim the full path off the command line so that we optimize for seeing '<cmd> <args>'.
|
||||
const cmdline: string = rawCommandLine.replace(/^\s*[\/\\]*(?:(?:[^\\\/\s]|\\.)+[\/\\])*([^\\\/\s]+)(?=\s|$)/, "$1");
|
||||
|
||||
// If the fullPath == '-', let's grab the arg0 from the raw command line.
|
||||
if (fullPath === '-') {
|
||||
const args = /^((?:\\\s|\S)+)\s*([\s\S]*)$/.exec(rawCommandLine);
|
||||
if (args) {
|
||||
fullPath = args[1];
|
||||
}
|
||||
}
|
||||
|
||||
const executable: string = fullPath.replace(/^.*\//, '');
|
||||
|
||||
// Skip processes that are '<defunct>'.
|
||||
if (executable === '<defunct>' || cmdline.includes('<defunct>')) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new Process(executable, pid, cmdline, fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -337,7 +367,7 @@ export class CimAttachItemsProvider extends NativeAttachItemsProvider {
|
||||
|
||||
protected async getInternalProcessEntries(token?: vscode.CancellationToken): Promise<Process[]> {
|
||||
const pwshCommand: string = `${this.pwsh} -NoProfile -Command`;
|
||||
const cimCommand: string = 'Get-CimInstance Win32_Process | Select-Object Name,ProcessId,CommandLine | ConvertTo-JSON -Compress';
|
||||
const cimCommand: string = 'Get-CimInstance Win32_Process | Select-Object Name,ProcessId,CommandLine,Path | ConvertTo-JSON -Compress';
|
||||
const processes: string = await spawnChildProcess(`${pwshCommand} "${cimCommand}"`, token);
|
||||
return CimProcessParser.ParseProcessFromCim(processes);
|
||||
}
|
||||
@@ -347,6 +377,7 @@ type CimProcessInfo = {
|
||||
Name: string;
|
||||
ProcessId: number;
|
||||
CommandLine: string | null;
|
||||
Path: string | null;
|
||||
};
|
||||
|
||||
export class CimProcessParser {
|
||||
@@ -364,7 +395,7 @@ export class CimProcessParser {
|
||||
if (cmdline?.startsWith(this.ntObjectManagerPathPrefix)) {
|
||||
cmdline = cmdline.slice(this.ntObjectManagerPathPrefix.length);
|
||||
}
|
||||
return new Process(info.Name, `${info.ProcessId}`, cmdline);
|
||||
return new Process(info.Name, `${info.ProcessId}`, cmdline, info.Path || undefined);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { TargetPopulation } from 'vscode-tas-client';
|
||||
import * as which from 'which';
|
||||
import { logAndReturn } from '../Utility/Async/returns';
|
||||
import * as util from '../common';
|
||||
import { ProcessReturnType, spawnChildProcess } from '../common-remote-safe';
|
||||
import { modelSelector } from '../constants';
|
||||
import { instrument } from '../instrumentation';
|
||||
import { getCrashCallStacksChannel } from '../logger';
|
||||
@@ -1244,9 +1245,9 @@ async function handleCrashFileRead(crashDirectory: string, crashFile: string, cr
|
||||
let funcStr: string = line.substring(startPos2, offsetPos);
|
||||
let origFuncStr: string = "";
|
||||
if (filtPath && filtPath.length !== 0) {
|
||||
let ret: util.ProcessReturnType | undefined = await util.spawnChildProcess(filtPath, ["--no-strip-underscore", funcStr], undefined, true).catch(logAndReturn.undefined);
|
||||
let ret: ProcessReturnType | undefined = await spawnChildProcess(filtPath, ["--no-strip-underscore", funcStr], undefined, true).catch(logAndReturn.undefined);
|
||||
if (ret?.output === funcStr) {
|
||||
ret = await util.spawnChildProcess(filtPath, [funcStr], undefined, true).catch(logAndReturn.undefined);
|
||||
ret = await spawnChildProcess(filtPath, [funcStr], undefined, true).catch(logAndReturn.undefined);
|
||||
}
|
||||
if (ret !== undefined && ret.succeeded && !ret.output.startsWith("Could not open input file")) {
|
||||
origFuncStr = funcStr;
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
import { constants } from 'fs';
|
||||
import { TreeItem } from "vscode";
|
||||
import * as nls from 'vscode-nls';
|
||||
import { extensionContext, ISshConfigHostInfo, pathAccessible } from "../../common";
|
||||
import { extensionContext, ISshConfigHostInfo } from "../../common";
|
||||
import { pathAccessible } from '../../common-remote-safe';
|
||||
import { LabelLeafNode } from "./common";
|
||||
|
||||
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { getFullHostAddress, getFullHostAddressNoPort, ISshHostInfo, ISshLocalForwardInfo, ProcessReturnType } from '../common';
|
||||
import { getFullHostAddress, getFullHostAddressNoPort, ISshHostInfo, ISshLocalForwardInfo } from '../common';
|
||||
import { ProcessReturnType } from '../common-remote-safe';
|
||||
import { defaultSystemInteractor } from './commandInteractors';
|
||||
import { runSshTerminalCommandWithLogin } from './sshCommandRunner';
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { CppSettings } from '../LanguageServer/settings';
|
||||
import { ManualPromise } from '../Utility/Async/manualPromise';
|
||||
import { ISshHostInfo, ProcessReturnType, getNumericLoggingLevel, splitLines, stripEscapeSequences } from '../common';
|
||||
import { ISshHostInfo, getNumericLoggingLevel, splitLines, stripEscapeSequences } from '../common';
|
||||
import { ProcessReturnType } from '../common-remote-safe';
|
||||
import { isWindows } from '../constants';
|
||||
import { getSshChannel } from '../logger';
|
||||
import {
|
||||
|
||||
@@ -43,14 +43,14 @@ export class ManualPromise<T = void> implements Promise<T> {
|
||||
* A method to manually resolve the Promise.
|
||||
*/
|
||||
public resolve: (value?: T | PromiseLike<T> | undefined) => void = (v) => {
|
||||
void v; /* */
|
||||
void v;
|
||||
};
|
||||
|
||||
/**
|
||||
* A method to manually reject the Promise
|
||||
*/
|
||||
public reject: (e: any) => void = (e) => {
|
||||
void e; /* */
|
||||
void e;
|
||||
};
|
||||
|
||||
private state: 'pending' | 'resolved' | 'rejected' = 'pending';
|
||||
|
||||
@@ -0,0 +1,541 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
import { fail, ok } from 'node:assert';
|
||||
import { isPromise } from 'node:util/types';
|
||||
import { MessagePort, SHARE_ENV, Worker, isMainThread } from 'node:worker_threads';
|
||||
|
||||
import { ManualPromise } from '../Async/manualPromise';
|
||||
import { finalize } from '../System/finalize';
|
||||
import { is } from '../System/guards';
|
||||
|
||||
/*
|
||||
* SNARE: Simple Nodejs Asynchronous Remoting Engine
|
||||
*
|
||||
* SNARE is an extremely lightweight remoting engine that
|
||||
* allows you to call functions in a nodejs worker thread
|
||||
*
|
||||
* It supports:
|
||||
* - notifications (no response)
|
||||
* - requests (async calls with a response)
|
||||
* - error handling
|
||||
* - simple byref object management
|
||||
*
|
||||
* As long as the values you pass and return are supported by the Structured Clone Algorithm,
|
||||
* (see https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)
|
||||
* you can pass them by value to the remote thread.
|
||||
*
|
||||
* If you need to pass an object by reference, you can manually craft a remote object extending
|
||||
* the MarshalByReference.
|
||||
*/
|
||||
|
||||
// Enable typescript disposable types/interfaces.
|
||||
/// <reference lib="esnext.disposable" />
|
||||
|
||||
// Polyfill `Symbol.dispose`
|
||||
(Symbol as any).dispose ??= Symbol("Symbol.dispose");
|
||||
(Symbol as any).asyncDispose ??= Symbol("Symbol.asyncDispose");
|
||||
|
||||
const results = new Map<number, ManualPromise<any>>();
|
||||
let next = 0;
|
||||
|
||||
/**
|
||||
* Internal interface representing a message payload exchanged between threads.
|
||||
* Contains operation names, sequence numbers, and associated data.
|
||||
*/
|
||||
interface Payload {
|
||||
/** The operation to perform (function name or built-in command). */
|
||||
operation: string;
|
||||
|
||||
/** Sequence number for matching requests with responses (0 for notifications). */
|
||||
sequence: number;
|
||||
|
||||
/** Function parameters when sending a request. */
|
||||
parameters?: any[];
|
||||
|
||||
/** Return value when sending a response. */
|
||||
result?: any;
|
||||
|
||||
/** Error information when an operation fails. */
|
||||
error?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a connection to a remote thread that can be used to invoke operations.
|
||||
* Provides methods for making requests, sending notifications, and managing remote objects.
|
||||
*/
|
||||
export interface RemoteConnection {
|
||||
/** The underlying Worker or MessagePort used for communication. */
|
||||
connection: Worker | MessagePort;
|
||||
|
||||
/** Terminates the connection and cleans up resources. */
|
||||
terminate(): void;
|
||||
|
||||
/**
|
||||
* Makes a request to the remote thread and awaits a response.
|
||||
*
|
||||
* @param operation The operation name to invoke.
|
||||
* @param parameters Parameters to pass to the remote operation.
|
||||
* @returns A promise that resolves with the operation result.
|
||||
*/
|
||||
request(operation: string, ...parameters: any[]): Promise<any>;
|
||||
|
||||
/**
|
||||
* Sends a notification to the remote thread (fire and forget).
|
||||
*
|
||||
* @param operation The operation name to invoke.
|
||||
* @param parameters Parameters to pass to the remote operation.
|
||||
*/
|
||||
notify(operation: string, ...parameters: any[]): void;
|
||||
|
||||
/**
|
||||
* Creates a proxy for a remote object.
|
||||
*
|
||||
* @param ctor Constructor for the proxy type.
|
||||
* @param instance Promise of a remote object instance ID.
|
||||
* @returns Promise of a proxy to the remote object.
|
||||
*/
|
||||
marshall<T extends MarshalByReference>(ctor: new (remote: RemoteConnection, instance: number) => T, instance: Promise<number>): Promise<T | undefined>;
|
||||
|
||||
/**
|
||||
* Creates a proxy for a remote object.
|
||||
*
|
||||
* @param ctor Constructor for the proxy type.
|
||||
* @param instance A remote object instance ID.
|
||||
* @returns A proxy to the remote object.
|
||||
*/
|
||||
marshall<T extends MarshalByReference>(ctor: new (remote: RemoteConnection, instance: number) => T, instance: number): T | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type representing a set of functions that can be called remotely.
|
||||
* The endpoint serves as the API that can be invoked from the other thread.
|
||||
*/
|
||||
export type Endpoint = Record<string, (...args: any[]) => any>;
|
||||
|
||||
/** SNARE: Simple Nodejs Asynchronous Remoting Engine */
|
||||
export function startRemoting(connection: Worker | MessagePort, endpoint: Endpoint): RemoteConnection { // Main thread needs to wait for the connection to be ready before doing anything.
|
||||
// The worker threads don't have an 'online' event (the port is already connected).
|
||||
let ready = isMainThread ? new ManualPromise<undefined>() : undefined;
|
||||
connection.on('online', () => ready?.resolve());
|
||||
|
||||
function postResult(sequence: number, retVal: any) {
|
||||
if (is.promise(retVal)) {
|
||||
retVal.then(
|
||||
(result) => connection.postMessage({ operation: "#result", sequence: sequence, result: sanitize(result) }),
|
||||
(error) => connection.postMessage({ operation: "#error", sequence: sequence, error: sanitize(error) })); // Call failed, threw an error.
|
||||
return;
|
||||
}
|
||||
|
||||
connection.postMessage({ operation: "#result", sequence: sequence, result: sanitize(retVal) });
|
||||
}
|
||||
|
||||
// Handle messages from the remote thread.
|
||||
connection.on('message', ({ operation, sequence, parameters, result, error }: Payload) => {
|
||||
// Unwrap incoming parameters that are byref or function references.
|
||||
parameters = parameters?.map(unwrapValue) || []; // Switch based on the operation type.
|
||||
switch (operation) {
|
||||
// If the message is an error, reject the promise.
|
||||
case '#error':
|
||||
results.get(sequence)?.reject(error);
|
||||
return results.delete(sequence);
|
||||
|
||||
// If the message is a result, resolve the promise.
|
||||
case '#result':
|
||||
results.get(sequence)?.resolve(unwrapValue(result));
|
||||
return results.delete(sequence);
|
||||
|
||||
// Unref is a built-in function to release a byref object.
|
||||
case '#unref':
|
||||
unref(parameters[0]);
|
||||
return;
|
||||
|
||||
// Callback is a built-in function to call a callback function.
|
||||
case '#callback':
|
||||
try {
|
||||
postResult(sequence, getByRef(parameters[0])(...parameters.slice(1)));
|
||||
} catch (err) {
|
||||
connection.postMessage({ operation: "#error", sequence, error: sanitize(err) });
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Otherwise, we're going to call a remote function.
|
||||
|
||||
// Get the sequence number for the result (0 indicates that it's a notification).
|
||||
try {
|
||||
// Call the endpoint.
|
||||
if (!is.function(endpoint[operation])) {
|
||||
throw new Error(`Attempting to call unknown remote method on endpoint: ${operation}`);
|
||||
}
|
||||
|
||||
const retVal = endpoint[operation](...parameters);
|
||||
if (sequence) {
|
||||
// Is it a request? If so, post the result.
|
||||
postResult(sequence, retVal);
|
||||
}
|
||||
} catch (err) {
|
||||
if (sequence) {
|
||||
connection.postMessage({ operation: "#error", sequence, error: sanitize(err) }); // Call failed, threw an error.
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const remote = {
|
||||
connection,
|
||||
request: async (operation: string, ...parameters: any[]) => {
|
||||
let result: ManualPromise<any> | undefined;
|
||||
let payload: Payload | undefined;
|
||||
try {
|
||||
if (ready) {
|
||||
await ready;
|
||||
}
|
||||
result = new ManualPromise<any>();
|
||||
payload = { operation, parameters: sanitizeParameters(parameters), sequence: ++next };
|
||||
results.set(payload.sequence, result);
|
||||
|
||||
connection.postMessage(payload);
|
||||
} catch (err) {
|
||||
// If we can't post the message, then we need to reject the promise.
|
||||
if (payload) {
|
||||
results.delete(payload.sequence);
|
||||
}
|
||||
if (result) {
|
||||
result.reject(err);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
notify: (operation: string, ...parameters: any[]) => {
|
||||
try {
|
||||
if (ready) {
|
||||
void ready.then(() => {
|
||||
ready = undefined;
|
||||
connection.postMessage({ operation, parameters: sanitizeParameters(parameters), sequence: 0 });
|
||||
});
|
||||
} else {
|
||||
connection.postMessage({ operation, parameters: sanitizeParameters(parameters), sequence: 0 });
|
||||
}
|
||||
} catch (err: any) {
|
||||
// Ignore the errors on notifications, as they are not expected to return a result.
|
||||
}
|
||||
},
|
||||
marshall: <T extends MarshalByReference>(ctor: new (remote: RemoteConnection, instance: number) => T, instance: number | Promise<number>) => instance ? is.promise(instance) ? instance.then(i => new ctor(remote, i)) : new ctor(remote, instance) : undefined,
|
||||
terminate: () => isMainThread ? finalize(connection) : undefined
|
||||
};
|
||||
|
||||
function unwrapValue(value: any) {
|
||||
return isReference(value) ?
|
||||
value.kind === 'function' ? (...args: any[]) => remote.request('#callback', value.identity, ...args) : // If it is a referenced function, we'll create a wrapper function to call it.
|
||||
getByRef(value.identity) : // If it is a referenced object, we'll get the object.
|
||||
value;
|
||||
}
|
||||
connection.on('close', () => { // Disable the remote connection interface so that it can't be used anymore.
|
||||
const r = remote as any;
|
||||
r.request = r.marshal = async () => { };
|
||||
r.notify = r.terminate = () => { };
|
||||
r.connection = undefined;
|
||||
|
||||
// The connection is closed, so reject all pending requests.
|
||||
for (const result of results.values()) {
|
||||
try {
|
||||
result.reject('Connection closed');
|
||||
} catch {
|
||||
// Ignore errors when rejecting.
|
||||
}
|
||||
}
|
||||
// Clear the results map.
|
||||
results.clear();
|
||||
|
||||
// Clear out any byref objects.
|
||||
identityIndex.length = 0;
|
||||
instanceIndex.clear();
|
||||
|
||||
});
|
||||
|
||||
return remote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the current code is running on the main thread.
|
||||
*
|
||||
* @throws If not running on the main thread.
|
||||
*/
|
||||
export function ensureIsMainThread() {
|
||||
ok(isMainThread, "Remoting: Failed to start host thread responder - not on main thread");
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new worker thread.
|
||||
*
|
||||
* @param workerPath Path to the worker script file.
|
||||
* @returns A new Worker instance with stderr, stdout, and environment variables shared.
|
||||
*/
|
||||
export function startWorker(workerPath: string) {
|
||||
return new Worker(workerPath, { stderr: true, stdout: true, env: SHARE_ENV });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value that is safe to go over the connection.
|
||||
*
|
||||
* This includes:
|
||||
* - functions (which are marshalled as references)
|
||||
* - MarshalByReference objects (which are marshalled as references)
|
||||
*
|
||||
* Otherwise, we do a deep filtered clone of the object.
|
||||
*
|
||||
* @param value The value to sanitize
|
||||
* @returns A sanitized version of the value that can be safely sent over the connection
|
||||
*/
|
||||
function sanitize(value: any) {
|
||||
return is.function(value) || value instanceof MarshalByReference ? ref(value) : filteredClone(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes an array of parameters for remoting.
|
||||
*
|
||||
* @param parameters Array of parameter values to sanitize.
|
||||
* @returns Array of sanitized parameters.
|
||||
*/
|
||||
function sanitizeParameters(parameters: any[]) {
|
||||
return parameters.map(sanitize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if a value is a reference object.
|
||||
* References are objects with identity and kind properties.
|
||||
*
|
||||
* @param p The value to check.
|
||||
* @returns True if the value is a reference object.
|
||||
*/
|
||||
function isReference(p: any): p is Reference {
|
||||
return is.object(p) && 'identity' in p && 'kind' in p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface representing a reference to an object or function in another thread.
|
||||
* References are used to represent values that cannot be cloned.
|
||||
*/
|
||||
interface Reference {
|
||||
/** Unique identifier for the referenced object. */
|
||||
identity: number;
|
||||
|
||||
/** The kind of reference - either an object or function. */
|
||||
kind: 'object' | 'function';
|
||||
}
|
||||
|
||||
/** Array of objects indexed by their identity number. */
|
||||
const identityIndex = new Array<any>();
|
||||
|
||||
/** Maps objects to their [identity, reference count] pairs. */
|
||||
const instanceIndex = new Map<any, [number, number]>();
|
||||
|
||||
/**
|
||||
* Gets an object by its reference identity.
|
||||
*
|
||||
* @param identity The unique identifier of the referenced object.
|
||||
* @returns The referenced object.
|
||||
* @throws If the identity doesn't correspond to a valid referenced object.
|
||||
*/
|
||||
export function getByRef<T = any>(identity: number): T {
|
||||
return identityIndex[identity] ?? fail(`Invalid ${identity} for ByRef object`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a reference to an object or function that can be sent to another thread.
|
||||
*
|
||||
* @param instance A Promise resolving to the object to reference.
|
||||
* @returns A Promise resolving to a reference object, or undefined if the value can't be referenced.
|
||||
*/
|
||||
export function ref(instance: Promise<any>): Promise<Reference | undefined>;
|
||||
|
||||
/**
|
||||
* Creates a reference to an object or function that can be sent to another thread.
|
||||
*
|
||||
* @param instance The object to reference.
|
||||
* @returns A reference object, or undefined if the value can't be referenced.
|
||||
*/
|
||||
export function ref(instance: any): Reference | undefined;
|
||||
|
||||
/**
|
||||
* Implementation of the ref function handling both synchronous and asynchronous cases.
|
||||
*
|
||||
* @param instance The object or Promise to reference.
|
||||
* @returns A reference object, a Promise to a reference object, or undefined.
|
||||
*/
|
||||
export function ref(instance: any | Promise<any>): Reference | undefined | Promise<Reference | undefined> {
|
||||
if (is.promise(instance)) {
|
||||
return instance.then(ref);
|
||||
}
|
||||
|
||||
const kind = typeof instance;
|
||||
switch (kind) {
|
||||
case 'object':
|
||||
case 'function':
|
||||
// Lookup the instance in the index.
|
||||
const [identity, refcount] = instanceIndex.get(instance) ?? [++next, 0];
|
||||
|
||||
// If refcount is zero, then we need to add it to the index.
|
||||
if (!refcount) {
|
||||
identityIndex[identity] = instance;
|
||||
}
|
||||
|
||||
// Increment the refcount.
|
||||
instanceIndex.set(instance, [identity, refcount + 1]);
|
||||
|
||||
// Return the identity.
|
||||
return { identity, kind };
|
||||
}
|
||||
// Otherwise, we can't ref it.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrements the reference count for a referenced object.
|
||||
* If the reference count reaches zero, the object is removed from the index and finalized.
|
||||
*
|
||||
* @param identity The identity of the referenced object to unreference.
|
||||
*/
|
||||
export function unref(identity: number) {
|
||||
// Lookup the instance.
|
||||
const instance = getByRef(identity);
|
||||
if (instance) {
|
||||
// Decrement the refcount.
|
||||
const [identity, refcount] = instanceIndex.get(instance) ?? [0, 0];
|
||||
if (refcount > 1) {
|
||||
// Reduce the refcount by one.
|
||||
return instanceIndex.set(instance, [identity, refcount - 1]);
|
||||
}
|
||||
// It's the last reference, so remove it from the index.
|
||||
identityIndex[identity] = undefined;
|
||||
instanceIndex.delete(instance);
|
||||
finalize(instance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A base class for objects that are passed by reference to a remote thread.
|
||||
*
|
||||
* All MarshalByReference wrappers are references to an object that lives in the remote thread.
|
||||
* It is important to call .dispose() when you are done with it, as this enables the remote
|
||||
* thread to release the object and free up resources.
|
||||
*/
|
||||
export class MarshalByReference implements Disposable {
|
||||
constructor(protected remote: RemoteConnection, protected instance: number) {
|
||||
}
|
||||
|
||||
/**
|
||||
* This disposes the ByRef object and notifies the remote thread to reduce the refcount,
|
||||
* which would dispose the remote object if it was the last reference.
|
||||
*/
|
||||
[Symbol.dispose]() {
|
||||
void this.remote.notify('#unref', this.instance);
|
||||
this.instance = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a filtered structured clone of an object that can be safely transmitted between threads.
|
||||
*
|
||||
* This recursively drops items that are:
|
||||
* - undefined
|
||||
* - functions
|
||||
* - symbols
|
||||
* - promises
|
||||
* - asyncIterators
|
||||
*
|
||||
* It should correctly handle:
|
||||
* - circular references by keeping a map of references that have already been cloned
|
||||
* - arrays, sets, maps, and iterables (iterables are converted to arrays)
|
||||
* - dates, regex, errors, and buffers by returning them directly
|
||||
* - vscode.Uri by converting it to a string
|
||||
* - all other objects by recursively cloning them
|
||||
*
|
||||
* @param data The data to clone.
|
||||
* @param references Map of already cloned objects (for handling circular references).
|
||||
* @param options Additional options for controlling the cloning process.
|
||||
* @returns A filtered clone of the input data suitable for transmission.
|
||||
*/
|
||||
export function filteredClone(data: any, references = new Map<any, any>(), options?: { breakCircular?: boolean }): any {
|
||||
// Fast checks for null and undefined.
|
||||
if (data === undefined || data === null) {
|
||||
return undefined;
|
||||
}
|
||||
if (references.has(data)) {
|
||||
return references.get(data);
|
||||
}
|
||||
|
||||
switch (typeof data) {
|
||||
case 'symbol':
|
||||
case 'function':
|
||||
return undefined;
|
||||
|
||||
case 'object':
|
||||
if (options?.breakCircular) {
|
||||
references.set(data, undefined);
|
||||
}
|
||||
if (isPromise(data) || typeof data.then === 'function' || typeof data[Symbol.asyncIterator] === 'function') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (data instanceof Set) {
|
||||
const result = new Set();
|
||||
references.set(data, result);
|
||||
for (const item of data) {
|
||||
result.add(filteredClone(item, references, options));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (data instanceof Map) {
|
||||
const result = new Map();
|
||||
references.set(data, result);
|
||||
for (const [key, value] of data) {
|
||||
const k = filteredClone(key, references, options);
|
||||
if (k !== undefined) {
|
||||
result.set(k, filteredClone(value, references, options));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (data[Symbol.iterator]) {
|
||||
const result: any[] = [];
|
||||
references.set(data, result);
|
||||
for (const item of data) {
|
||||
result.push(filteredClone(item, references, options));
|
||||
} return result;
|
||||
}
|
||||
|
||||
// These are ok to return the value directly.
|
||||
if (data instanceof Date || data instanceof RegExp || data instanceof Error || data instanceof Buffer) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// Special case for vscode.Uri - we'll just return the string.
|
||||
if ('path' in data && 'scheme' in data && 'authority' in data && 'fragment' in data) {
|
||||
return data.toString();
|
||||
}
|
||||
|
||||
// Everything else is an object that we'll recursively clone.
|
||||
const result: any = {};
|
||||
if (!options?.breakCircular) {
|
||||
references.set(data, result);
|
||||
}
|
||||
|
||||
for (const key in data) {
|
||||
if (key.startsWith('_')) {
|
||||
continue;
|
||||
}
|
||||
const v = filteredClone(data[key], references, options);
|
||||
if (v !== undefined) {
|
||||
// Drop undefined values entirely.
|
||||
result[key] = v;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,463 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
/** @file The functions here are safe to call from a worker thread or the main thread. */
|
||||
|
||||
import { ChildProcess, spawn } from 'node:child_process';
|
||||
import { access, constants, readdir, stat } from 'node:fs/promises';
|
||||
import { basename, delimiter, dirname, isAbsolute, normalize, resolve } from 'node:path';
|
||||
import { isMainThread } from 'node:worker_threads';
|
||||
|
||||
import { isWindows } from './constants';
|
||||
import { ManualPromise } from './Utility/Async/manualPromise';
|
||||
import { RemoteConnection } from './Utility/Remoting/snare';
|
||||
import { is } from './Utility/System/guards';
|
||||
|
||||
/**
|
||||
* @file These are vscode-extension functions that have been wrapped
|
||||
* so that they can be safely called from the main thread or worker threads.
|
||||
*
|
||||
* If this is imported in a module running in a worker thread, the worker
|
||||
* thread must call `initialize(remote)` with a valid `RemoteConnection`
|
||||
* instance before using these functions, otherwise they will be a no-op.
|
||||
*/
|
||||
|
||||
let remoteConnection: RemoteConnection | undefined;
|
||||
|
||||
// In the main thread, grab the logger and localize functions directly.
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const logFn = isMainThread ? require('./logger').log : () => { };
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const noteFn = isMainThread ? require('./logger').note : () => { };
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const localizeFn = isMainThread ? require('./localization').localize : (info: { key: string; comment: string[] } | string, message: string, ...args: (string | number | boolean | undefined | null)[]) => message.replace(/\{(\d+)\}/g, (_, index) => String(args[Number(index)] ?? 'undefined'));
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const getOutputChannelLoggerFn = isMainThread ? require('./logger').getOutputChannelLogger : () => undefined;
|
||||
|
||||
/**
|
||||
* Used when this is imported in a module running in a worker thread.
|
||||
*
|
||||
* This ensures that the remote connection is set up before any of the
|
||||
* functions are called. If this is not called, the functions will be no-ops.
|
||||
*
|
||||
* @param remote The remote connection instance to use.
|
||||
*/
|
||||
export function initialize(remote: RemoteConnection | undefined): void {
|
||||
if (!isMainThread) {
|
||||
remoteConnection = remote;
|
||||
}
|
||||
}
|
||||
|
||||
/** Appends the message to the log file.
|
||||
*
|
||||
* @param message The message to log. If this is a Promise, it will be resolved and logged.
|
||||
*/
|
||||
export function log(message: string | Promise<string>) {
|
||||
if (is.promise(message)) {
|
||||
void message.then(log);
|
||||
return;
|
||||
}
|
||||
return isMainThread ? logFn(message) : remoteConnection?.notify('log', message);
|
||||
}
|
||||
|
||||
/** Sets a transient message in the vscode status bar.
|
||||
*
|
||||
* The mesage will be displayed for a short time and then cleared.
|
||||
* @param message The message to display.
|
||||
*/
|
||||
export function note(message: string | Promise<string>) {
|
||||
if (is.promise(message)) {
|
||||
void message.then(note);
|
||||
return;
|
||||
}
|
||||
return isMainThread ? noteFn(message) : remoteConnection?.notify('note', message);
|
||||
}
|
||||
|
||||
/** Localizes a message.
|
||||
*
|
||||
* This function is used to localize a message in the vscode extension.
|
||||
*
|
||||
* @param info The localization information.
|
||||
* @param message The message to localize.
|
||||
* @param args The arguments for the message.
|
||||
*/
|
||||
export const localize: AsyncLocalizeFunc = (infoOrKey, message, ...args) => {
|
||||
if (isMainThread) {
|
||||
if (typeof infoOrKey === 'string') {
|
||||
return localizeFn(infoOrKey, message, ...args);
|
||||
} else {
|
||||
return localizeFn(infoOrKey as LocalizeInfo, message, ...args);
|
||||
}
|
||||
}
|
||||
return remoteConnection?.request('localize', infoOrKey, message, ...args) ??
|
||||
message.replace(/\{(\d+)\}/g, (_, index) => String(args[Number(index)] ?? 'undefined'));
|
||||
};
|
||||
|
||||
export function appendLineAtLevel(level: number, message: string | Promise<string>): void {
|
||||
if (is.promise(message)) {
|
||||
void message.then((msg) => appendLineAtLevel(level, msg));
|
||||
return;
|
||||
}
|
||||
if (isMainThread) {
|
||||
getOutputChannelLoggerFn()?.appendLineAtLevel(level, message);
|
||||
} else {
|
||||
remoteConnection?.notify('appendLineAtLevel', level, message);
|
||||
}
|
||||
}
|
||||
|
||||
export function appendLine(message: string | Promise<string>): void {
|
||||
if (is.promise(message)) {
|
||||
void message.then(appendLine);
|
||||
return;
|
||||
}
|
||||
if (isMainThread) {
|
||||
getOutputChannelLoggerFn()?.appendLine(message);
|
||||
} else {
|
||||
remoteConnection?.notify('appendLine', message);
|
||||
}
|
||||
}
|
||||
|
||||
/** When on Windows, ensures that the given executable name ends in an '.exe'.
|
||||
* @param executableName The name of the executable to check.
|
||||
* @returns The executable name with .exe appended if on Windows and the name does not already end in an '.exe'.
|
||||
*/
|
||||
export function executableName(executableName: string) {
|
||||
return isWindows && !/\.exe$/i.test(executableName) ? `${executableName}.exe` : executableName;
|
||||
}
|
||||
/**
|
||||
* Represents a type which can release resources, such
|
||||
* as event listening or a timer.
|
||||
*
|
||||
* (This is similar to the `Disposable` interface in VS Code.)
|
||||
*/
|
||||
export interface Disposable {
|
||||
/**
|
||||
* Dispose this object.
|
||||
*/
|
||||
dispose(): any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a typed event.
|
||||
*
|
||||
* A function that represents an event to which you subscribe by calling it with
|
||||
* a listener function as argument.
|
||||
*
|
||||
* (This is similar to the `Event` interface in VS Code.)
|
||||
*
|
||||
* @example
|
||||
* item.onDidChange(function(event) { console.log("Event happened: " + event); });
|
||||
*
|
||||
* @param listener The listener function will be called when the event happens.
|
||||
* @param thisArgs The `this`-argument which will be used when calling the event listener.
|
||||
* @param disposables An array to which a {@link Disposable} will be added.
|
||||
* @returns A disposable which unsubscribes the event listener.
|
||||
*/
|
||||
export type Event<T> = (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => Disposable;
|
||||
|
||||
/** The key for a localize call.
|
||||
* This interface provides structure for localization information.
|
||||
*/
|
||||
export interface LocalizeInfo {
|
||||
/** The localization key identifier. */
|
||||
key: string;
|
||||
/** Comments providing context for translators. */
|
||||
comment: string[];
|
||||
}
|
||||
|
||||
/** The type for the localize function for string localization, but can work asynchronously (can be called via remoting). */
|
||||
export type AsyncLocalizeFunc = (
|
||||
infoOrKey: LocalizeInfo | string,
|
||||
message: string,
|
||||
...args: (string | number | boolean | undefined | null)[]
|
||||
) => string | Promise<string>;
|
||||
|
||||
/**
|
||||
* A cancellation token is passed to an asynchronous or long running
|
||||
* operation to request cancellation, like cancelling a request
|
||||
* for completion items because the user continued to type.
|
||||
*
|
||||
* (This is similar to the `CancellationToken` interface in VS Code.)
|
||||
*
|
||||
* To get an instance of a `CancellationToken` use a
|
||||
* {@link CancellationTokenSource}.
|
||||
*/
|
||||
export interface CancellationToken {
|
||||
|
||||
/**
|
||||
* Is `true` when the token has been cancelled, `false` otherwise.
|
||||
*/
|
||||
isCancellationRequested: boolean;
|
||||
|
||||
/**
|
||||
* An {@link Event} which fires upon cancellation.
|
||||
*/
|
||||
onCancellationRequested: Event<any>;
|
||||
}
|
||||
|
||||
/** Searches the PATH for a given executable program, using an optional predicate to control if the candidate is accepted.
|
||||
* @param filename The name of the executable to search for (string or a regular expression).
|
||||
* @param predicate A function that takes a binary file path and returns a boolean indicating whether to include it in the results.
|
||||
* @returns A promise that resolves to the full path of the executable if found, or undefined if not found.
|
||||
*/
|
||||
export async function findInPath(filename: string | RegExp, predicate?: (binary: string) => Promise<boolean>): Promise<string | undefined> {
|
||||
return searchFolders(process.env["PATH"]?.split(delimiter) || [], filename, predicate, 0);
|
||||
}
|
||||
|
||||
/** Checks if a path is accessible with the specified permissions.
|
||||
*
|
||||
* @param filePath The path to check for accessibility.
|
||||
* @param permission fs file access constants: https://nodejs.org/api/fs.html#file-access-constants
|
||||
* @returns A promise that resolves to true if the path is accessible with the specified permissions, false otherwise.
|
||||
*/
|
||||
export async function pathAccessible(filePath: string, permission: number = constants.F_OK): Promise<boolean> {
|
||||
return filePath ? access(filePath, permission).then(() => true).catch(() => false) : false;
|
||||
}
|
||||
|
||||
/** Checks if a file is executable by the current user.
|
||||
* @param file The path to the file to check.
|
||||
* @returns A promise that resolves to true if the file is executable, false otherwise.
|
||||
*/
|
||||
export function isExecutable(file: string): Promise<boolean> {
|
||||
return pathAccessible(file, constants.X_OK);
|
||||
}
|
||||
|
||||
/** Represents the result of spawning a child process.
|
||||
* Contains information about the process exit code, standard output and error output.
|
||||
*/
|
||||
export interface ProcessReturnType {
|
||||
succeeded: boolean;
|
||||
exitCode?: number | NodeJS.Signals;
|
||||
output: string;
|
||||
outputError: string;
|
||||
}
|
||||
|
||||
/** Escapes special characters in a string to make it safe for use in a regular expression.
|
||||
* @param str The string to escape.
|
||||
* @returns A new string with all special regex characters escaped with backslashes.
|
||||
*/
|
||||
export function escapeStringForRegex(str: string): string {
|
||||
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1');
|
||||
}
|
||||
|
||||
/** Spawns a child process and returns its output.
|
||||
* @param program The path to the program to execute.
|
||||
* @param args The command line arguments to pass to the program.
|
||||
* @param localizer The function to use for localization.
|
||||
* @param continueOn Optional string pattern that, when found in stdout, will cause the process to be considered complete before it exits.
|
||||
* @param skipLogging Optional flag to skip logging process output.
|
||||
* @param cancellationToken Optional token that can be used to cancel the process.
|
||||
* @returns A promise that resolves to an object containing process output and exit information.
|
||||
*/
|
||||
export async function spawnChildProcess(program: string, args: string[] = [], continueOn?: string, skipLogging?: boolean, cancellationToken?: CancellationToken): Promise<ProcessReturnType> {
|
||||
// Do not use CppSettings to avoid circular require()
|
||||
if (skipLogging === undefined || !skipLogging) {
|
||||
appendLineAtLevel(5, `$ ${program} ${args.join(' ')}`);
|
||||
}
|
||||
const programOutput: ProcessOutput = await spawnChildProcessImpl(program, args, continueOn, skipLogging, cancellationToken);
|
||||
const exitCode: number | NodeJS.Signals | undefined = programOutput.exitCode;
|
||||
if (programOutput.exitCode) {
|
||||
return { succeeded: false, exitCode, outputError: programOutput.stderr, output: programOutput.stderr || programOutput.stdout || await localize('process.exited', 'Process exited with code {0}', exitCode) };
|
||||
} else {
|
||||
let stdout: string;
|
||||
if (programOutput.stdout.length) {
|
||||
// Type system doesn't work very well here, so we need call toString
|
||||
stdout = programOutput.stdout;
|
||||
} else {
|
||||
stdout = await localize('process.succeeded', 'Process executed successfully.');
|
||||
}
|
||||
return { succeeded: true, exitCode, outputError: programOutput.stderr, output: stdout };
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents the output of a child process spawned by the extension.
|
||||
* Contains the raw output streams and exit code information.
|
||||
*/
|
||||
interface ProcessOutput {
|
||||
/** The exit code of the process, or the signal that terminated it. */
|
||||
exitCode?: number | NodeJS.Signals;
|
||||
/** The standard output of the process as a string. */
|
||||
stdout: string;
|
||||
/** The standard error output of the process as a string. */
|
||||
stderr: string;
|
||||
}
|
||||
|
||||
/** Implementation of the process spawning functionality.
|
||||
* This function handles the actual creation and management of the child process.
|
||||
*
|
||||
* @param program The path to the program to execute.
|
||||
* @param args The command line arguments to pass to the program.
|
||||
* @param continueOn Optional string pattern that, when found in stdout, will cause the process to be considered complete before it exits.
|
||||
* @param skipLogging Optional flag to skip logging process output.
|
||||
* @param cancellationToken Optional token that can be used to cancel the process.
|
||||
* @returns A promise that resolves to the process output, including stdout, stderr and exit code.
|
||||
*/
|
||||
async function spawnChildProcessImpl(program: string, args: string[], continueOn?: string, skipLogging?: boolean, cancellationToken?: CancellationToken): Promise<ProcessOutput> {
|
||||
const result = new ManualPromise<ProcessOutput>();
|
||||
|
||||
let proc: ChildProcess;
|
||||
if (await isExecutable(program)) {
|
||||
proc = spawn(`.${isWindows ? '\\' : '/'}${basename(program)}`, args, { shell: true, cwd: dirname(program) });
|
||||
} else {
|
||||
proc = spawn(program, args, { shell: true });
|
||||
}
|
||||
|
||||
const cancellationTokenListener: Disposable | undefined = cancellationToken?.onCancellationRequested(() => {
|
||||
|
||||
appendLine(localize('killing.process', 'Killing process {0}', program));
|
||||
|
||||
proc.kill();
|
||||
});
|
||||
|
||||
/** Cleans up resources associated with the process.
|
||||
* Removes all event listeners and disposes the cancellation token listener.
|
||||
*/
|
||||
const clean = () => {
|
||||
proc.removeAllListeners();
|
||||
if (cancellationTokenListener) {
|
||||
cancellationTokenListener.dispose();
|
||||
}
|
||||
};
|
||||
|
||||
let stdout: string = '';
|
||||
let stderr: string = '';
|
||||
if (proc.stdout) {
|
||||
proc.stdout.on('data', data => {
|
||||
const str: string = data.toString();
|
||||
if (skipLogging === undefined || !skipLogging) {
|
||||
appendLineAtLevel(1, str);
|
||||
}
|
||||
stdout += str;
|
||||
if (continueOn) {
|
||||
const continueOnReg: string = escapeStringForRegex(continueOn);
|
||||
if (stdout.search(continueOnReg)) {
|
||||
result.resolve({ stdout: stdout.trim(), stderr: stderr.trim() });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (proc.stderr) {
|
||||
proc.stderr.on('data', data => stderr += data.toString());
|
||||
}
|
||||
proc.on('close', (code, signal) => {
|
||||
clean();
|
||||
result.resolve({ exitCode: code || signal || undefined, stdout: stdout.trim(), stderr: stderr.trim() });
|
||||
});
|
||||
proc.on('error', error => {
|
||||
clean();
|
||||
result.reject(error);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Returns true if the path is a folder.
|
||||
*
|
||||
* @param path The path to check.
|
||||
* @returns A promise that resolves to true if the path is a folder, false otherwise.
|
||||
*/
|
||||
export async function isFolder(path: string) {
|
||||
try {
|
||||
return (await stat(path)).isDirectory();
|
||||
} catch {
|
||||
// Ignore errors, if we can't access the path, it's not a folder.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function searchFolders(folders: string[], filename: string | RegExp, predicate?: (binary: string) => Promise<boolean>, maxDepth = 4, options: { result?: string; visited?: Set<string>; topDepth?: number } = {}): Promise<string | undefined> {
|
||||
options.topDepth ??= maxDepth;
|
||||
options.visited ??= new Set<string>();
|
||||
const nameMatches = is.string(filename) ? (item: string) => item === filename : (item: string) => filename.test(item);
|
||||
|
||||
for (let folder of folders) {
|
||||
// If the result was reached from anything going on asynchronously, we can stop searching.
|
||||
if (options.result) {
|
||||
return options.result;
|
||||
}
|
||||
|
||||
// Ensure that the folder is normalized and absolute.
|
||||
folder = normalize(folder);
|
||||
if (!isAbsolute(folder)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we've been here before, skip this folder.
|
||||
if (options.visited.has(folder) || !await isFolder(folder)) {
|
||||
continue;
|
||||
}
|
||||
// Mark the folder as visited to avoid infinite loops.
|
||||
options.visited.add(folder);
|
||||
|
||||
// We can do a quick check in the folder when the filename is a string.
|
||||
if (is.string(filename)) {
|
||||
const fullPath = resolve(folder, filename);
|
||||
if (predicate ? await predicate(fullPath) : true) {
|
||||
return options.result ??= fullPath;
|
||||
}
|
||||
|
||||
// And, if maxDepth is 0, we don't need to bother searching subfolders at all.
|
||||
if (maxDepth === 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const subfolders = new Array<string>();
|
||||
|
||||
// Parallelize the search for files in the folder.
|
||||
await Promise.all((await readdir(folder).catch(() => [])).map(async (item) => {
|
||||
|
||||
// If we already found a match, stop searching.
|
||||
if (options.result) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are not going to search subfolders, we can skip this item if it doesn't match the filename.
|
||||
if (maxDepth === 0 && !nameMatches(item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fullPath: string = resolve(folder, item);
|
||||
|
||||
const stats = await stat(fullPath).catch(() => undefined);
|
||||
if (!stats) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
// If anything else found anything, we can stop searching.
|
||||
case !!options.result:
|
||||
return;
|
||||
|
||||
// If the path is a symlink, skip it entirely.
|
||||
case stats.isSymbolicLink():
|
||||
// If it's a symlink, we can't follow it, so skip it.
|
||||
log(`Skipping symlink: ${fullPath}`);
|
||||
return;
|
||||
|
||||
// If it is a file, check for a match.
|
||||
case stats.isFile():
|
||||
if (nameMatches(item) && (predicate ? await predicate(fullPath) : true)) {
|
||||
return options.result ??= fullPath;
|
||||
}
|
||||
break;
|
||||
|
||||
// If it's a folder, and we're not at the max depth yet, add it to the list of subfolders to search.
|
||||
case maxDepth && stats.isDirectory():
|
||||
subfolders.push(fullPath);
|
||||
break;
|
||||
}
|
||||
}));
|
||||
|
||||
if (subfolders.length) {
|
||||
await searchFolders(subfolders, filename, predicate, maxDepth - 1, options);
|
||||
}
|
||||
} catch {
|
||||
// Skip folders that can't be accessed.
|
||||
}
|
||||
}
|
||||
|
||||
return options.result;
|
||||
}
|
||||
+1
-100
@@ -15,7 +15,7 @@ import { DocumentFilter, Range } from 'vscode-languageclient';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { TargetPopulation } from 'vscode-tas-client';
|
||||
import * as which from "which";
|
||||
import { ManualPromise } from './Utility/Async/manualPromise';
|
||||
import { isExecutable } from './common-remote-safe';
|
||||
import { isWindows } from './constants';
|
||||
import { getOutputChannelLogger, showOutputChannel } from './logger';
|
||||
import { PlatformInformation } from './platform';
|
||||
@@ -749,105 +749,6 @@ export function execChildProcess(process: string, workingDirectory?: string, cha
|
||||
});
|
||||
}
|
||||
|
||||
export interface ProcessReturnType {
|
||||
succeeded: boolean;
|
||||
exitCode?: number | NodeJS.Signals;
|
||||
output: string;
|
||||
outputError: string;
|
||||
}
|
||||
|
||||
export async function spawnChildProcess(program: string, args: string[] = [], continueOn?: string, skipLogging?: boolean, cancellationToken?: vscode.CancellationToken): Promise<ProcessReturnType> {
|
||||
// Do not use CppSettings to avoid circular require()
|
||||
if (skipLogging === undefined || !skipLogging) {
|
||||
getOutputChannelLogger().appendLineAtLevel(5, `$ ${program} ${args.join(' ')}`);
|
||||
}
|
||||
const programOutput: ProcessOutput = await spawnChildProcessImpl(program, args, continueOn, skipLogging, cancellationToken);
|
||||
const exitCode: number | NodeJS.Signals | undefined = programOutput.exitCode;
|
||||
if (programOutput.exitCode) {
|
||||
return { succeeded: false, exitCode, outputError: programOutput.stderr, output: programOutput.stderr || programOutput.stdout || localize('process.exited', 'Process exited with code {0}', exitCode) };
|
||||
} else {
|
||||
let stdout: string;
|
||||
if (programOutput.stdout.length) {
|
||||
// Type system doesn't work very well here, so we need call toString
|
||||
stdout = programOutput.stdout;
|
||||
} else {
|
||||
stdout = localize('process.succeeded', 'Process executed successfully.');
|
||||
}
|
||||
return { succeeded: true, exitCode, outputError: programOutput.stderr, output: stdout };
|
||||
}
|
||||
}
|
||||
|
||||
interface ProcessOutput {
|
||||
exitCode?: number | NodeJS.Signals;
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
}
|
||||
|
||||
async function spawnChildProcessImpl(program: string, args: string[], continueOn?: string, skipLogging?: boolean, cancellationToken?: vscode.CancellationToken): Promise<ProcessOutput> {
|
||||
const result = new ManualPromise<ProcessOutput>();
|
||||
|
||||
let proc: child_process.ChildProcess;
|
||||
if (await isExecutable(program)) {
|
||||
proc = child_process.spawn(`.${isWindows ? '\\' : '/'}${path.basename(program)}`, args, { shell: true, cwd: path.dirname(program) });
|
||||
} else {
|
||||
proc = child_process.spawn(program, args, { shell: true });
|
||||
}
|
||||
|
||||
const cancellationTokenListener: vscode.Disposable | undefined = cancellationToken?.onCancellationRequested(() => {
|
||||
getOutputChannelLogger().appendLine(localize('killing.process', 'Killing process {0}', program));
|
||||
proc.kill();
|
||||
});
|
||||
|
||||
const clean = () => {
|
||||
proc.removeAllListeners();
|
||||
if (cancellationTokenListener) {
|
||||
cancellationTokenListener.dispose();
|
||||
}
|
||||
};
|
||||
|
||||
let stdout: string = '';
|
||||
let stderr: string = '';
|
||||
if (proc.stdout) {
|
||||
proc.stdout.on('data', data => {
|
||||
const str: string = data.toString();
|
||||
if (skipLogging === undefined || !skipLogging) {
|
||||
getOutputChannelLogger().appendAtLevel(1, str);
|
||||
}
|
||||
stdout += str;
|
||||
if (continueOn) {
|
||||
const continueOnReg: string = escapeStringForRegex(continueOn);
|
||||
if (stdout.search(continueOnReg)) {
|
||||
result.resolve({ stdout: stdout.trim(), stderr: stderr.trim() });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (proc.stderr) {
|
||||
proc.stderr.on('data', data => stderr += data.toString());
|
||||
}
|
||||
proc.on('close', (code, signal) => {
|
||||
clean();
|
||||
result.resolve({ exitCode: code || signal || undefined, stdout: stdout.trim(), stderr: stderr.trim() });
|
||||
});
|
||||
proc.on('error', error => {
|
||||
clean();
|
||||
result.reject(error);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param permission fs file access constants: https://nodejs.org/api/fs.html#file-access-constants
|
||||
*/
|
||||
export function pathAccessible(filePath: string, permission: number = fs.constants.F_OK): Promise<boolean> {
|
||||
if (!filePath) { return Promise.resolve(false); }
|
||||
return new Promise(resolve => fs.access(filePath, permission, err => resolve(!err)));
|
||||
}
|
||||
|
||||
export function isExecutable(file: string): Promise<boolean> {
|
||||
return pathAccessible(file, fs.constants.X_OK);
|
||||
}
|
||||
|
||||
export async function allowExecution(file: string): Promise<void> {
|
||||
if (process.platform !== 'win32') {
|
||||
const exists: boolean = await checkFileExists(file);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
// Centralized https links that are used by the extension.
|
||||
|
||||
/** Link to documentation on how to troubleshoot LLDB-DAP. */
|
||||
export const TroubleshootingLldbDap = "https://aka.ms/vscode-cpptools/TroubleshootingLldbDap";
|
||||
@@ -0,0 +1,8 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
|
||||
export const localize: nls.LocalizeFunc = nls.loadMessageBundle();
|
||||
@@ -193,3 +193,8 @@ export function disposeOutputChannels(): void {
|
||||
warningChannel.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets a transient message in the vscode status bar. */
|
||||
export function note(message: string) {
|
||||
vscode.window.setStatusBarMessage(message, 5000);
|
||||
}
|
||||
|
||||
@@ -8,10 +8,11 @@
|
||||
import * as assert from 'assert';
|
||||
import { suite } from 'mocha';
|
||||
import * as vscode from 'vscode';
|
||||
import { DebuggerType } from '../../../../src/Debugger/configurations';
|
||||
|
||||
suite(`Debug Integration Test: `, function(): void {
|
||||
suite(`Debug Integration Test: `, function (): void {
|
||||
|
||||
suiteSetup(async function(): Promise<void> {
|
||||
suiteSetup(async function (): Promise<void> {
|
||||
const extension: vscode.Extension<any> = vscode.extensions.getExtension("ms-vscode.cpptools") || assert.fail("Extension not found");
|
||||
if (!extension.isActive) {
|
||||
await extension.activate();
|
||||
@@ -27,7 +28,7 @@ suite(`Debug Integration Test: `, function(): void {
|
||||
});
|
||||
|
||||
try {
|
||||
assert.equal(vscode.debug.activeDebugSession?.type, "cppdbg");
|
||||
assert.equal(vscode.debug.activeDebugSession?.type, DebuggerType.cppdbg);
|
||||
} catch (e) {
|
||||
assert.fail("Debugger failed to launch. Did the extension activate correctly?");
|
||||
}
|
||||
|
||||
@@ -12,40 +12,40 @@ import { LinuxDistribution } from '../../../../src/linuxDistribution';
|
||||
suite("LinuxDistro Tests", () => {
|
||||
test("Parse valid os-release file", () => {
|
||||
const dataUbuntu1404: string = 'NAME="Ubuntu"' + os.EOL +
|
||||
'VERSION="14.04.4 LTS, Trusty Tahr"' + os.EOL +
|
||||
'ID=ubuntu' + os.EOL +
|
||||
'ID_LIKE=debian' + os.EOL +
|
||||
'PRETTY_NAME="Ubuntu 14.04.4 LTS"' + os.EOL +
|
||||
'VERSION_ID="14.04"' + os.EOL +
|
||||
'HOME_URL="http://www.ubuntu.com/"' + os.EOL +
|
||||
'SUPPORT_URL="http://help.ubuntu.com/"' + os.EOL +
|
||||
'BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"';
|
||||
'VERSION="14.04.4 LTS, Trusty Tahr"' + os.EOL +
|
||||
'ID=ubuntu' + os.EOL +
|
||||
'ID_LIKE=debian' + os.EOL +
|
||||
'PRETTY_NAME="Ubuntu 14.04.4 LTS"' + os.EOL +
|
||||
'VERSION_ID="14.04"' + os.EOL +
|
||||
'HOME_URL="http://www.ubuntu.com/"' + os.EOL +
|
||||
'SUPPORT_URL="http://help.ubuntu.com/"' + os.EOL +
|
||||
'BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"';
|
||||
|
||||
const dataUbuntu1510: string = 'NAME="Ubuntu"' + os.EOL +
|
||||
'VERSION="15.10 (Wily Werewolf)"' + os.EOL +
|
||||
'ID=ubuntu' + os.EOL +
|
||||
'ID_LIKE=debian' + os.EOL +
|
||||
'PRETTY_NAME="Ubuntu 15.10"' + os.EOL +
|
||||
'VERSION_ID="15.10"' + os.EOL +
|
||||
'HOME_URL="http://www.ubuntu.com/"' + os.EOL +
|
||||
'SUPPORT_URL="http://help.ubuntu.com/"' + os.EOL +
|
||||
'BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"';
|
||||
'VERSION="15.10 (Wily Werewolf)"' + os.EOL +
|
||||
'ID=ubuntu' + os.EOL +
|
||||
'ID_LIKE=debian' + os.EOL +
|
||||
'PRETTY_NAME="Ubuntu 15.10"' + os.EOL +
|
||||
'VERSION_ID="15.10"' + os.EOL +
|
||||
'HOME_URL="http://www.ubuntu.com/"' + os.EOL +
|
||||
'SUPPORT_URL="http://help.ubuntu.com/"' + os.EOL +
|
||||
'BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"';
|
||||
|
||||
const dataCentos73: string = 'NAME="CentOS Linux"' + os.EOL +
|
||||
'VERSION="7 (Core)"' + os.EOL +
|
||||
'ID="centos"' + os.EOL +
|
||||
'ID_LIKE="rhel fedora"' + os.EOL +
|
||||
'VERSION_ID="7"' + os.EOL +
|
||||
'PRETTY_NAME="CentOS Linux 7 (Core)"' + os.EOL +
|
||||
'ANSI_COLOR="0;31"' + os.EOL +
|
||||
'CPE_NAME="cpe:/o:centos:centos:7"' + os.EOL +
|
||||
'HOME_URL="https://www.centos.org/"' + os.EOL +
|
||||
'BUG_REPORT_URL="https://bugs.centos.org/"' + os.EOL +
|
||||
os.EOL +
|
||||
'CENTOS_MANTISBT_PROJECT="CentOS-7"' + os.EOL +
|
||||
'CENTOS_MANTISBT_PROJECT_VERSION="7"' + os.EOL +
|
||||
'REDHAT_SUPPORT_PRODUCT="centos"' + os.EOL +
|
||||
'REDHAT_SUPPORT_PRODUCT_VERSION="7"';
|
||||
'VERSION="7 (Core)"' + os.EOL +
|
||||
'ID="centos"' + os.EOL +
|
||||
'ID_LIKE="rhel fedora"' + os.EOL +
|
||||
'VERSION_ID="7"' + os.EOL +
|
||||
'PRETTY_NAME="CentOS Linux 7 (Core)"' + os.EOL +
|
||||
'ANSI_COLOR="0;31"' + os.EOL +
|
||||
'CPE_NAME="cpe:/o:centos:centos:7"' + os.EOL +
|
||||
'HOME_URL="https://www.centos.org/"' + os.EOL +
|
||||
'BUG_REPORT_URL="https://bugs.centos.org/"' + os.EOL +
|
||||
os.EOL +
|
||||
'CENTOS_MANTISBT_PROJECT="CentOS-7"' + os.EOL +
|
||||
'CENTOS_MANTISBT_PROJECT_VERSION="7"' + os.EOL +
|
||||
'REDHAT_SUPPORT_PRODUCT="centos"' + os.EOL +
|
||||
'REDHAT_SUPPORT_PRODUCT_VERSION="7"';
|
||||
|
||||
const ubuntu1404: LinuxDistribution = LinuxDistribution.getDistroInformation(dataUbuntu1404);
|
||||
const ubuntu1510: LinuxDistribution = LinuxDistribution.getDistroInformation(dataUbuntu1510);
|
||||
@@ -74,18 +74,18 @@ suite("Pick Process Tests", () => {
|
||||
test("Parse valid wmic output", () => {
|
||||
// output from the command used in WmicAttachItemsProvider
|
||||
const wmicOutput: string = 'CommandLine=' + os.EOL +
|
||||
'Name=System Idle Process' + os.EOL +
|
||||
'ProcessId=0' + os.EOL +
|
||||
'' + os.EOL +
|
||||
'' + os.EOL +
|
||||
'CommandLine="C:\\Program Files (x86)\\Microsoft Office\\root\\Office16\\ONENOTE.EXE"' + os.EOL +
|
||||
'Name=ONENOTE.EXE' + os.EOL +
|
||||
'ProcessId=6540' + os.EOL +
|
||||
'' + os.EOL +
|
||||
'' + os.EOL +
|
||||
`CommandLine=\\??\\C:\\windows\\system32\\conhost.exe 0x4` + os.EOL +
|
||||
'Name=conhost.exe' + os.EOL +
|
||||
'ProcessId=59148' + os.EOL;
|
||||
'Name=System Idle Process' + os.EOL +
|
||||
'ProcessId=0' + os.EOL +
|
||||
'' + os.EOL +
|
||||
'' + os.EOL +
|
||||
'CommandLine="C:\\Program Files (x86)\\Microsoft Office\\root\\Office16\\ONENOTE.EXE"' + os.EOL +
|
||||
'Name=ONENOTE.EXE' + os.EOL +
|
||||
'ProcessId=6540' + os.EOL +
|
||||
'' + os.EOL +
|
||||
'' + os.EOL +
|
||||
`CommandLine=\\??\\C:\\windows\\system32\\conhost.exe 0x4` + os.EOL +
|
||||
'Name=conhost.exe' + os.EOL +
|
||||
'ProcessId=59148' + os.EOL;
|
||||
|
||||
const parsedOutput: Process[] = WmicProcessParser.ParseProcessFromWmic(wmicOutput);
|
||||
|
||||
@@ -108,9 +108,9 @@ suite("Pick Process Tests", () => {
|
||||
|
||||
test("Parse valid ps output", () => {
|
||||
// output from the command used in PsAttachItemsProvider
|
||||
const psOutput: string = ' aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + os.EOL +
|
||||
'15470 ScopedBookmarkAgent ScopedBookmarkAgent' + os.EOL +
|
||||
'15220 mdworker mdworker -s mdworker -c MDSImporterWorker -m com.apple.mdworker.shared' + os.EOL;
|
||||
const psOutput: string = ' ' + PsProcessParser.fixedWidth + os.EOL +
|
||||
'15470 ' + "ScopedBookmarkAgent".padEnd(512, ' ') + 'ScopedBookmarkAgent' + os.EOL +
|
||||
'15220 ' + "mdworker".padEnd(512, ' ') + 'mdworker -s mdworker -c MDSImporterWorker -m com.apple.mdworker.shared' + os.EOL;
|
||||
|
||||
const parsedOutput: Process[] = PsProcessParser.ParseProcessFromPs(psOutput);
|
||||
|
||||
|
||||
@@ -47,11 +47,9 @@
|
||||
"default": {}
|
||||
},
|
||||
"quoteArgs": {
|
||||
"exceptions": {
|
||||
"type": "boolean",
|
||||
"description": "%c_cpp.debuggers.pipeTransport.quoteArgs.description%",
|
||||
"default": true
|
||||
}
|
||||
"type": "boolean",
|
||||
"description": "%c_cpp.debuggers.pipeTransport.quoteArgs.description%",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -604,7 +602,7 @@
|
||||
"program": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.program.description%",
|
||||
"default": "${workspaceRoot}/a.out"
|
||||
"default": "${workspaceFolder}/a.out"
|
||||
},
|
||||
"args": {
|
||||
"type": "array",
|
||||
@@ -839,7 +837,7 @@
|
||||
"program": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.program.description%",
|
||||
"default": "${workspaceRoot}/a.out"
|
||||
"default": "${workspaceFolder}/a.out"
|
||||
},
|
||||
"targetArchitecture": {
|
||||
"type": "string",
|
||||
@@ -951,17 +949,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"CppvsdbgLaunchOptions": {
|
||||
"CpplldbLaunchOptions": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"program",
|
||||
"cwd"
|
||||
"program"
|
||||
],
|
||||
"properties": {
|
||||
"program": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.program.description%",
|
||||
"default": "${workspaceRoot}/program.exe"
|
||||
"default": "${workspaceFolder}/a.out"
|
||||
},
|
||||
"args": {
|
||||
"type": "array",
|
||||
@@ -974,7 +971,401 @@
|
||||
"cwd": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.cwd.description%",
|
||||
"default": "${workspaceRoot}"
|
||||
"default": "."
|
||||
},
|
||||
"debuggerPath": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.cpplldb.debuggerPath.description%",
|
||||
"default": "lldb-dap"
|
||||
},
|
||||
"debuggerArgs": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.cpplldb.debuggerArgs.description%",
|
||||
"default": ""
|
||||
},
|
||||
"environment": {
|
||||
"type": "array",
|
||||
"description": "%c_cpp.debuggers.environment.description%",
|
||||
"items": {
|
||||
"$ref": "#/definitions/KeyValuePair"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"envFile": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.envFile.description%",
|
||||
"default": "${workspaceFolder}/.env"
|
||||
},
|
||||
"stopAtEntry": {
|
||||
"type": "boolean",
|
||||
"markdownDescription": "%c_cpp.debuggers.stopAtEntry.markdownDescription%",
|
||||
"default": false
|
||||
},
|
||||
"serverLaunchTimeout": {
|
||||
"type": "integer",
|
||||
"description": "%c_cpp.debuggers.serverLaunchTimeout.description%",
|
||||
"default": "10000"
|
||||
},
|
||||
"externalConsole": {
|
||||
"type": "boolean",
|
||||
"description": "%c_cpp.debuggers.cpplldb.externalConsole.description%",
|
||||
"default": false
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"markdownDescription": "%c_cpp.debuggers.sourceFileMap.markdownDescription%",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"default": {
|
||||
"<source-path>": "<target-path>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/SourceFileMapEntry"
|
||||
}
|
||||
]
|
||||
},
|
||||
"deploySteps": {
|
||||
"$ref": "#/definitions/DeploySteps"
|
||||
},
|
||||
"disableASLR": {
|
||||
"type": "boolean",
|
||||
"description": "%c_cpp.debuggers.cpplldb.disableAslr.description%",
|
||||
"default": true
|
||||
},
|
||||
"disableSTDIO": {
|
||||
"type": "boolean",
|
||||
"description": "%c_cpp.debuggers.cpplldb.disableStdio.description%",
|
||||
"default": false
|
||||
},
|
||||
"shellExpandArguments": {
|
||||
"type": "boolean",
|
||||
"description": "%c_cpp.debuggers.cpplldb.expandShellArguments.description%",
|
||||
"default": false
|
||||
},
|
||||
"detachOnError": {
|
||||
"type": "boolean",
|
||||
"description": "%c_cpp.debuggers.cpplldb.detachOnError.description%",
|
||||
"default": false
|
||||
},
|
||||
"debuggerRoot": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.cpplldb.debuggerRoot.description%"
|
||||
},
|
||||
"targetTriple": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.cpplldb.targetTriple.description%"
|
||||
},
|
||||
"platformName": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.cpplldb.platformName.description%"
|
||||
},
|
||||
"initCommands": {
|
||||
"type": "array",
|
||||
"description": "%c_cpp.debuggers.cpplldb.initCommands.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"preRunCommands": {
|
||||
"type": "array",
|
||||
"description": "%c_cpp.debuggers.cpplldb.preRunCommands.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"postRunCommands": {
|
||||
"type": "array",
|
||||
"description": "%c_cpp.debuggers.cpplldb.postRunCommands.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"launchCommands": {
|
||||
"type": "array",
|
||||
"description": "%c_cpp.debuggers.cpplldb.launchCommands.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"stopCommands": {
|
||||
"type": "array",
|
||||
"description": "%c_cpp.debuggers.cpplldb.stopCommands.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"exitCommands": {
|
||||
"type": "array",
|
||||
"description": "%c_cpp.debuggers.cpplldb.exitCommands.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"terminateCommands": {
|
||||
"type": "array",
|
||||
"description": "%c_cpp.debuggers.cpplldb.terminateCommands.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"enableAutoVariableSummaries": {
|
||||
"type": "boolean",
|
||||
"description": "%c_cpp.debuggers.cpplldb.enableAutoVariableSummaries.description%",
|
||||
"default": false
|
||||
},
|
||||
"displayExtendedBacktrace": {
|
||||
"type": "boolean",
|
||||
"description": "%c_cpp.debuggers.cpplldb.displayExtendedBacktrace.description%",
|
||||
"default": false
|
||||
},
|
||||
"enableSyntheticChildDebugging": {
|
||||
"type": "boolean",
|
||||
"description": "%c_cpp.debuggers.cpplldb.enableSyntheticChildDebugging.description%",
|
||||
"default": false
|
||||
},
|
||||
"commandEscapePrefix": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.cpplldb.commandEscapePrefix.description%",
|
||||
"default": "`"
|
||||
},
|
||||
"customFrameFormat": {
|
||||
"type": "string",
|
||||
"markdownDescription": "%c_cpp.debuggers.cpplldb.customFrameFormat.markdownDescription%",
|
||||
"default": ""
|
||||
},
|
||||
"customThreadFormat": {
|
||||
"type": "string",
|
||||
"markdownDescription": "%c_cpp.debuggers.cpplldb.customThreadFormat.markdownDescription%",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"CpplldbAttachOptions": {
|
||||
"type": "object",
|
||||
"default": {},
|
||||
"properties": {
|
||||
"program": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.program.description%",
|
||||
"default": "${workspaceFolder}/a.out"
|
||||
},
|
||||
"debuggerPath": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.cpplldbdebuggerPath.description%",
|
||||
"default": "lldb-dap"
|
||||
},
|
||||
"debuggerArgs": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.cpplldb.debuggerArgs.description%",
|
||||
"default": ""
|
||||
},
|
||||
"processId": {
|
||||
"markdownDescription": "%c_cpp.debuggers.processId.anyOf.markdownDescription%",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "${command:pickProcess}"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"markdownDescription": "%c_cpp.debuggers.sourceFileMap.markdownDescription%",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"default": {
|
||||
"<source-path>": "<target-path>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/SourceFileMapEntry"
|
||||
}
|
||||
]
|
||||
},
|
||||
"serverLaunchTimeout": {
|
||||
"type": "integer",
|
||||
"description": "%c_cpp.debuggers.serverLaunchTimeout.description%",
|
||||
"default": "10000"
|
||||
},
|
||||
"waitFor": {
|
||||
"type": "boolean",
|
||||
"markdownDescription": "%c_cpp.debuggers.cpplldb.waitFor.markdownDescription%",
|
||||
"default": true
|
||||
},
|
||||
"deploySteps": {
|
||||
"$ref": "#/definitions/DeploySteps"
|
||||
},
|
||||
"disableASLR": {
|
||||
"type": "boolean",
|
||||
"description": "%c_cpp.debuggers.cpplldb.disableAslr.description%",
|
||||
"default": true
|
||||
},
|
||||
"disableSTDIO": {
|
||||
"type": "boolean",
|
||||
"description": "%c_cpp.debuggers.cpplldb.disableStdio.description%",
|
||||
"default": false
|
||||
},
|
||||
"shellExpandArguments": {
|
||||
"type": "boolean",
|
||||
"description": "%c_cpp.debuggers.cpplldb.expandShellArguments.description%",
|
||||
"default": false
|
||||
},
|
||||
"detachOnError": {
|
||||
"type": "boolean",
|
||||
"description": "%c_cpp.debuggers.cpplldb.detachOnError.description%",
|
||||
"default": false
|
||||
},
|
||||
"debuggerRoot": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.cpplldb.debuggerRoot.description%"
|
||||
},
|
||||
"targetTriple": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.cpplldb.targetTriple.description%"
|
||||
},
|
||||
"platformName": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.cpplldb.platformName.description%"
|
||||
},
|
||||
"attachCommands": {
|
||||
"type": "array",
|
||||
"description": "%c_cpp.debuggers.cpplldb.platformName.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"initCommands": {
|
||||
"type": "array",
|
||||
"description": "%c_cpp.debuggers.cpplldb.initCommands.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"preRunCommands": {
|
||||
"type": "array",
|
||||
"description": "%c_cpp.debuggers.cpplldb.preRunCommands.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"postRunCommands": {
|
||||
"type": "array",
|
||||
"description": "%c_cpp.debuggers.cpplldb.postRunCommands.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"stopCommands": {
|
||||
"type": "array",
|
||||
"description": "%c_cpp.debuggers.cpplldb.stopCommands.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"exitCommands": {
|
||||
"type": "array",
|
||||
"description": "%c_cpp.debuggers.cpplldb.exitCommands.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"terminateCommands": {
|
||||
"type": "array",
|
||||
"description": "%c_cpp.debuggers.cpplldb.terminateCommands.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"coreFile": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.cpplldb.coreFile.description%"
|
||||
},
|
||||
"gdb-remote-port": {
|
||||
"type": [
|
||||
"number",
|
||||
"string"
|
||||
],
|
||||
"markdownDescription": "%c_cpp.debuggers.cpplldb.gdbRemotePort.markdownDescription%"
|
||||
},
|
||||
"gdb-remote-hostname": {
|
||||
"type": "string",
|
||||
"markdownDescription": "%c_cpp.debuggers.cpplldb.gdbRemoteHost.markdownDescription%"
|
||||
},
|
||||
"enableAutoVariableSummaries": {
|
||||
"type": "boolean",
|
||||
"description": "%c_cpp.debuggers.cpplldb.enableAutoVariableSummaries.description%",
|
||||
"default": false
|
||||
},
|
||||
"displayExtendedBacktrace": {
|
||||
"type": "boolean",
|
||||
"description": "%c_cpp.debuggers.cpplldb.displayExtendedBacktrace.description%",
|
||||
"default": false
|
||||
},
|
||||
"enableSyntheticChildDebugging": {
|
||||
"type": "boolean",
|
||||
"description": "%c_cpp.debuggers.cpplldb.enableSyntheticChildDebugging.description%",
|
||||
"default": false
|
||||
},
|
||||
"commandEscapePrefix": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.cpplldb.commandEscapePrefix.description%",
|
||||
"default": "`"
|
||||
},
|
||||
"customFrameFormat": {
|
||||
"type": "string",
|
||||
"markdownDescription": "%c_cpp.debuggers.cpplldb.customFrameFormat.markdownDescription%",
|
||||
"default": ""
|
||||
},
|
||||
"customThreadFormat": {
|
||||
"type": "string",
|
||||
"markdownDescription": "%c_cpp.debuggers.cpplldb.customThreadFormat.markdownDescription%",
|
||||
"default": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"CppvsdbgLaunchOptions": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"program",
|
||||
"cwd"
|
||||
],
|
||||
"properties": {
|
||||
"program": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.program.description%",
|
||||
"default": "${workspaceFolder}/program.exe"
|
||||
},
|
||||
"args": {
|
||||
"type": "array",
|
||||
"description": "%c_cpp.debuggers.args.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"cwd": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.cwd.description%",
|
||||
"default": "${workspaceFolder}"
|
||||
},
|
||||
"environment": {
|
||||
"type": "array",
|
||||
@@ -1102,6 +1493,11 @@
|
||||
"processId"
|
||||
],
|
||||
"properties": {
|
||||
"program": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.program.description%",
|
||||
"default": "${workspaceFolder}/program.exe"
|
||||
},
|
||||
"symbolSearchPath": {
|
||||
"type": "string",
|
||||
"description": "%c_cpp.debuggers.symbolSearchPath.description%",
|
||||
|
||||
Reference in New Issue
Block a user