Skip to content

Commit 98bcd4b

Browse files
committed
(GH-459) Use new DebugAdapterDescriptionFactory API
This commit adds a new Debugging Feature class which is used to create a DebugAdapterDescriptionFactory. This factory is responsible for creating the environment to start the Puppet Language Debug Server e.g. getting the correct path and environment variables.
1 parent a96e021 commit 98bcd4b

File tree

5 files changed

+158
-1
lines changed

5 files changed

+158
-1
lines changed

src/configuration.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ export class ConnectionConfiguration implements IConnectionConfiguration {
137137
return path.join('vendor', 'languageserver', 'puppet-languageserver');
138138
}
139139

140+
get debugServerPath(): string {
141+
return path.join('vendor', 'languageserver', 'puppet-debugserver');
142+
}
143+
140144
get type(): ConnectionType {
141145
switch (this.settings.editorService.protocol) {
142146
case ProtocolType.TCP:

src/extension.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { ConnectionHandler } from './handler';
88
import { StdioConnectionHandler } from './handlers/stdio';
99
import { TcpConnectionHandler } from './handlers/tcp';
1010
import { IFeature } from './feature';
11+
import { DebuggingFeature } from './feature/DebuggingFeature';
1112
import { DebugConfigurationFeature } from './feature/DebugConfigurationFeature';
1213
import { FormatDocumentFeature } from './feature/FormatDocumentFeature';
1314
import { NodeGraphFeature } from './feature/NodeGraphFeature';
@@ -22,6 +23,7 @@ import { Reporter, reporter } from './telemetry/telemetry';
2223

2324
export const puppetLangID = 'puppet'; // don't change this
2425
export const puppetFileLangID = 'puppetfile'; // don't change this
26+
const debugType = 'Puppet'; // don't change this
2527

2628
let extContext: vscode.ExtensionContext;
2729
let connectionHandler: ConnectionHandler;
@@ -72,7 +74,8 @@ export function activate(context: vscode.ExtensionContext) {
7274
new FormatDocumentFeature(puppetLangID, connectionHandler, settings, logger, extContext),
7375
new NodeGraphFeature(puppetLangID, connectionHandler, logger, extContext),
7476
new PDKFeature(extContext, logger),
75-
new PuppetResourceFeature(extContext, connectionHandler, logger)
77+
new PuppetResourceFeature(extContext, connectionHandler, logger),
78+
new DebuggingFeature(debugType, settings, configSettings, extContext, logger)
7679
];
7780
}
7881

src/feature/DebuggingFeature.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
'use strict';
2+
3+
import * as vscode from "vscode";
4+
import * as cp from 'child_process';
5+
6+
import { IFeature } from "../feature";
7+
import { ILogger } from "../logging";
8+
import { CommandEnvironmentHelper } from "../helpers/commandHelper";
9+
import { IConnectionConfiguration } from '../interfaces';
10+
import { ISettings } from '../settings';
11+
12+
// Socket vs Exec DebugAdapter types
13+
// https://github.com/Microsoft/vscode/blob/2808feeaf6b24feaaa6ba49fb91ea165c4d5fb06/src/vs/workbench/parts/debug/node/debugger.ts#L58-L61
14+
//
15+
// DebugAdapterExecutable uses stdin/stdout
16+
// https://github.com/Microsoft/vscode/blob/2808feeaf6b24feaaa6ba49fb91ea165c4d5fb06/src/vs/workbench/parts/debug/node/debugAdapter.ts#L305
17+
//
18+
// DebugAdapterServer uses tcp
19+
// https://github.com/Microsoft/vscode/blob/2808feeaf6b24feaaa6ba49fb91ea165c4d5fb06/src/vs/workbench/parts/debug/node/debugAdapter.ts#L256
20+
21+
export class DebugAdapterDescriptorFactory implements vscode.DebugAdapterDescriptorFactory, vscode.Disposable {
22+
readonly Context: vscode.ExtensionContext;
23+
readonly Settings: ISettings;
24+
readonly Config: IConnectionConfiguration;
25+
readonly Logger: ILogger;
26+
27+
public ChildProcesses: cp.ChildProcess[] = [];
28+
29+
constructor(
30+
context: vscode.ExtensionContext,
31+
settings: ISettings,
32+
config: IConnectionConfiguration,
33+
logger: ILogger
34+
) {
35+
this.Context = context;
36+
this.Settings = settings;
37+
this.Config = config;
38+
this.Logger = logger;
39+
}
40+
41+
public createDebugAdapterDescriptor(session: vscode.DebugSession, executable: vscode.DebugAdapterExecutable): vscode.ProviderResult<vscode.DebugAdapterDescriptor> {
42+
// Right now we don't care about session as we only have one type of adapter, which is launch. When
43+
// we add the ability to attach to a debugger remotely we'll need to switch scenarios based on `session`
44+
let thisFactory = this;
45+
46+
return new Promise<vscode.DebugAdapterDescriptor>( function(resolve, reject) {
47+
let debugServer = CommandEnvironmentHelper.getDebugServerRubyEnvFromConfiguration(
48+
thisFactory.Context.asAbsolutePath(thisFactory.Config.debugServerPath),
49+
thisFactory.Settings,
50+
thisFactory.Config,
51+
);
52+
53+
let spawn_options: cp.SpawnOptions = {};
54+
spawn_options.env = debugServer.options.env;
55+
spawn_options.stdio = 'pipe';
56+
if (process.platform !== 'win32') { spawn_options.shell = true; }
57+
58+
thisFactory.Logger.verbose("Starting the Debug Server with " + debugServer.command + " " + debugServer.args.join(" "));
59+
let debugServerProc = cp.spawn(debugServer.command, debugServer.args, spawn_options);
60+
thisFactory.ChildProcesses.push(debugServerProc);
61+
62+
let debugSessionRunning: boolean = false;
63+
debugServerProc.stdout.on('data', (data) => {
64+
thisFactory.Logger.debug("Debug Server STDOUT: " + data.toString());
65+
// If the debug client isn't already running and it's sent the trigger text, start up a client
66+
if ( !debugSessionRunning && (data.toString().match("DEBUG SERVER RUNNING") !== null) ) {
67+
debugSessionRunning = true;
68+
69+
var p = data.toString().match(/DEBUG SERVER RUNNING (.*):(\d+)/);
70+
if (p === null) {
71+
reject("Debug Server started but unable to parse hostname and port");
72+
} else {
73+
thisFactory.Logger.debug("Starting Debug Client connection to " + p[1] + ":" + p[2]);
74+
resolve(new vscode.DebugAdapterServer(Number(p[2]), p[1]));
75+
}
76+
}
77+
});
78+
debugServerProc.on('error', (data) => {
79+
thisFactory.Logger.error("Debug Srver errored with " + data);
80+
reject("Spawning Debug Server failed with " + data);
81+
});
82+
debugServerProc.on('close', (exitCode) => {
83+
thisFactory.Logger.verbose("Debug Server exited with exitcode " + exitCode);
84+
});
85+
});
86+
}
87+
88+
public dispose(): any {
89+
this.ChildProcesses.forEach( (item) => {
90+
item.kill('SIGHUP');
91+
});
92+
this.ChildProcesses = [];
93+
return undefined;
94+
}
95+
}
96+
97+
export class DebuggingFeature implements IFeature {
98+
private factory: DebugAdapterDescriptorFactory;
99+
100+
constructor(
101+
debugType: string,
102+
settings: ISettings,
103+
config: IConnectionConfiguration,
104+
context: vscode.ExtensionContext,
105+
logger: ILogger
106+
) {
107+
this.factory = new DebugAdapterDescriptorFactory(context, settings, config, logger);
108+
109+
logger.debug("Registered DebugAdapterDescriptorFactory for " + debugType);
110+
context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory(debugType, this.factory));
111+
}
112+
113+
public dispose(): any {
114+
if (this.factory !== null) {
115+
this.factory.dispose();
116+
this.factory = null;
117+
}
118+
}
119+
}

