Skip to content

Commit

Permalink
feat(windows): Utilize wsl.exe instead of deprecated bash.exe
Browse files Browse the repository at this point in the history
* feature: Allow usage without installing bashdb

* further changes to enable debugging without installing bashdb

* some things discovered by shellcheck extension

* some readme update

* fix for 'examine' and result with multiple spaces

* changes related to proper set

* fix examine functionality

* create GPL licensed folder in  source

* merge to newest bashdb (master)

* print some non-changing variables only at startup (improve performance)

* - switch windows to use "bash" instead of full bash exe path
- better immidiate window functionality
- handle chmod error

* prepare for merge

* interactive scripting - separate input stream

* some fixes related to breakpoint setting

* fix from bashdb unit tests

* fix for examine - multiple spaces in strings

* show command executable location

* proper debug console colors

* use integrated terminal

* remove polling (performance)

* Remove deprecated bash exe in windows

* fix for pause and stopping

* fix for termination
  • Loading branch information
rogalmic authored and ext-michal.rogalinski committed Sep 14, 2018
1 parent b1c5a19 commit 3bbc0e8
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 35 deletions.
44 changes: 22 additions & 22 deletions src/bashDebug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import {
Thread, StackFrame, Scope, Source, Handles, Breakpoint
} from 'vscode-debugadapter';
import { DebugProtocol } from 'vscode-debugprotocol';
import { ChildProcess, spawn } from 'child_process';
import { ChildProcess } from 'child_process';
import { basename, normalize, join, isAbsolute } from 'path';
import * as fs from 'fs';
import * as which from 'npm-which';
import { validatePath } from './bashRuntime';
import { getWSLPath, reverseWSLPath, escapeCharactersInBashdbArg } from './handlePath';
import { getWSLPath, reverseWSLPath, escapeCharactersInBashdbArg, getWSLLauncherPath } from './handlePath';
import { EventSource } from './eventSource';
import { spawnBashScript } from './spawnBash';

