From 26de0cd07326147cc8a27728a50c1cf740a33920 Mon Sep 17 00:00:00 2001 From: Chuck Ries Date: Tue, 29 Mar 2016 12:56:52 -0700 Subject: [PATCH 1/5] Implement debug proxy to display error message before install completes This adds a debug proxy that acts as the debug adapter program before the coreclr-debug are fully downloaded. If the user tries to start debugging before download is finished, the proxy displays a sane error message. Once the download is complete, we rewrite the manifest to no longer call the proxy. During the VS Code session that downloads the components, the manifest is rewritten but not reloaded. In this case the proxy spawns the real debugger as a child process and proxies its stdin/stdout. Once vs code is restarted the new manifest is loaded and the proxy is no longer called. Also adds an empty command that can be run to force activation of the C# extension, which will kick off debugger acquisition. --- coreclr-debug/.gitignore | 3 +- package.json | 16 +- .../main.ts} | 523 +++++++++--------- src/coreclr-debug/proxy.ts | 109 ++++ src/coreclr-debug/util.ts | 117 ++++ src/omnisharpMain.ts | 8 +- 6 files changed, 506 insertions(+), 270 deletions(-) rename src/{coreclr-debug.ts => coreclr-debug/main.ts} (71%) create mode 100644 src/coreclr-debug/proxy.ts create mode 100644 src/coreclr-debug/util.ts diff --git a/coreclr-debug/.gitignore b/coreclr-debug/.gitignore index 0541f8ebe1..ce4b985431 100644 --- a/coreclr-debug/.gitignore +++ b/coreclr-debug/.gitignore @@ -2,4 +2,5 @@ bin obj project.lock.json debugAdapters -install.log \ No newline at end of file +install.log +extension.log \ No newline at end of file diff --git a/package.json b/package.json index cdfefd071a..0cc0353d1e 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "github-releases": "^0.3.0", "run-in-terminal": "*", "semver": "*", + "vscode-debugprotocol": "^1.6.1", "vscode-extension-telemetry": "0.0.4", "tmp": "0.0.28", "open": "*" @@ -50,6 +51,7 @@ "onCommand:o.execute-last-command", "onCommand:dotnet.restore", "onCommand:csharp.addTasksJson", + "onCommand:csharp.installDebugger", "workspaceContains:project.json" ], "contributes": { @@ -105,6 +107,11 @@ "command": "dotnet.restore", "title": "Restore Packages", "category": "dotnet" + }, + { + "command": "csharp.installDebugger", + "title": "Install .NET Core Debugger", + "category": "Debugger" } ], "keybindings": [ @@ -154,10 +161,9 @@ "csharp" ] }, - "program": "./coreclr-debug/debugAdapters/OpenDebugAD7", - "windows": { - "program": "./coreclr-debug/debugAdapters/OpenDebugAD7.exe" - }, + "runtime": "node", + "runtimeArgs": [], + "program": "./out/coreclr-debug/proxy.js", "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "configurationAttributes": { "launch": { @@ -380,4 +386,4 @@ } ] } -} +} \ No newline at end of file diff --git a/src/coreclr-debug.ts b/src/coreclr-debug/main.ts similarity index 71% rename from src/coreclr-debug.ts rename to src/coreclr-debug/main.ts index df33d9b86a..524672ac3c 100644 --- a/src/coreclr-debug.ts +++ b/src/coreclr-debug/main.ts @@ -1,263 +1,262 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import * as vscode from 'vscode'; -import * as child_process from 'child_process'; -import * as fs from 'fs'; -import * as path from 'path'; -import TelemetryReporter from 'vscode-extension-telemetry'; - -let _coreClrDebugDir: string; -let _debugAdapterDir: string; -let _channel: vscode.OutputChannel; -let _installLog: NodeJS.WritableStream; -let _reporter: TelemetryReporter; // Telemetry reporter -const _completionFileName: string = 'install.complete'; - -export function installCoreClrDebug(context: vscode.ExtensionContext, reporter: TelemetryReporter) { - _coreClrDebugDir = path.join(context.extensionPath, 'coreclr-debug'); - _debugAdapterDir = path.join(_coreClrDebugDir, 'debugAdapters'); - - if (existsSync(path.join(_debugAdapterDir, _completionFileName))) { - console.log('.NET Core Debugger tools already installed'); - return; - } - - if (!isOnPath('dotnet')) { - const getDotNetMessage = "Get .NET CLI tools"; - vscode.window.showErrorMessage("The .NET CLI tools cannot be located. .NET Core debugging will not be enabled. Make sure .NET CLI tools are installed and are on the path.", - getDotNetMessage).then(function (value) { - if (value === getDotNetMessage) { - var open = require('open'); - open("http://dotnet.github.io/getting-started/"); - } - }); - - return; - } - - _reporter = reporter; - _channel = vscode.window.createOutputChannel('coreclr-debug'); - - // Create our log file and override _channel.append to also outpu to the log - _installLog = fs.createWriteStream(path.join(_coreClrDebugDir, 'install.log')); - (function() { - let proxied = _channel.append; - _channel.append = function(val: string) { - _installLog.write(val); - proxied.apply(this, arguments); - }; - })(); - - vscode.window.setStatusBarMessage("Downloading and configuring the .NET Core Debugger..."); - - let installStage = 'dotnet restore'; - let installError = ''; - - spawnChildProcess('dotnet', ['--verbose', 'restore', '--configfile', 'NuGet.config'], _channel, _coreClrDebugDir) - .then(function() { - installStage = "dotnet publish"; - return spawnChildProcess('dotnet', ['--verbose', 'publish', '-o', _debugAdapterDir], _channel, _coreClrDebugDir); - }).then(function() { - installStage = "ensureAd7"; - return ensureAd7EngineExists(_channel, _debugAdapterDir); - }).then(function() { - installStage = "additionalTasks"; - let promises: Promise[] = []; - - promises.push(renameDummyEntrypoint()); - promises.push(removeLibCoreClrTraceProvider()); - - return Promise.all(promises); - }).then(function() { - installStage = "writeCompletionFile"; - return writeCompletionFile(); - }).then(function() { - installStage = "completeSuccess"; - vscode.window.setStatusBarMessage('Successfully installed .NET Core Debugger.'); - }) - .catch(function(error) { - const viewLogMessage = "View Log"; - vscode.window.showErrorMessage('Error while installing .NET Core Debugger.', viewLogMessage).then(function (value) { - if (value === viewLogMessage) { - _channel.show(vscode.ViewColumn.Three); - } - }) - - installError = error.toString(); - console.log(error); - }).then(function() { - // log telemetry - logTelemetry('Acquisition', {installStage: installStage, installError: installError}); - }); -} - -function logTelemetry(eventName: string, properties?: {[prop: string]: string}) { - if (_reporter) - { - _reporter.sendTelemetryEvent('coreclr-debug/' + eventName, properties); - } -} - -function writeCompletionFile() : Promise { - return new Promise(function(resolve, reject) { - fs.writeFile(path.join(_debugAdapterDir, _completionFileName), '', function(err) { - if (err) { - reject(err.code); - } - else { - resolve(); - } - }); - }); -} - -function renameDummyEntrypoint() : Promise { - let src = path.join(_debugAdapterDir, 'dummy'); - let dest = path.join(_debugAdapterDir, 'OpenDebugAD7'); - - src += getPlatformExeExtension(); - dest += getPlatformExeExtension(); - - const promise = new Promise(function(resolve, reject) { - fs.rename(src, dest, function(err) { - if (err) { - reject(err.code); - } else { - resolve(); - } - }); - }); - - return promise; -} - -function removeLibCoreClrTraceProvider() : Promise -{ - const filePath = path.join(_debugAdapterDir, 'libcoreclrtraceptprovider' + getPlatformLibExtension()); - - if (!existsSync(filePath)) { - return Promise.resolve(); - } else { - return new Promise(function(resolve, reject) { - fs.unlink(filePath, function(err) { - if (err) { - reject(err.code); - } else { - _channel.appendLine('Succesfully deleted ' + filePath); - resolve(); - } - }); - }); - } -} - -function existsSync(path: string) : boolean { - try { - fs.accessSync(path, fs.F_OK); - return true; - } catch (err) { - if (err.code === 'ENOENT') { - return false; - } else { - throw Error(err.code); - } - } -} - -function getPlatformExeExtension() : string { - if (process.platform === 'win32') { - return '.exe'; - } - - return ''; -} - -function getPlatformLibExtension() : string { - switch (process.platform) { - case 'win32': - return '.dll'; - case 'darwin': - return '.dylib'; - case 'linux': - return '.so'; - default: - throw Error('Unsupported platform ' + process.platform); - } -} - -// Determines if the specified command is in one of the directories in the PATH environment variable. -function isOnPath(command : string) : boolean { - let pathValue = process.env['PATH']; - if (!pathValue) { - return false; - } - let fileName = command; - let seperatorChar = ':'; - if (process.platform == 'win32') { - // on Windows, add a '.exe', and the path is semi-colon seperatode - fileName = fileName + ".exe"; - seperatorChar = ';'; - } - - let pathSegments: string[] = pathValue.split(seperatorChar); - for (let segment of pathSegments) { - if (segment.length === 0 || !path.isAbsolute(segment)) { - continue; - } - - const segmentPath = path.join(segment, fileName); - if (existsSync(segmentPath)) { - return true; - } - } - - return false; -} - -function ensureAd7EngineExists(channel: vscode.OutputChannel, outputDirectory: string) : Promise { - let filePath = path.join(outputDirectory, "coreclr.ad7Engine.json"); - return new Promise((resolve, reject) => { - fs.exists(filePath, (exists) => { - if (exists) { - return resolve(); - } else { - channel.appendLine(`${filePath} does not exist.`); - channel.appendLine(''); - // NOTE: The minimum build number is actually less than 1584, but this is the minimum - // build that I have tested. - channel.appendLine("Error: The .NET CLI did not correctly restore debugger files. Ensure that you have .NET CLI version 1.0.0 build #001584 or newer. You can check your .NET CLI version using 'dotnet --version'."); - return reject("The .NET CLI did not correctly restore debugger files."); - } - }); - }); -} - -function spawnChildProcess(process: string, args: string[], channel: vscode.OutputChannel, workingDirectory: string) : Promise { - const promise = new Promise(function(resolve, reject) { - const child = child_process.spawn(process, args, {cwd: workingDirectory}); - - child.stdout.on('data', (data) => { - channel.append(`${data}`); - }); - - child.stderr.on('data', (data) => { - channel.appendLine(`Error: ${data}`); - }); - - child.on('close', (code: number) => { - if (code != 0) { - channel.appendLine(`${process} exited with error code ${code}`); - reject(new Error(code.toString())); - } - else { - resolve(); - } - }); - }); - - return promise; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as vscode from 'vscode'; +import * as child_process from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as util from './util'; +import TelemetryReporter from 'vscode-extension-telemetry'; + +let _channel: vscode.OutputChannel; +let _installLog: NodeJS.WritableStream; +let _reporter: TelemetryReporter; // Telemetry reporter + +export function activate(context: vscode.ExtensionContext, reporter: TelemetryReporter) { + util.setExtensionDir(context.extensionPath); + + if (util.installCompleteExists()) { + console.log('.NET Core Debugger tools already installed'); + return; + } + + if (!isOnPath('dotnet')) { + const getDotNetMessage = "Get .NET CLI tools"; + vscode.window.showErrorMessage("The .NET CLI tools cannot be located. .NET Core debugging will not be enabled. Make sure .NET CLI tools are installed and are on the path.", + getDotNetMessage).then(function (value) { + if (value === getDotNetMessage) { + let open = require('open'); + open("http://dotnet.github.io/getting-started/"); + } + }); + + return; + } + + _reporter = reporter; + _channel = vscode.window.createOutputChannel('coreclr-debug'); + + // Create our log file and override _channel.append to also output to the log + _installLog = fs.createWriteStream(util.installLogPath()); + (function() { + let proxied = _channel.append; + _channel.append = function(val: string) { + _installLog.write(val); + proxied.apply(this, arguments); + }; + })(); + + let statusBarMessage = vscode.window.setStatusBarMessage("Downloading and configuring the .NET Core Debugger..."); + + let installStage = 'installBegin'; + let installError = ''; + + writeInstallBeginFile().then(function() { + installStage = 'dotnetRestore' + return spawnChildProcess('dotnet', ['--verbose', 'restore', '--configfile', 'NuGet.config'], _channel, util.coreClrDebugDir()) + }).then(function() { + installStage = "dotnetPublish"; + return spawnChildProcess('dotnet', ['--verbose', 'publish', '-o', util.debugAdapterDir()], _channel, util.coreClrDebugDir()); + }).then(function() { + installStage = "ensureAd7"; + return ensureAd7EngineExists(_channel, util.debugAdapterDir()); + }).then(function() { + installStage = "additionalTasks"; + let promises: Promise[] = []; + + promises.push(renameDummyEntrypoint()); + promises.push(removeLibCoreClrTraceProvider()); + + return Promise.all(promises); + }).then(function() { + installStage = "rewriteManifest"; + rewriteManifest(); + installStage = "writeCompletionFile"; + return writeCompletionFile(); + }).then(function() { + installStage = "completeSuccess"; + vscode.window.setStatusBarMessage('Successfully installed .NET Core Debugger.'); + }) + .catch(function(error) { + const viewLogMessage = "View Log"; + vscode.window.showErrorMessage('Error while installing .NET Core Debugger.', viewLogMessage).then(function (value) { + if (value === viewLogMessage) { + _channel.show(vscode.ViewColumn.Three); + } + }); + statusBarMessage.dispose(); + + installError = error.toString(); + console.log(error); + + + }).then(function() { + // log telemetry and delete install begin file + logTelemetry('Acquisition', {installStage: installStage, installError: installError}); + + try { + deleteInstallBeginFile(); + } catch (err) { + // if this throws there's really nothing we can do + } + }); +} + +function logTelemetry(eventName: string, properties?: {[prop: string]: string}) { + if (_reporter) + { + _reporter.sendTelemetryEvent('coreclr-debug/' + eventName, properties); + } +} + +function rewriteManifest() : void { + const manifestPath = path.join(util.extensionDir(), 'package.json'); + let manifestString = fs.readFileSync(manifestPath, 'utf8'); + let manifestObject = JSON.parse(manifestString); + manifestObject.contributes.debuggers[0].runtime = ''; + manifestObject.contributes.debuggers[0].program = './coreclr-debug/debugAdapters/OpenDebugAD7' + util.getPlatformExeExtension(); + manifestString = JSON.stringify(manifestObject, null, 2); + fs.writeFileSync(manifestPath, manifestString); +} + +function writeInstallBeginFile() : Promise { + return writeEmptyFile(util.installBeginFilePath()); +} + +function deleteInstallBeginFile() { + if (util.existsSync(util.installBeginFilePath())) { + fs.unlinkSync(util.installBeginFilePath()); + } +} + +function writeCompletionFile() : Promise { + return writeEmptyFile(util.installCompleteFilePath()); +} + +function writeEmptyFile(path: string) : Promise { + return new Promise(function(resolve, reject) { + fs.writeFile(path, '', function(err) { + if (err) { + reject(err.code); + } else { + resolve(); + } + }); + }); +} + +function renameDummyEntrypoint() : Promise { + let src = path.join(util.debugAdapterDir(), 'dummy'); + let dest = path.join(util.debugAdapterDir(), 'OpenDebugAD7'); + + src += util.getPlatformExeExtension(); + dest += util.getPlatformExeExtension(); + + const promise = new Promise(function(resolve, reject) { + fs.rename(src, dest, function(err) { + if (err) { + reject(err.code); + } else { + resolve(); + } + }); + }); + + return promise; +} + +function removeLibCoreClrTraceProvider() : Promise +{ + const filePath = path.join(util.debugAdapterDir(), 'libcoreclrtraceptprovider' + util.getPlatformLibExtension()); + + if (!util.existsSync(filePath)) { + return Promise.resolve(); + } else { + return new Promise(function(resolve, reject) { + fs.unlink(filePath, function(err) { + if (err) { + reject(err.code); + } else { + _channel.appendLine('Succesfully deleted ' + filePath); + resolve(); + } + }); + }); + } +} + +// Determines if the specified command is in one of the directories in the PATH environment variable. +function isOnPath(command : string) : boolean { + let pathValue = process.env['PATH']; + if (!pathValue) { + return false; + } + let fileName = command; + let seperatorChar = ':'; + if (process.platform == 'win32') { + // on Windows, add a '.exe', and the path is semi-colon seperatode + fileName = fileName + ".exe"; + seperatorChar = ';'; + } + + let pathSegments: string[] = pathValue.split(seperatorChar); + for (let segment of pathSegments) { + if (segment.length === 0 || !path.isAbsolute(segment)) { + continue; + } + + const segmentPath = path.join(segment, fileName); + if (util.existsSync(segmentPath)) { + return true; + } + } + + return false; +} + +function ensureAd7EngineExists(channel: vscode.OutputChannel, outputDirectory: string) : Promise { + let filePath = path.join(outputDirectory, "coreclr.ad7Engine.json"); + return new Promise((resolve, reject) => { + fs.exists(filePath, (exists) => { + if (exists) { + return resolve(); + } else { + channel.appendLine(`${filePath} does not exist.`); + channel.appendLine(''); + // NOTE: The minimum build number is actually less than 1584, but this is the minimum + // build that I have tested. + channel.appendLine("Error: The .NET CLI did not correctly restore debugger files. Ensure that you have .NET CLI version 1.0.0 build #001584 or newer. You can check your .NET CLI version using 'dotnet --version'."); + return reject("The .NET CLI did not correctly restore debugger files."); + } + }); + }); +} + +function spawnChildProcess(process: string, args: string[], channel: vscode.OutputChannel, workingDirectory: string) : Promise { + const promise = new Promise(function(resolve, reject) { + const child = child_process.spawn(process, args, {cwd: workingDirectory}); + + child.stdout.on('data', (data) => { + channel.append(`${data}`); + }); + + child.stderr.on('data', (data) => { + channel.appendLine(`Error: ${data}`); + }); + + child.on('close', (code: number) => { + if (code != 0) { + channel.appendLine(`${process} exited with error code ${code}`); + reject(new Error(code.toString())); + } + else { + resolve(); + } + }); + }); + + return promise; } \ No newline at end of file diff --git a/src/coreclr-debug/proxy.ts b/src/coreclr-debug/proxy.ts new file mode 100644 index 0000000000..7426480fcb --- /dev/null +++ b/src/coreclr-debug/proxy.ts @@ -0,0 +1,109 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as path from 'path'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import * as child_process from 'child_process'; +import * as util from './util'; + +class ProxyErrorResponse implements DebugProtocol.ErrorResponse { + public body: { error?: DebugProtocol.Message }; + public request_seq: number; + public success: boolean; + public command: string; + public seq: number; + public type: string; + + constructor(public message: string) { + this.request_seq = 1; + this.seq = 1; + this.type = "response"; + this.success = false; + this.command = "initialize"; + } +} + +function serializeProtocolEvent(message: DebugProtocol.ProtocolMessage): string { + const payload: string = JSON.stringify(message); + const finalPayload: string = `Content-Length: ${payload.length}\r\n\r\n${payload}`; + return finalPayload; +} + +// The default extension manifest calls this proxy as the debugger program +// When installation of the debugger components finishes, the extension manifest is rewritten so that this proxy is no longer called +// If the debugger components have not finished downloading, the proxy displays an error message to the user +// If the debugger components have finished downloading, the manifest has been rewritten but has not been reloaded. +// This proxy will still be called and launch OpenDebugAD7 as a child process. +// During subsequent code sessions, the rewritten manifest will be loaded and this proxy will no longer be called. +function proxy() { + util.setExtensionDir(path.resolve(__dirname, '../../')); + + if (!util.installCompleteExists()) { + if (util.existsSync(util.installBeginFilePath())) { + process.stdout.write(serializeProtocolEvent(new ProxyErrorResponse('The .NET Core Debugger has not finished installing. See Status Bar for details.'))); + } else { + process.stdout.write(serializeProtocolEvent(new ProxyErrorResponse('Run \'Debugger: Install .NET Core Debugger\' command or open a .NET project directory to download the .NET Core Debugger'))); + } + } + else + { + new Promise(function(resolve, reject) { + let processPath = path.join(util.debugAdapterDir(), "OpenDebugAD7" + util.getPlatformExeExtension()); + let args = process.argv.slice(2); + + // do not explicitly set a current working dir + // this seems to match what code does when OpenDebugAD7 is launched directly from the manifest + const child = child_process.spawn(processPath, args); + + // If we don't exit cleanly from the child process, log the error. + child.on('close', function(code: number) { + if (code !== 0) { + reject(new Error(code.toString())); + } else { + resolve(); + } + }); + + process.stdin.setEncoding('utf8'); + + child.on('error', function(data) { + util.logToFile(`Child error: ${data}`); + }); + + process.on('SIGTERM', function() { + child.kill(); + process.exit(0); + }); + + process.on('SIGHUP', function() { + child.kill(); + process.exit(0); + }); + + process.stdin.on('error', function(error) { + util.logToFile(`process.stdin error: ${error}`); + }); + + process.stdout.on('error', function(error) { + util.logToFile(`process.stdout error: ${error}`); + }); + + child.stdout.on('data', function(data) { + process.stdout.write(data); + }); + + process.stdin.on('data', function(data) { + child.stdin.write(data); + }); + + process.stdin.resume(); + }).catch(function(err) { + util.logToFile(`Promise failed: ${err}`); + }); + } +} + +proxy(); \ No newline at end of file diff --git a/src/coreclr-debug/util.ts b/src/coreclr-debug/util.ts new file mode 100644 index 0000000000..2c6a95138a --- /dev/null +++ b/src/coreclr-debug/util.ts @@ -0,0 +1,117 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as path from 'path'; +import * as fs from 'fs'; +import * as os from 'os'; + +let _extensionDir: string = ''; +let _coreClrDebugDir: string = ''; +let _debugAdapterDir: string = ''; +let _installLogPath: string = ''; +let _installBeginFilePath: string = ''; +let _installCompleteFilePath: string = ''; + +export function setExtensionDir(extensionDir: string) { + // ensure that this path actually exists and looks like the root of the extension + if (!existsSync(path.join(extensionDir, 'package.json'))) { + throw new Error(`Cannot set extension path to ${extensionDir}`); + } + _extensionDir = extensionDir; + _coreClrDebugDir = path.join(_extensionDir, 'coreclr-debug'); + _debugAdapterDir = path.join(_coreClrDebugDir, 'debugAdapters'); + _installLogPath = path.join(_coreClrDebugDir, 'install.log'); + _installBeginFilePath = path.join(_coreClrDebugDir, 'install.begin'); + _installCompleteFilePath = path.join(_debugAdapterDir, 'install.complete'); +} + +export function extensionDir(): string { + if (_extensionDir === '') + { + throw new Error('Failed to set extension directory'); + } + return _extensionDir; +} + +export function coreClrDebugDir(): string { + if (_coreClrDebugDir === '') { + throw new Error('Failed to set coreclrdebug directory'); + } + return _coreClrDebugDir; +} + +export function debugAdapterDir(): string { + if (_debugAdapterDir === '') { + throw new Error('Failed to set debugadpter directory'); + } + return _debugAdapterDir; +} + +export function installLogPath(): string { + if (_installLogPath === '') { + throw new Error('Failed to set install log path'); + } + return _installLogPath; +} + +export function installBeginFilePath(): string { + if (_installBeginFilePath === '') { + throw new Error('Failed to set install begin file path'); + } + return _installBeginFilePath; +} + +export function installCompleteFilePath(): string { + if (_installCompleteFilePath === '') + { + throw new Error('Failed to set install complete file path'); + } + return _installCompleteFilePath; +} + +export function installCompleteExists() : boolean { + return existsSync(installCompleteFilePath()); +} + +export function existsSync(path: string) : boolean { + try { + fs.accessSync(path, fs.F_OK); + return true; + } catch (err) { + if (err.code === 'ENOENT') { + return false; + } else { + throw Error(err.code); + } + } +} + +export function getPlatformExeExtension() : string { + if (process.platform === 'win32') { + return '.exe'; + } + + return ''; +} + +export function getPlatformLibExtension() : string { + switch (process.platform) { + case 'win32': + return '.dll'; + case 'darwin': + return '.dylib'; + case 'linux': + return '.so'; + default: + throw Error('Unsupported platform ' + process.platform); + } +} + +/** Used for diagnostics only */ +export function logToFile(message: string): void { + let logFolder = path.resolve(coreClrDebugDir(), "extension.log"); + fs.writeFileSync(logFolder, `${message}${os.EOL}`, { flag: 'a' }); +} diff --git a/src/omnisharpMain.ts b/src/omnisharpMain.ts index d31e1d08ca..73dd388636 100644 --- a/src/omnisharpMain.ts +++ b/src/omnisharpMain.ts @@ -22,7 +22,7 @@ import registerCommands from './features/commands'; import {StdioOmnisharpServer} from './omnisharpServer'; import forwardChanges from './features/changeForwarding'; import reportStatus from './features/omnisharpStatus'; -import {installCoreClrDebug} from './coreclr-debug'; +import * as coreclrdebug from './coreclr-debug/main'; import {addAssetsIfNecessary} from './assets'; import * as vscode from 'vscode'; import TelemetryReporter from 'vscode-extension-telemetry'; @@ -91,8 +91,12 @@ export function activate(context: vscode.ExtensionContext): any { server.stop(); })); + // register empty handler for csharp.installDebugger + // running the command activates the extension, which is all we need for installation to kickoff + disposables.push(vscode.commands.registerCommand('csharp.installDebugger', () => { })); + // install coreclr-debug - installCoreClrDebug(context, reporter); + coreclrdebug.activate(context, reporter); context.subscriptions.push(...disposables); } From 473d88e4934540b7c6566063989cac6bcdff078c Mon Sep 17 00:00:00 2001 From: Chuck Ries Date: Wed, 30 Mar 2016 10:42:29 -0700 Subject: [PATCH 2/5] Abstract the util functions into CoreClrDebugUtil class --- src/coreclr-debug/main.ts | 41 ++++----- src/coreclr-debug/proxy.ts | 10 +-- src/coreclr-debug/util.ts | 165 +++++++++++++++++++------------------ 3 files changed, 110 insertions(+), 106 deletions(-) diff --git a/src/coreclr-debug/main.ts b/src/coreclr-debug/main.ts index 524672ac3c..be10e19ce4 100644 --- a/src/coreclr-debug/main.ts +++ b/src/coreclr-debug/main.ts @@ -8,17 +8,18 @@ import * as vscode from 'vscode'; import * as child_process from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; -import * as util from './util'; import TelemetryReporter from 'vscode-extension-telemetry'; +import CoreClrDebugUtil from './util' let _channel: vscode.OutputChannel; let _installLog: NodeJS.WritableStream; let _reporter: TelemetryReporter; // Telemetry reporter +let _util: CoreClrDebugUtil; export function activate(context: vscode.ExtensionContext, reporter: TelemetryReporter) { - util.setExtensionDir(context.extensionPath); + _util = new CoreClrDebugUtil(context.extensionPath); - if (util.installCompleteExists()) { + if (CoreClrDebugUtil.existsSync(_util.installCompleteFilePath())) { console.log('.NET Core Debugger tools already installed'); return; } @@ -40,7 +41,7 @@ export function activate(context: vscode.ExtensionContext, reporter: TelemetryRe _channel = vscode.window.createOutputChannel('coreclr-debug'); // Create our log file and override _channel.append to also output to the log - _installLog = fs.createWriteStream(util.installLogPath()); + _installLog = fs.createWriteStream(_util.installLogPath()); (function() { let proxied = _channel.append; _channel.append = function(val: string) { @@ -56,13 +57,13 @@ export function activate(context: vscode.ExtensionContext, reporter: TelemetryRe writeInstallBeginFile().then(function() { installStage = 'dotnetRestore' - return spawnChildProcess('dotnet', ['--verbose', 'restore', '--configfile', 'NuGet.config'], _channel, util.coreClrDebugDir()) + return spawnChildProcess('dotnet', ['--verbose', 'restore', '--configfile', 'NuGet.config'], _channel, _util.coreClrDebugDir()) }).then(function() { installStage = "dotnetPublish"; - return spawnChildProcess('dotnet', ['--verbose', 'publish', '-o', util.debugAdapterDir()], _channel, util.coreClrDebugDir()); + return spawnChildProcess('dotnet', ['--verbose', 'publish', '-o', _util.debugAdapterDir()], _channel, _util.coreClrDebugDir()); }).then(function() { installStage = "ensureAd7"; - return ensureAd7EngineExists(_channel, util.debugAdapterDir()); + return ensureAd7EngineExists(_channel, _util.debugAdapterDir()); }).then(function() { installStage = "additionalTasks"; let promises: Promise[] = []; @@ -113,27 +114,27 @@ function logTelemetry(eventName: string, properties?: {[prop: string]: string}) } function rewriteManifest() : void { - const manifestPath = path.join(util.extensionDir(), 'package.json'); + const manifestPath = path.join(_util.extensionDir(), 'package.json'); let manifestString = fs.readFileSync(manifestPath, 'utf8'); let manifestObject = JSON.parse(manifestString); manifestObject.contributes.debuggers[0].runtime = ''; - manifestObject.contributes.debuggers[0].program = './coreclr-debug/debugAdapters/OpenDebugAD7' + util.getPlatformExeExtension(); + manifestObject.contributes.debuggers[0].program = './coreclr-debug/debugAdapters/OpenDebugAD7' + CoreClrDebugUtil.getPlatformExeExtension(); manifestString = JSON.stringify(manifestObject, null, 2); fs.writeFileSync(manifestPath, manifestString); } function writeInstallBeginFile() : Promise { - return writeEmptyFile(util.installBeginFilePath()); + return writeEmptyFile(_util.installBeginFilePath()); } function deleteInstallBeginFile() { - if (util.existsSync(util.installBeginFilePath())) { - fs.unlinkSync(util.installBeginFilePath()); + if (CoreClrDebugUtil.existsSync(_util.installBeginFilePath())) { + fs.unlinkSync(_util.installBeginFilePath()); } } function writeCompletionFile() : Promise { - return writeEmptyFile(util.installCompleteFilePath()); + return writeEmptyFile(_util.installCompleteFilePath()); } function writeEmptyFile(path: string) : Promise { @@ -149,11 +150,11 @@ function writeEmptyFile(path: string) : Promise { } function renameDummyEntrypoint() : Promise { - let src = path.join(util.debugAdapterDir(), 'dummy'); - let dest = path.join(util.debugAdapterDir(), 'OpenDebugAD7'); + let src = path.join(_util.debugAdapterDir(), 'dummy'); + let dest = path.join(_util.debugAdapterDir(), 'OpenDebugAD7'); - src += util.getPlatformExeExtension(); - dest += util.getPlatformExeExtension(); + src += CoreClrDebugUtil.getPlatformExeExtension(); + dest += CoreClrDebugUtil.getPlatformExeExtension(); const promise = new Promise(function(resolve, reject) { fs.rename(src, dest, function(err) { @@ -170,9 +171,9 @@ function renameDummyEntrypoint() : Promise { function removeLibCoreClrTraceProvider() : Promise { - const filePath = path.join(util.debugAdapterDir(), 'libcoreclrtraceptprovider' + util.getPlatformLibExtension()); + const filePath = path.join(_util.debugAdapterDir(), 'libcoreclrtraceptprovider' + CoreClrDebugUtil.getPlatformLibExtension()); - if (!util.existsSync(filePath)) { + if (!CoreClrDebugUtil.existsSync(filePath)) { return Promise.resolve(); } else { return new Promise(function(resolve, reject) { @@ -209,7 +210,7 @@ function isOnPath(command : string) : boolean { } const segmentPath = path.join(segment, fileName); - if (util.existsSync(segmentPath)) { + if (CoreClrDebugUtil.existsSync(segmentPath)) { return true; } } diff --git a/src/coreclr-debug/proxy.ts b/src/coreclr-debug/proxy.ts index 7426480fcb..92b8d76c04 100644 --- a/src/coreclr-debug/proxy.ts +++ b/src/coreclr-debug/proxy.ts @@ -7,7 +7,7 @@ import * as path from 'path'; import { DebugProtocol } from 'vscode-debugprotocol'; import * as child_process from 'child_process'; -import * as util from './util'; +import CoreClrDebugUtil from './util'; class ProxyErrorResponse implements DebugProtocol.ErrorResponse { public body: { error?: DebugProtocol.Message }; @@ -39,10 +39,10 @@ function serializeProtocolEvent(message: DebugProtocol.ProtocolMessage): string // This proxy will still be called and launch OpenDebugAD7 as a child process. // During subsequent code sessions, the rewritten manifest will be loaded and this proxy will no longer be called. function proxy() { - util.setExtensionDir(path.resolve(__dirname, '../../')); + let util = new CoreClrDebugUtil(path.resolve(__dirname, '../../')); - if (!util.installCompleteExists()) { - if (util.existsSync(util.installBeginFilePath())) { + if (!CoreClrDebugUtil.existsSync(util.installCompleteFilePath())) { + if (CoreClrDebugUtil.existsSync(util.installBeginFilePath())) { process.stdout.write(serializeProtocolEvent(new ProxyErrorResponse('The .NET Core Debugger has not finished installing. See Status Bar for details.'))); } else { process.stdout.write(serializeProtocolEvent(new ProxyErrorResponse('Run \'Debugger: Install .NET Core Debugger\' command or open a .NET project directory to download the .NET Core Debugger'))); @@ -51,7 +51,7 @@ function proxy() { else { new Promise(function(resolve, reject) { - let processPath = path.join(util.debugAdapterDir(), "OpenDebugAD7" + util.getPlatformExeExtension()); + let processPath = path.join(util.debugAdapterDir(), "OpenDebugAD7" + CoreClrDebugUtil.getPlatformExeExtension()); let args = process.argv.slice(2); // do not explicitly set a current working dir diff --git a/src/coreclr-debug/util.ts b/src/coreclr-debug/util.ts index 2c6a95138a..bae844c07f 100644 --- a/src/coreclr-debug/util.ts +++ b/src/coreclr-debug/util.ts @@ -15,103 +15,106 @@ let _installLogPath: string = ''; let _installBeginFilePath: string = ''; let _installCompleteFilePath: string = ''; -export function setExtensionDir(extensionDir: string) { - // ensure that this path actually exists and looks like the root of the extension - if (!existsSync(path.join(extensionDir, 'package.json'))) { - throw new Error(`Cannot set extension path to ${extensionDir}`); +export default class CoreClrDebugUtil +{ + private _extensionDir: string = ''; + private _coreClrDebugDir: string = ''; + private _debugAdapterDir: string = ''; + private _installLogPath: string = ''; + private _installBeginFilePath: string = ''; + private _installCompleteFilePath: string = ''; + + constructor(extensionDir) { + _extensionDir = extensionDir; + _coreClrDebugDir = path.join(_extensionDir, 'coreclr-debug'); + _debugAdapterDir = path.join(_coreClrDebugDir, 'debugAdapters'); + _installLogPath = path.join(_coreClrDebugDir, 'install.log'); + _installBeginFilePath = path.join(_coreClrDebugDir, 'install.begin'); + _installCompleteFilePath = path.join(_debugAdapterDir, 'install.complete'); } - _extensionDir = extensionDir; - _coreClrDebugDir = path.join(_extensionDir, 'coreclr-debug'); - _debugAdapterDir = path.join(_coreClrDebugDir, 'debugAdapters'); - _installLogPath = path.join(_coreClrDebugDir, 'install.log'); - _installBeginFilePath = path.join(_coreClrDebugDir, 'install.begin'); - _installCompleteFilePath = path.join(_debugAdapterDir, 'install.complete'); -} - -export function extensionDir(): string { - if (_extensionDir === '') - { - throw new Error('Failed to set extension directory'); + + extensionDir(): string { + if (_extensionDir === '') + { + throw new Error('Failed to set extension directory'); + } + return _extensionDir; } - return _extensionDir; -} -export function coreClrDebugDir(): string { - if (_coreClrDebugDir === '') { - throw new Error('Failed to set coreclrdebug directory'); + coreClrDebugDir(): string { + if (_coreClrDebugDir === '') { + throw new Error('Failed to set coreclrdebug directory'); + } + return _coreClrDebugDir; } - return _coreClrDebugDir; -} -export function debugAdapterDir(): string { - if (_debugAdapterDir === '') { - throw new Error('Failed to set debugadpter directory'); + debugAdapterDir(): string { + if (_debugAdapterDir === '') { + throw new Error('Failed to set debugadpter directory'); + } + return _debugAdapterDir; } - return _debugAdapterDir; -} -export function installLogPath(): string { - if (_installLogPath === '') { - throw new Error('Failed to set install log path'); + installLogPath(): string { + if (_installLogPath === '') { + throw new Error('Failed to set install log path'); + } + return _installLogPath; } - return _installLogPath; -} -export function installBeginFilePath(): string { - if (_installBeginFilePath === '') { - throw new Error('Failed to set install begin file path'); + installBeginFilePath(): string { + if (_installBeginFilePath === '') { + throw new Error('Failed to set install begin file path'); + } + return _installBeginFilePath; } - return _installBeginFilePath; -} -export function installCompleteFilePath(): string { - if (_installCompleteFilePath === '') - { - throw new Error('Failed to set install complete file path'); + installCompleteFilePath(): string { + if (_installCompleteFilePath === '') + { + throw new Error('Failed to set install complete file path'); + } + return _installCompleteFilePath; } - return _installCompleteFilePath; -} - -export function installCompleteExists() : boolean { - return existsSync(installCompleteFilePath()); -} - -export function existsSync(path: string) : boolean { - try { - fs.accessSync(path, fs.F_OK); - return true; - } catch (err) { - if (err.code === 'ENOENT') { - return false; - } else { - throw Error(err.code); + + static existsSync(path: string) : boolean { + try { + fs.accessSync(path, fs.F_OK); + return true; + } catch (err) { + if (err.code === 'ENOENT') { + return false; + } else { + throw Error(err.code); + } } } -} + + static getPlatformExeExtension() : string { + if (process.platform === 'win32') { + return '.exe'; + } -export function getPlatformExeExtension() : string { - if (process.platform === 'win32') { - return '.exe'; + return ''; } - return ''; -} - -export function getPlatformLibExtension() : string { - switch (process.platform) { - case 'win32': - return '.dll'; - case 'darwin': - return '.dylib'; - case 'linux': - return '.so'; - default: - throw Error('Unsupported platform ' + process.platform); + static getPlatformLibExtension() : string { + switch (process.platform) { + case 'win32': + return '.dll'; + case 'darwin': + return '.dylib'; + case 'linux': + return '.so'; + default: + throw Error('Unsupported platform ' + process.platform); + } + } + + + /** Used for diagnostics only */ + logToFile(message: string): void { + let logFolder = path.resolve(this.coreClrDebugDir(), "extension.log"); + fs.writeFileSync(logFolder, `${message}${os.EOL}`, { flag: 'a' }); } -} - -/** Used for diagnostics only */ -export function logToFile(message: string): void { - let logFolder = path.resolve(coreClrDebugDir(), "extension.log"); - fs.writeFileSync(logFolder, `${message}${os.EOL}`, { flag: 'a' }); } From 893a8b89567a92bf694d03a43d6952eda7aec47c Mon Sep 17 00:00:00 2001 From: Chuck Ries Date: Wed, 30 Mar 2016 11:14:43 -0700 Subject: [PATCH 3/5] Rename command to download the debugger --- package.json | 8 ++++---- src/omnisharpMain.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 0cc0353d1e..4529035a15 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "onCommand:o.execute-last-command", "onCommand:dotnet.restore", "onCommand:csharp.addTasksJson", - "onCommand:csharp.installDebugger", + "onCommand:csharp.downloadDebugger", "workspaceContains:project.json" ], "contributes": { @@ -109,9 +109,9 @@ "category": "dotnet" }, { - "command": "csharp.installDebugger", - "title": "Install .NET Core Debugger", - "category": "Debugger" + "command": "charp.downloadDebugger", + "title": "Download .NET Core Debugger", + "category": "Debug" } ], "keybindings": [ diff --git a/src/omnisharpMain.ts b/src/omnisharpMain.ts index 73dd388636..5f3e786567 100644 --- a/src/omnisharpMain.ts +++ b/src/omnisharpMain.ts @@ -93,7 +93,7 @@ export function activate(context: vscode.ExtensionContext): any { // register empty handler for csharp.installDebugger // running the command activates the extension, which is all we need for installation to kickoff - disposables.push(vscode.commands.registerCommand('csharp.installDebugger', () => { })); + disposables.push(vscode.commands.registerCommand('csharp.downloadDebugger', () => { })); // install coreclr-debug coreclrdebug.activate(context, reporter); From e0fd8855fbf2a9283267a6aa587275bb79608f99 Mon Sep 17 00:00:00 2001 From: Chuck Ries Date: Wed, 30 Mar 2016 12:49:24 -0700 Subject: [PATCH 4/5] respond to PR review feedback --- src/coreclr-debug/main.ts | 1 + src/coreclr-debug/proxy.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/coreclr-debug/main.ts b/src/coreclr-debug/main.ts index be10e19ce4..0abcd6fd88 100644 --- a/src/coreclr-debug/main.ts +++ b/src/coreclr-debug/main.ts @@ -79,6 +79,7 @@ export function activate(context: vscode.ExtensionContext, reporter: TelemetryRe return writeCompletionFile(); }).then(function() { installStage = "completeSuccess"; + statusBarMessage.dispose(); vscode.window.setStatusBarMessage('Successfully installed .NET Core Debugger.'); }) .catch(function(error) { diff --git a/src/coreclr-debug/proxy.ts b/src/coreclr-debug/proxy.ts index 92b8d76c04..d6a9c3bfd4 100644 --- a/src/coreclr-debug/proxy.ts +++ b/src/coreclr-debug/proxy.ts @@ -43,9 +43,9 @@ function proxy() { if (!CoreClrDebugUtil.existsSync(util.installCompleteFilePath())) { if (CoreClrDebugUtil.existsSync(util.installBeginFilePath())) { - process.stdout.write(serializeProtocolEvent(new ProxyErrorResponse('The .NET Core Debugger has not finished installing. See Status Bar for details.'))); + process.stdout.write(serializeProtocolEvent(new ProxyErrorResponse('The .NET Core Debugger is still being downloaded. See the Status Bar for more information.'))); } else { - process.stdout.write(serializeProtocolEvent(new ProxyErrorResponse('Run \'Debugger: Install .NET Core Debugger\' command or open a .NET project directory to download the .NET Core Debugger'))); + process.stdout.write(serializeProtocolEvent(new ProxyErrorResponse('Run \'Debug: Download .NET Core Debugger\' in the Command Palette or open a .NET project directory to download the .NET Core Debugger'))); } } else From e13c78ddc9ee4ca7885ec7061a7d50ec447a62fa Mon Sep 17 00:00:00 2001 From: Chuck Ries Date: Wed, 30 Mar 2016 14:58:23 -0700 Subject: [PATCH 5/5] Fix csharp.downloadDebugger command and remove addtasks.json command --- package.json | 3 +-- src/features/commands.ts | 6 +++++- src/omnisharpMain.ts | 6 +----- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 4529035a15..cb840c8ecb 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,6 @@ "onCommand:o.showOutput", "onCommand:o.execute-last-command", "onCommand:dotnet.restore", - "onCommand:csharp.addTasksJson", "onCommand:csharp.downloadDebugger", "workspaceContains:project.json" ], @@ -109,7 +108,7 @@ "category": "dotnet" }, { - "command": "charp.downloadDebugger", + "command": "csharp.downloadDebugger", "title": "Download .NET Core Debugger", "category": "Debug" } diff --git a/src/features/commands.ts b/src/features/commands.ts index d734afd16b..2cc72feb4b 100644 --- a/src/features/commands.ts +++ b/src/features/commands.ts @@ -23,8 +23,12 @@ export default function registerCommands(server: OmnisharpServer, extensionPath: let d5 = vscode.commands.registerCommand('o.execute-last-command', () => dnxExecuteLastCommand(server)); let d6 = vscode.commands.registerCommand('o.showOutput', () => server.getChannel().show(vscode.ViewColumn.Three)); let d7 = vscode.commands.registerCommand('dotnet.restore', () => dotnetRestore(server)); + + // register empty handler for csharp.installDebugger + // running the command activates the extension, which is all we need for installation to kickoff + let d8 = vscode.commands.registerCommand('csharp.downloadDebugger', () => { }); - return vscode.Disposable.from(d1, d2, d3, d4, d5, d6, d7); + return vscode.Disposable.from(d1, d2, d3, d4, d5, d6, d7, d8); } function pickProjectAndStart(server: OmnisharpServer) { diff --git a/src/omnisharpMain.ts b/src/omnisharpMain.ts index 5f3e786567..ec5e51e04b 100644 --- a/src/omnisharpMain.ts +++ b/src/omnisharpMain.ts @@ -90,11 +90,7 @@ export function activate(context: vscode.ExtensionContext): any { server.reportAndClearTelemetry(); server.stop(); })); - - // register empty handler for csharp.installDebugger - // running the command activates the extension, which is all we need for installation to kickoff - disposables.push(vscode.commands.registerCommand('csharp.downloadDebugger', () => { })); - + // install coreclr-debug coreclrdebug.activate(context, reporter);