src/helpers/commandHelper.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,20 @@ export class CommandEnvironmentHelper {
2121
return exe;
2222
}
2323

24+
public static getDebugServerRubyEnvFromConfiguration(
25+
debugServerpath: string,
26+
settings: ISettings,
27+
config: IConnectionConfiguration,
28+
): Executable {
29+
let exe: Executable = {
30+
command: this.buildExecutableCommand(settings, config),
31+
args: this.buildDebugServerArguments(debugServerpath),
32+
options: {},
33+
};
34+
this.applyRubyEnvFromConfiguration(exe, settings, config);
35+
return exe;
36+
}
37+
2438
private static applyRubyEnvFromConfiguration(
2539
exe: Executable,
2640
settings: ISettings,
@@ -107,6 +121,22 @@ export class CommandEnvironmentHelper {
107121
return args;
108122
}
109123

124+
private static buildDebugServerArguments(
125+
serverPath: string
126+
): string[] {
127+
let args = [serverPath];
128+
129+
// The Debug Adapter always runs on TCP and IPv4 loopback
130+
// Using localhost can have issues due to ruby and node differing on what address
131+
// to use for localhost e.g Ruby may prefer 127.0.0.1 (IP4) and Node may prefer ::1 (IP6)
132+
// and therefore won't connect.
133+
args.push('--ip=127.0.0.1');
134+
135+
// TODO: Add additional command line args e.g. --debuglogfie
136+
137+
return args;
138+
}
139+
110140
private static buildPuppetEnvironment(exe: Executable, config: IConnectionConfiguration) {
111141
exe.options.env.RUBYOPT = 'rubygems';
112142
exe.options.env.SSL_CERT_FILE = config.sslCertFile;

src/interfaces.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export interface IConnectionConfiguration {
3131
puppetBaseDir: string;
3232
puppetDir: string;
3333
languageServerPath: string;
34+
debugServerPath: string;
3435
rubydir: string;
3536
rubylib: string;
3637
environmentPath: string;

0 commit comments

Comments
 (0)