export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {

Expand Down Expand Up @@ -78,19 +79,9 @@ export class BashDebugSession extends LoggingDebugSession {
protected disconnectRequest(response: DebugProtocol.DisconnectResponse, _args: DebugProtocol.DisconnectArguments): void {
this.debuggerExecutableBusy = false;

let process = spawn(this.launchArgs.pathBash, [`-c`, `${this.launchArgs.pathPkill} -KILL -P "${this.debuggerProcessParentId}"; ${this.launchArgs.pathPkill} -TERM -P "${this.proxyData["PROXYID"]}"`]);

process.on("error", (error) => {
this.sendEvent(new OutputEvent(`${error}`, 'console'));
});

process.stderr.on("data", (data) => {
this.sendEvent(new OutputEvent(`${data}`, 'console'));
});

process.stdout.on("data", (data) => {
this.sendEvent(new OutputEvent(`${data}`, 'console'));
});
spawnBashScript(`${this.launchArgs.pathPkill} -KILL -P "${this.debuggerProcessParentId}"; ${this.launchArgs.pathPkill} -TERM -P "${this.proxyData["PROXYID"]}"`,
this.launchArgs.pathBash,
data=> this.sendEvent(new OutputEvent(`${data}`, 'console')))

this.proxyProcess.on("exit", () => {
this.debuggerExecutableClosing = true;
Expand Down Expand Up @@ -138,7 +129,7 @@ export class BashDebugSession extends LoggingDebugSession {
const fifo_path = "/tmp/vscode-bash-debug-fifo-" + (Math.floor(Math.random() * 10000) + 10000);

// http://tldp.org/LDP/abs/html/io-redirection.html
this.proxyProcess = spawn(args.pathBash, ["-c",
this.proxyProcess = spawnBashScript(
`function cleanup()
{
exit_code=$?
Expand All @@ -156,20 +147,22 @@ export class BashDebugSession extends LoggingDebugSession {
"${args.pathCat}" "${fifo_path}" &
exec 4>"${fifo_path}"
"${args.pathCat}" >"${fifo_path}_in"`
.replace("\r", "")
], { stdio: ["pipe", "pipe", "pipe"] });
.replace("\r", ""),
this.launchArgs.pathBash)

this.proxyProcess.stdin.write(`examine Debug environment: bash_ver=$BASH_VERSION, bashdb_ver=$_Dbg_release, program=$0, args=$*\nprint "$PPID"\nhandle INT stop\nprint '${BashDebugSession.END_MARKER}'\n`);

const currentShell = (process.platform === "win32") ? getWSLLauncherPath(true) : args.pathBash;
const optionalBashPathArgument = (currentShell !== args.pathBash) ? args.pathBash : "";
const termArgs: DebugProtocol.RunInTerminalRequestArguments = {
kind: this.launchArgs.terminalKind,
title: "Bash Debug Console",
cwd: ".",
args: [`bash`, `-c` ,
args: [currentShell, optionalBashPathArgument, `-c`,
`cd "${args.cwdEffective}"; while [[ ! -p "${fifo_path}" ]]; do sleep 0.25; done
"${args.pathBashdb}" --quiet --tty "${fifo_path}" --tty_in "${fifo_path}_in" --library "${args.pathBashdbLib}" -- "${args.programEffective}" ${args.args.map(e => `"` + e.replace(`"`,`\\\"`) + `"`).join(` `)}`
.replace("\r", "").replace("\n", "; ")
],
].filter(arg => arg !== ""),
};

this.runInTerminalRequest(termArgs, 10000, (response) =>{
Expand Down Expand Up @@ -560,7 +553,10 @@ export class BashDebugSession extends LoggingDebugSession {

protected pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): void {
if (args.threadId === BashDebugSession.THREAD_ID) {
spawn("bash", ["-c", `${this.launchArgs.pathPkill} -INT -P ${this.debuggerProcessParentId} -f bashdb`]).on("exit", () => this.sendResponse(response));
spawnBashScript(`${this.launchArgs.pathPkill} -INT -P ${this.debuggerProcessParentId} -f bashdb`,
this.launchArgs.pathBash,
data=> this.sendEvent(new OutputEvent(`${data}`, 'console')))
.on("exit", () => this.sendResponse(response));
return;
}

Expand Down Expand Up @@ -600,7 +596,11 @@ export class BashDebugSession extends LoggingDebugSession {
this.sendEvent(new OutputEvent(`Sending StoppedEvent`, 'telemetry'));
this.sendEvent(new StoppedEvent("break", BashDebugSession.THREAD_ID));
}
else if (line.indexOf("terminated") > 0) {
else if (line.indexOf("Program received signal SIGINT") === 0) {
this.sendEvent(new OutputEvent(`Sending StoppedEvent`, 'telemetry'));
this.sendEvent(new StoppedEvent("break", BashDebugSession.THREAD_ID));
}
else if (line.indexOf("Debugged program terminated") === 0) {
this.proxyProcess.stdin.write(`\nq\n`);
this.sendEvent(new OutputEvent(`Sending TerminatedEvent`, 'telemetry'));
this.sendEvent(new TerminatedEvent());
Expand Down
11 changes: 5 additions & 6 deletions src/bashRuntime.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { spawnSync } from 'child_process';
import { spawnBashScriptSync } from './spawnBash';

enum validatePathResult {
success = 0,
Expand Down Expand Up @@ -55,20 +55,18 @@ enum validatePathResult {
* // => validatePathResult.timeout
*/
function _validatePath(cwd: string,
pathBash: string, pathBashdb: string, pathCat: string, pathMkfifo: string, pathPkill: string, spawnTimeout: number = 1000): validatePathResult {
pathBash: string, pathBashdb: string, pathCat: string, pathMkfifo: string, pathPkill: string, spawnTimeout: number = 5000): validatePathResult {

const vpr = validatePathResult;

const argv = ["-c",
const proc = spawnBashScriptSync(
(pathBashdb.indexOf("bashdb_dir") > 0) ? `chmod +x "${pathBashdb}" || exit ${vpr.cannotChmod};` : `` +
`type "${pathBashdb}" || exit ${vpr.notFoundBashdb};` +
`type "${pathCat}" || exit ${vpr.notFoundCat};` +
`type "${pathMkfifo}" || exit ${vpr.notFoundMkfifo};` +
`type "${pathPkill}" || exit ${vpr.notFoundPkill};` +
`test -d "${cwd}" || exit ${vpr.notExistCwd};` +
""
]
const proc = spawnSync(pathBash, argv, { timeout: spawnTimeout });
"", pathBash, spawnTimeout);

if (proc.error !== undefined) {
// @ts-ignore Property 'code' does not exist on type 'Error'.
Expand Down Expand Up @@ -140,3 +138,4 @@ export function validatePath(cwd: string,
return "Error: BUG: reached to unreachable code " +
"while validating environment. " + askReport;
}

9 changes: 2 additions & 7 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,7 @@ class BashConfigurationProvider implements vscode.DebugConfigurationProvider {
if (!config.args) { config.args = [] }
if (!config.cwd) { config.cwd = folder.uri.fsPath }
if (!config.pathBash) {
if (process.platform === "win32") {
config.pathBash = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432') ?
join("C:", "Windows", "sysnative", "bash.exe") : join("C:", "Windows", "System32", "bash.exe");
}
else {
config.pathBash = "bash"
}
config.pathBash = "bash"
}
if (!config.pathBashdb) {
if (process.platform === "win32") {
Expand All @@ -112,6 +106,7 @@ class BashConfigurationProvider implements vscode.DebugConfigurationProvider {
config.pathBashdbLib = normalize(join(__dirname, "..", "bashdb_dir"));
}
}

if (!config.pathCat) { config.pathCat = "cat" }
if (!config.pathMkfifo) { config.pathMkfifo = "mkfifo" }
if (!config.pathPkill) { config.pathPkill = "pkill" }
Expand Down
13 changes: 13 additions & 0 deletions src/handlePath.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { join } from 'path';

/**
* @example <caption>Undefined path stays undefined.</caption>
* // can't execute jsdoctest
Expand Down Expand Up @@ -64,6 +66,17 @@ export function reverseWSLPath(wslPath: string): string {
return wslPath.split("/").join("\\");
}

export function getWSLLauncherPath(useInShell: boolean): string {

if (useInShell) {
return "wsl.exe";
}

return process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432') ?
join("C:", "Windows", "sysnative", "wsl.exe") :
join("C:", "Windows", "System32", "wsl.exe");
}

/**
* @example <caption>Escape whitespace for setting bashdb arguments with spaces</caption>
* escapeCharactersInBashdbArg("/pa th/to/script.sh");
Expand Down
32 changes: 32 additions & 0 deletions src/spawnBash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ChildProcess, SpawnSyncReturns, spawnSync, spawn } from 'child_process';
import { getWSLLauncherPath } from './handlePath';

export function spawnBashScript(scriptCode: string, pathBash: string, outputHandler?: (output: string) => void): ChildProcess{
const currentShell = (process.platform === "win32") ? getWSLLauncherPath(false) : pathBash;
const optionalBashPathArgument = (currentShell !== pathBash) ? pathBash : "";

let spawnedProcess = spawn(currentShell, [optionalBashPathArgument, "-c", scriptCode].filter(arg => arg !== ""), { stdio: ["pipe", "pipe", "pipe"], shell: false});

if (outputHandler) {
spawnedProcess.on("error", (error) => {
outputHandler(`${error}`);
});

spawnedProcess.stderr.on("data", (data) => {
outputHandler(`${data}`);
});

spawnedProcess.stdout.on("data", (data) => {
outputHandler(`${data}`);
});
}

return spawnedProcess;
}

export function spawnBashScriptSync(scriptCode: string, pathBash: string, spawnTimeout: number): SpawnSyncReturns<Buffer>{
const currentShell = (process.platform === "win32") ? getWSLLauncherPath(false) : pathBash;
const optionalBashPathArgument = (currentShell !== pathBash) ? pathBash : "";

return spawnSync(currentShell, [optionalBashPathArgument, "-c", scriptCode].filter(arg => arg !== ""), { timeout: spawnTimeout, shell: false });
}

0 comments on commit 3bbc0e8

Please sign in to comment.