@@ -3,10 +3,12 @@ import * as util from "util";
3
3
import * as vscode from "vscode" ;
4
4
import * as child_process from "child_process" ;
5
5
import * as fs from "node:fs/promises" ;
6
+ import { ConfigureButton , OpenSettingsButton } from "./ui/show-error-message" ;
7
+ import { ErrorWithNotification } from "./ui/error-with-notification" ;
6
8
7
9
const exec = util . promisify ( child_process . execFile ) ;
8
10
9
- export async function isExecutable ( path : string ) : Promise < Boolean > {
11
+ async function isExecutable ( path : string ) : Promise < Boolean > {
10
12
try {
11
13
await fs . access ( path , fs . constants . X_OK ) ;
12
14
} catch {
@@ -25,7 +27,7 @@ async function findWithXcrun(executable: string): Promise<string | undefined> {
25
27
if ( stdout ) {
26
28
return stdout . toString ( ) . trimEnd ( ) ;
27
29
}
28
- } catch ( error ) { }
30
+ } catch ( error ) { }
29
31
}
30
32
return undefined ;
31
33
}
@@ -65,142 +67,157 @@ async function findDAPExecutable(): Promise<string | undefined> {
65
67
return undefined ;
66
68
}
67
69
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
+ */
68
79
async function getDAPExecutable (
69
- session : vscode . DebugSession ,
70
- ) : Promise < string | undefined > {
80
+ workspaceFolder : vscode . WorkspaceFolder | undefined ,
81
+ configuration : vscode . DebugConfiguration ,
82
+ ) : Promise < string > {
71
83
// Check if the executable was provided in the launch configuration.
72
- const launchConfigPath = session . configuration [ "debugAdapterExecutable" ] ;
84
+ const launchConfigPath = configuration [ "debugAdapterExecutable" ] ;
73
85
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
+ }
74
92
return launchConfigPath ;
75
93
}
76
94
77
95
// 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 ) ;
82
97
const configPath = config . get < string > ( "executable-path" ) ;
83
98
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
+ }
84
105
return configPath ;
85
106
}
86
107
87
108
// Try finding the lldb-dap binary.
88
109
const foundPath = await findDAPExecutable ( ) ;
89
110
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
+ }
90
117
return foundPath ;
91
118
}
92
119
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
+ ) ;
94
124
}
95
125
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 ) ;
99
191
}
100
192
101
193
/**
102
194
* This class defines a factory used to find the lldb-dap binary to use
103
195
* depending on the session configuration.
104
196
*/
105
197
export 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
+ {
115
200
async createDebugAdapterDescriptor (
116
201
session : vscode . DebugSession ,
117
202
executable : vscode . DebugAdapterExecutable | undefined ,
118
203
) : 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
+ ) ;
156
208
}
157
209
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 ,
168
215
) ;
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
+ }
184
217
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 ,
197
221
) ;
198
-
199
- if ( openSettingsAction === callbackValue ) {
200
- vscode . commands . executeCommand (
201
- "workbench.action.openSettings" ,
202
- "lldb-dap.executable-path" ,
203
- ) ;
204
- }
205
222
}
206
223
}
0 commit comments