Skip to content
This repository has been archived by the owner on Oct 12, 2022. It is now read-only.

Add support for Windows Subsystem Linux #158

Merged
merged 1 commit into from
Sep 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/node/extension/protocolDetection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import * as cp from 'child_process';
import { log, localize } from './utilities';
import * as net from 'net';
import * as WSL from '../subsystemLinux';

export const INSPECTOR_PORT_DEFAULT = 9229;
export const LEGACY_PORT_DEFAULT = 5858;
Expand Down Expand Up @@ -103,7 +104,7 @@ function detectProtocolForLaunch(config: any): string | undefined {
return 'inspector';
} else {
// only determine version if no runtimeExecutable is set (and 'node' on PATH is used)
const result = cp.spawnSync('node', ['--version']);
const result = WSL.spawnSync(config.useWSL, 'node', ['--version']);
const semVerString = result.stdout ? result.stdout.toString() : undefined;
if (semVerString) {
config.__nodeVersion = semVerString.trim();
Expand Down
33 changes: 28 additions & 5 deletions src/node/nodeDebug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from './nodeV8Protocol';
import {ISourceMaps, SourceMaps, SourceMap} from './sourceMaps';
import * as PathUtils from './pathUtilities';
import * as WSL from './subsystemLinux';
import * as CP from 'child_process';
import * as Net from 'net';
import * as URL from 'url';
Expand Down Expand Up @@ -299,6 +300,8 @@ interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments, C
externalConsole?: boolean;
/** Where to launch the debug target. */
console?: ConsoleType;
/** Use Windows Subsystem Linux */
useWSL?: boolean;
}

/**
Expand Down Expand Up @@ -839,10 +842,16 @@ export class NodeDebugSession extends LoggingDebugSession {
this._console = 'externalTerminal';
}

if (args.useWSL && !WSL.subsystemLinuxPresent) {
this.sendErrorResponse(response, 2007, localize('attribute.wls.not.exist', "Cannot find Windows Subsystem Linux installation"));
}

const port = args.port || random(3000, 50000);

let runtimeExecutable = args.runtimeExecutable;
if (runtimeExecutable) {
if (args.useWSL) {
runtimeExecutable = runtimeExecutable || NodeDebugSession.NODE;
} else if (runtimeExecutable) {
if (!Path.isAbsolute(runtimeExecutable)) {
const re = PathUtils.findOnPath(runtimeExecutable);
if (!re) {
Expand Down Expand Up @@ -1054,13 +1063,26 @@ export class NodeDebugSession extends LoggingDebugSession {
}
}

const wslLaunchArgs = WSL.createLaunchArg(args.useWSL,
this._supportsRunInTerminalRequest && this._console === 'externalTerminal',
<string> workingDirectory,
launchArgs[0],
launchArgs.slice(1));
// if using subsystem linux, we will trick the debugger to map source files
if (args.useWSL && !args.localRoot) {
args.localRoot = wslLaunchArgs.localRoot;
this._localRoot = wslLaunchArgs.localRoot;
args.remoteRoot = wslLaunchArgs.remoteRoot;
this._remoteRoot = wslLaunchArgs.remoteRoot;
}

if (this._supportsRunInTerminalRequest && (this._console === 'externalTerminal' || this._console === 'integratedTerminal')) {

const termArgs : DebugProtocol.RunInTerminalRequestArguments = {
kind: this._console === 'integratedTerminal' ? 'integrated' : 'external',
title: localize('node.console.title', "Node Debug Console"),
cwd: <string> workingDirectory,
args: launchArgs,
cwd: wslLaunchArgs.cwd,
args: wslLaunchArgs.combined,
env: envVars
};

Expand All @@ -1069,7 +1091,8 @@ export class NodeDebugSession extends LoggingDebugSession {

// since node starts in a terminal, we cannot track it with an 'exit' handler
// plan for polling after we have gotten the process pid.
this._pollForNodeProcess = !args.runtimeExecutable; // only if no 'runtimeExecutable' is specified
this._pollForNodeProcess = !args.runtimeExecutable // only if no 'runtimeExecutable' is specified
&& !args.useWSL; // it will not work with WSL either

if (this._noDebug) {
this.sendResponse(response);
Expand All @@ -1094,7 +1117,7 @@ export class NodeDebugSession extends LoggingDebugSession {
env: envVars
};

const nodeProcess = CP.spawn(runtimeExecutable, launchArgs.slice(1), options);
const nodeProcess = CP.spawn(wslLaunchArgs.executable, wslLaunchArgs.args, options);
nodeProcess.on('error', (error) => {
// tslint:disable-next-line:no-bitwise
this.sendErrorResponse(response, 2017, localize('VSND2017', "Cannot launch debug target ({0}).", '{_error}'), { _error: error.message }, ErrorDestination.Telemetry | ErrorDestination.User );
Expand Down
72 changes: 72 additions & 0 deletions src/node/subsystemLinux.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import * as path from 'path';
import * as fs from 'fs';
import * as child_process from 'child_process';

const isWindows = process.platform === 'win32';
const is64bit = process.arch === 'x64';

const bashPath32bitApp = path.join(process.env['SystemRoot'], 'Sysnative', 'bash.exe');
const bashPath64bitApp = path.join(process.env['SystemRoot'], 'System32', 'bash.exe');
const bashPathHost = is64bit ? bashPath64bitApp : bashPath32bitApp;

export function subsystemLinuxPresent() : boolean {
const bashPath = is64bit ? bashPath64bitApp : bashPath32bitApp;
if (!isWindows) {
return false;
}
return fs.existsSync(bashPath);
}

function windowsPathToWSLPath(windowsPath: string) : string {
if (!windowsPath || !isWindows) {
return undefined;
} else if (path.isAbsolute(windowsPath)) {
return `/mnt/${windowsPath.substr(0,1).toLowerCase()}/${windowsPath.substr(3).replace(/\\/g, '/')}`;
} else {
return windowsPath.replace(/\\/g, '/');
}
}

export interface ILaunchArgs {
cwd: string;
executable: string;
args: string[];
combined: string[];
localRoot?: string;
remoteRoot?: string;
}

export function createLaunchArg(useSubsytemLinux: boolean, useExternalConsole: boolean, cwd: string | undefined, executable: string, args?: string[]): ILaunchArgs {
const subsystemLinuxPath = useExternalConsole ? bashPath64bitApp : bashPathHost;

if (useSubsytemLinux) {
let bashCommand = [executable].concat(args || []).map((element) => {
return element.indexOf(' ') > 0 ? `'${element}'` : element;
}).join(' ');
return <ILaunchArgs>{
cwd: cwd,
executable: subsystemLinuxPath,
args: ['-ic', bashCommand],
combined: [subsystemLinuxPath].concat(['-ic', bashCommand]),
localRoot: cwd,
remoteRoot: windowsPathToWSLPath(cwd)
};
} else {
return <ILaunchArgs>{
cwd: cwd,
executable: executable,
args: args || [],
combined: [executable].concat(args || [])
};
}
}

export function spawn(useWSL: boolean, executable: string, args?: string[], options? : child_process.SpawnOptions) {
const launchArgs = createLaunchArg(useWSL, false, undefined, executable, args);
return child_process.spawn(launchArgs.executable, launchArgs.args, options);
}

export function spawnSync(useWSL: boolean, executable: string, args?: string[], options? : child_process.SpawnSyncOptions) {
const launchArgs = createLaunchArg(useWSL, false, undefined, executable, args);
return child_process.spawnSync(launchArgs.executable, launchArgs.args, options);
}