@@ -3,10 +3,12 @@ import * as util from "util";
33import * as vscode from "vscode" ;
44import * as child_process from "child_process" ;
55import * as fs from "node:fs/promises" ;
6+ import { ConfigureButton , OpenSettingsButton } from "./ui/show-error-message" ;
7+ import { ErrorWithNotification } from "./ui/error-with-notification" ;
68
79const exec = util . promisify ( child_process . execFile ) ;
810
9- export async function isExecutable ( path : string ) : Promise < Boolean > {
11+ async function isExecutable ( path : string ) : Promise < Boolean > {
1012 try {
1113 await fs . access ( path , fs . constants . X_OK ) ;
1214 } catch {
@@ -25,7 +27,7 @@ async function findWithXcrun(executable: string): Promise<string | undefined> {
2527 if ( stdout ) {
2628 return stdout . toString ( ) . trimEnd ( ) ;
2729 }
28- } catch ( error ) { }
30+ } catch ( error ) { }
2931 }
3032 return undefined ;
3133}
@@ -65,142 +67,157 @@ async function findDAPExecutable(): Promise<string | undefined> {
6567 return undefined ;
6668}
6769
70+ /**
71+ * Retrieves the lldb-dap executable path either from settings or the provided
72+ * {@link vscode.DebugConfiguration}.
73+ *
74+ * @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within
75+ * @param configuration The {@link vscode.DebugConfiguration} that will be launched
76+ * @throws An {@link ErrorWithNotification} if something went wrong
77+ * @returns The path to the lldb-dap executable
78+ */
6879async function getDAPExecutable (
69- session : vscode . DebugSession ,
70- ) : Promise < string | undefined > {
80+ workspaceFolder : vscode . WorkspaceFolder | undefined ,
81+ configuration : vscode . DebugConfiguration ,
82+ ) : Promise < string > {
7183 // Check if the executable was provided in the launch configuration.
72- const launchConfigPath = session . configuration [ "debugAdapterExecutable" ] ;
84+ const launchConfigPath = configuration [ "debugAdapterExecutable" ] ;
7385 if ( typeof launchConfigPath === "string" && launchConfigPath . length !== 0 ) {
86+ if ( ! ( await isExecutable ( launchConfigPath ) ) ) {
87+ throw new ErrorWithNotification (
88+ `Debug adapter path "${ launchConfigPath } " is not a valid file. The path comes from your launch configuration.` ,
89+ new ConfigureButton ( ) ,
90+ ) ;
91+ }
7492 return launchConfigPath ;
7593 }
7694
7795 // Check if the executable was provided in the extension's configuration.
78- const config = vscode . workspace . getConfiguration (
79- "lldb-dap" ,
80- session . workspaceFolder ,
81- ) ;
96+ const config = vscode . workspace . getConfiguration ( "lldb-dap" , workspaceFolder ) ;
8297 const configPath = config . get < string > ( "executable-path" ) ;
8398 if ( configPath && configPath . length !== 0 ) {
99+ if ( ! ( await isExecutable ( configPath ) ) ) {
100+ throw new ErrorWithNotification (
101+ `Debug adapter path "${ configPath } " is not a valid file. The path comes from your settings.` ,
102+ new OpenSettingsButton ( "lldb-dap.executable-path" ) ,
103+ ) ;
104+ }
84105 return configPath ;
85106 }
86107
87108 // Try finding the lldb-dap binary.
88109 const foundPath = await findDAPExecutable ( ) ;
89110 if ( foundPath ) {
111+ if ( ! ( await isExecutable ( foundPath ) ) ) {
112+ throw new ErrorWithNotification (
113+ `Found a potential debug adapter on your system at "${ configPath } ", but it is not a valid file.` ,
114+ new OpenSettingsButton ( "lldb-dap.executable-path" ) ,
115+ ) ;
116+ }
90117 return foundPath ;
91118 }
92119
93- return undefined ;
120+ throw new ErrorWithNotification (
121+ "Unable to find the path to the LLDB debug adapter executable." ,
122+ new OpenSettingsButton ( "lldb-dap.executable-path" ) ,
123+ ) ;
94124}
95125
96- async function isServerModeSupported ( exe : string ) : Promise < boolean > {
97- const { stdout } = await exec ( exe , [ '--help' ] ) ;
98- return / - - c o n n e c t i o n / . test ( stdout ) ;
126+ /**
127+ * Retrieves the arguments that will be provided to lldb-dap either from settings or the provided
128+ * {@link vscode.DebugConfiguration}.
129+ *
130+ * @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within
131+ * @param configuration The {@link vscode.DebugConfiguration} that will be launched
132+ * @throws An {@link ErrorWithNotification} if something went wrong
133+ * @returns The arguments that will be provided to lldb-dap
134+ */
135+ async function getDAPArguments (
136+ workspaceFolder : vscode . WorkspaceFolder | undefined ,
137+ configuration : vscode . DebugConfiguration ,
138+ ) : Promise < string [ ] > {
139+ // Check the debug configuration for arguments first.
140+ const debugConfigArgs = configuration . debugAdapterArgs ;
141+ if ( debugConfigArgs ) {
142+ if (
143+ ! Array . isArray ( debugConfigArgs ) ||
144+ debugConfigArgs . findIndex ( ( entry ) => typeof entry !== "string" ) !== - 1
145+ ) {
146+ throw new ErrorWithNotification (
147+ "The debugAdapterArgs property must be an array of string values. Please update your launch configuration" ,
148+ new ConfigureButton ( ) ,
149+ ) ;
150+ }
151+ return debugConfigArgs ;
152+ }
153+ // Fall back on the workspace configuration.
154+ return vscode . workspace
155+ . getConfiguration ( "lldb-dap" , workspaceFolder )
156+ . get < string [ ] > ( "arguments" , [ ] ) ;
157+ }
158+
159+ /**
160+ * Creates a new {@link vscode.DebugAdapterExecutable} based on the provided workspace folder and
161+ * debug configuration. Assumes that the given debug configuration is for a local launch of lldb-dap.
162+ *
163+ * @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within
164+ * @param configuration The {@link vscode.DebugConfiguration} that will be launched
165+ * @throws An {@link ErrorWithNotification} if something went wrong
166+ * @returns The {@link vscode.DebugAdapterExecutable} that can be used to launch lldb-dap
167+ */
168+ export async function createDebugAdapterExecutable (
169+ workspaceFolder : vscode . WorkspaceFolder | undefined ,
170+ configuration : vscode . DebugConfiguration ,
171+ ) : Promise < vscode . DebugAdapterExecutable > {
172+ const config = vscode . workspace . getConfiguration ( "lldb-dap" , workspaceFolder ) ;
173+ const log_path = config . get < string > ( "log-path" ) ;
174+ let env : { [ key : string ] : string } = { } ;
175+ if ( log_path ) {
176+ env [ "LLDBDAP_LOG" ] = log_path ;
177+ }
178+ const configEnvironment =
179+ config . get < { [ key : string ] : string } > ( "environment" ) || { } ;
180+ const dapPath = await getDAPExecutable ( workspaceFolder , configuration ) ;
181+
182+ const dbgOptions = {
183+ env : {
184+ ...configEnvironment ,
185+ ...env ,
186+ } ,
187+ } ;
188+ const dbgArgs = await getDAPArguments ( workspaceFolder , configuration ) ;
189+
190+ return new vscode . DebugAdapterExecutable ( dapPath , dbgArgs , dbgOptions ) ;
99191}
100192
101193/**
102194 * This class defines a factory used to find the lldb-dap binary to use
103195 * depending on the session configuration.
104196 */
105197export class LLDBDapDescriptorFactory
106- implements vscode . DebugAdapterDescriptorFactory , vscode . Disposable {
107- private server ?: Promise < { process : child_process . ChildProcess , host : string , port : number } > ;
108-
109- dispose ( ) {
110- this . server ?. then ( ( { process } ) => {
111- process . kill ( ) ;
112- } ) ;
113- }
114-
198+ implements vscode . DebugAdapterDescriptorFactory
199+ {
115200 async createDebugAdapterDescriptor (
116201 session : vscode . DebugSession ,
117202 executable : vscode . DebugAdapterExecutable | undefined ,
118203 ) : Promise < vscode . DebugAdapterDescriptor | undefined > {
119- const config = vscode . workspace . getConfiguration (
120- "lldb-dap" ,
121- session . workspaceFolder ,
122- ) ;
123-
124- const log_path = config . get < string > ( "log-path" ) ;
125- let env : { [ key : string ] : string } = { } ;
126- if ( log_path ) {
127- env [ "LLDBDAP_LOG" ] = log_path ;
128- }
129- const configEnvironment =
130- config . get < { [ key : string ] : string } > ( "environment" ) || { } ;
131- const dapPath = ( await getDAPExecutable ( session ) ) ?? executable ?. command ;
132-
133- if ( ! dapPath ) {
134- LLDBDapDescriptorFactory . showLLDBDapNotFoundMessage ( ) ;
135- return undefined ;
136- }
137-
138- if ( ! ( await isExecutable ( dapPath ) ) ) {
139- LLDBDapDescriptorFactory . showLLDBDapNotFoundMessage ( dapPath ) ;
140- return ;
141- }
142-
143- const dbgOptions = {
144- env : {
145- ...executable ?. options ?. env ,
146- ...configEnvironment ,
147- ...env ,
148- } ,
149- } ;
150- const dbgArgs = executable ?. args ?? [ ] ;
151-
152- const serverMode = config . get < boolean > ( 'serverMode' , false ) ;
153- if ( serverMode && await isServerModeSupported ( dapPath ) ) {
154- const { host, port } = await this . startServer ( dapPath , dbgArgs , dbgOptions ) ;
155- return new vscode . DebugAdapterServer ( port , host ) ;
204+ if ( executable ) {
205+ throw new Error (
206+ "Setting the debug adapter executable in the package.json is not supported." ,
207+ ) ;
156208 }
157209
158- return new vscode . DebugAdapterExecutable ( dapPath , dbgArgs , dbgOptions ) ;
159- }
160-
161- startServer ( dapPath : string , args : string [ ] , options : child_process . CommonSpawnOptions ) : Promise < { host : string , port : number } > {
162- if ( this . server ) return this . server ;
163-
164- this . server = new Promise ( resolve => {
165- args . push (
166- '--connection' ,
167- 'connect://localhost:0'
210+ // Use a server connection if the debugAdapterPort is provided
211+ if ( session . configuration . debugAdapterPort ) {
212+ return new vscode . DebugAdapterServer (
213+ session . configuration . debugAdapterPort ,
214+ session . configuration . debugAdapterHost ,
168215 ) ;
169- const server = child_process . spawn ( dapPath , args , options ) ;
170- server . stdout ! . setEncoding ( 'utf8' ) . once ( 'data' , ( data : string ) => {
171- const connection = / c o n n e c t i o n : \/ \/ \[ ( [ ^ \] ] + ) \] : ( \d + ) / . exec ( data ) ;
172- if ( connection ) {
173- const host = connection [ 1 ] ;
174- const port = Number ( connection [ 2 ] ) ;
175- resolve ( { process : server , host, port } ) ;
176- }
177- } ) ;
178- server . on ( 'exit' , ( ) => {
179- this . server = undefined ;
180- } )
181- } ) ;
182- return this . server ;
183- }
216+ }
184217
185- /**
186- * Shows a message box when the debug adapter's path is not found
187- */
188- static async showLLDBDapNotFoundMessage ( path ?: string ) {
189- const message =
190- path
191- ? `Debug adapter path: ${ path } is not a valid file.`
192- : "Unable to find the path to the LLDB debug adapter executable." ;
193- const openSettingsAction = "Open Settings" ;
194- const callbackValue = await vscode . window . showErrorMessage (
195- message ,
196- openSettingsAction ,
218+ return createDebugAdapterExecutable (
219+ session . workspaceFolder ,
220+ session . configuration ,
197221 ) ;
198-
199- if ( openSettingsAction === callbackValue ) {
200- vscode . commands . executeCommand (
201- "workbench.action.openSettings" ,
202- "lldb-dap.executable-path" ,
203- ) ;
204- }
205222 }
206223}
0 commit comments