From 5ef7d01eff6a1c2110aeeea71e0d74daf4647cb6 Mon Sep 17 00:00:00 2001 From: Chuck Ries Date: Fri, 4 Nov 2016 14:04:20 -0700 Subject: [PATCH 1/4] Reimplement debugger acquisition to use Package Manager 1. Remove all code related to calling dotnet restore/dotnet publish 2. Remove coreclr-debug directory 3. Add debugger runtime packages to package manifest 4. Add an install.Begin file to the package manager so we can detect that it is in progress 5. Rework the debugger proxy so that it understands the package manager 6. Add knowledge of the dotnet cli on the path to the debugger proxy TODO: 1. Lots and lots of testing 2. Offline package creation --- .gitignore | 3 +- coreclr-debug/.gitignore | 7 - coreclr-debug/NuGet.config | 10 -- coreclr-debug/dummy.cs | 13 -- gulpfile.js | 17 +-- package.json | 104 ++++++++++++++ src/common.ts | 36 +++-- src/coreclr-debug/activate.ts | 173 +++++------------------ src/coreclr-debug/install.ts | 250 ++-------------------------------- src/coreclr-debug/proxy.ts | 70 ++++++++-- src/coreclr-debug/util.ts | 168 ++++++++++------------- src/main.ts | 41 ++++-- 12 files changed, 342 insertions(+), 550 deletions(-) delete mode 100644 coreclr-debug/.gitignore delete mode 100644 coreclr-debug/NuGet.config delete mode 100644 coreclr-debug/dummy.cs diff --git a/.gitignore b/.gitignore index ea0c8f290..062afeb3b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,8 @@ bin node_modules out .omnisharp-*/ +.debugger -install.lock +install.* *.vsix diff --git a/coreclr-debug/.gitignore b/coreclr-debug/.gitignore deleted file mode 100644 index 60944094f..000000000 --- a/coreclr-debug/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -bin -obj -project.lock.json -debugAdapters -install.log -extension.log -project.json diff --git a/coreclr-debug/NuGet.config b/coreclr-debug/NuGet.config deleted file mode 100644 index ef957669a..000000000 --- a/coreclr-debug/NuGet.config +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/coreclr-debug/dummy.cs b/coreclr-debug/dummy.cs deleted file mode 100644 index 15513fc2c..000000000 --- a/coreclr-debug/dummy.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Dummy -{ - class Dummy - { - static void Main(string[] args) { - // empty boilerplate required by dotnet build/publish to emit an entry point - // The entrypoint created is dummy[.exe], which we rename to OpenDebugAD7[.exe] - // The generated entry point will then run OpenDebugAD7.dll for us - } - } -} \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 207d4bc0a..a1faae5f7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -29,11 +29,11 @@ const PlatformInformation = platform.PlatformInformation; /// used in offline packaging run so does not clean .vsix function clean() { cleanDebugger(); - return cleanOmnisharp(); + cleanOmnisharp(); } gulp.task('clean', ['omnisharp:clean', 'debugger:clean', 'package:clean'], () => { - + del.sync('install.*'); }); /// Omnisharp Tasks @@ -48,11 +48,11 @@ function installOmnisharp(platformInfo, packageJSON) { } function cleanOmnisharp() { - return del('.omnisharp-*'); + del.sync('.omnisharp-*'); } gulp.task('omnisharp:clean', () => { - return cleanOmnisharp(); + cleanOmnisharp(); }); gulp.task('omnisharp:install', ['omnisharp:clean'], () => { @@ -72,12 +72,7 @@ function installDebugger(runtimeId) { } function cleanDebugger() { - try { - getDebugInstaller().clean(); - console.log('Cleaned Succesfully'); - } catch (error) { - console.error(error); - } + del.sync('.debugger'); } gulp.task('debugger:install', ['debugger:clean'], () => { @@ -130,7 +125,7 @@ function getPackageJSON() { } gulp.task('package:clean', () => { - return del('*.vsix'); + del.sync('*.vsix'); }); gulp.task('package:online', ['clean'], () => { diff --git a/package.json b/package.json index 684d8cf13..6b425349e 100644 --- a/package.json +++ b/package.json @@ -190,6 +190,110 @@ "darwin", "linux" ] + }, + { + "description": ".NET Core Debugger (Windows / x64)", + "url": "https://vsdebugger.azureedge.net/coreclr-debug-1-5-0/coreclr-debug-win7-x64.zip", + "installPath": ".debugger", + "runtimeIds": [ + "win7-x64" + ] + }, + { + "description": ".NET Core Debugger (OSX / x64)", + "url": "https://vsdebugger.azureedge.net/coreclr-debug-1-5-0/coreclr-debug-osx.10.11-x64.zip", + "installPath": ".debugger", + "runtimeIds": [ + "osx.10.11-x64" + ], + "binaries": [ + "./OpenDebugAD7", + "./clrdbg" + ] + }, + { + "description": ".NET Core Debugger (CentOS / x64)", + "url": "https://vsdebugger.azureedge.net/coreclr-debug-1-5-0/coreclr-debug-centos.7-x64.zip", + "installPath": ".debugger", + "runtimeIds": [ + "centos.7-x64" + ], + "binaries": [ + "./OpenDebugAD7", + "./clrdbg" + ] + }, + { + "description": ".NET Core Debugger (Debian / x64)", + "url": "https://vsdebugger.azureedge.net/coreclr-debug-1-5-0/coreclr-debug-debian.8-x64.zip", + "installPath": ".debugger", + "runtimeIds": [ + "debian.8-x64" + ], + "binaries": [ + "./OpenDebugAD7", + "./clrdbg" + ] + }, + { + "description": ".NET Core Debugger (Fedora / x64)", + "url": "https://vsdebugger.azureedge.net/coreclr-debug-1-5-0/coreclr-debug-fedora.23-x64.zip", + "installPath": ".debugger", + "runtimeIds": [ + "fedora.23-x64" + ], + "binaries": [ + "./OpenDebugAD7", + "./clrdbg" + ] + }, + { + "description": ".NET Core Debugger (OpenSUSE / x64)", + "url": "https://vsdebugger.azureedge.net/coreclr-debug-1-5-0/coreclr-debug-opensuse.13.2-x64.zip", + "installPath": ".debugger", + "runtimeIds": [ + "opensuse.13.2-x64" + ], + "binaries": [ + "./OpenDebugAD7", + "./clrdbg" + ] + }, + { + "description": ".NET Core Debugger (RHEL / x64)", + "url": "https://vsdebugger.azureedge.net/coreclr-debug-1-5-0/coreclr-debug-rhel.7.2-x64.zip", + "installPath": ".debugger", + "runtimeIds": [ + "rhel.7-x64" + ], + "binaries": [ + "./OpenDebugAD7", + "./clrdbg" + ] + }, + { + "description": ".NET Core Debugger (Ubuntu 14 / x64)", + "url": "https://vsdebugger.azureedge.net/coreclr-debug-1-5-0/coreclr-debug-ubuntu.14.04-x64.zip", + "installPath": ".debugger", + "runtimeIds": [ + "ubuntu.14.04-x64" + ], + "binaries": [ + "./OpenDebugAD7", + "./clrdbg" + ] + }, + { + "description": ".NET Core Debugger (Ubuntu 16 / x64)", + "url": "https://vsdebugger.azureedge.net/coreclr-debug-1-5-0/coreclr-debug-ubuntu.16.04-x64.zip", + "installPath": ".debugger", + "runtimeIds": [ + "ubuntu.16.04-x64" + ], + "binaries": [ + "./OpenDebugAD7", + "./clrdbg" + ] } ], "engines": { diff --git a/src/common.ts b/src/common.ts index 552f63ff7..034818cf4 100644 --- a/src/common.ts +++ b/src/common.ts @@ -21,10 +21,6 @@ export function getExtensionPath() { return extensionPath; } -export function getBinPath() { - return path.resolve(getExtensionPath(), "bin"); -} - export function buildPromiseChain(array: T[], builder: (item: T) => Promise): Promise { return array.reduce( (promise, n) => promise.then(() => builder(n)), @@ -60,19 +56,37 @@ export function fileExists(filePath: string): Promise { }); } -function getInstallLockFilePath(): string { - return path.resolve(getExtensionPath(), 'install.lock'); +export enum InstallFileType { + Begin, + Lock +} + +function getInstallFilePath(type: InstallFileType): string { + let installFile = 'install.' + InstallFileType[type]; + return path.resolve(getExtensionPath(), installFile); } -export function lockFileExists(): Promise { - return fileExists(getInstallLockFilePath()); +export function installFileExists(type: InstallFileType): Promise { + return fileExists(getInstallFilePath(type)); +} + +export function touchInstallFile(type: InstallFileType): Promise { + return new Promise((resolve, reject) => { + fs.writeFile(getInstallFilePath(type), '', err => { + if (err) { + reject(err); + } + + resolve(); + }); + }); } -export function touchLockFile(): Promise { +export function deleteInstallFile(type: InstallFileType): Promise { return new Promise((resolve, reject) => { - fs.writeFile(getInstallLockFilePath(), '', err => { + fs.unlink(getInstallFilePath(type), err => { if (err) { - return reject(err); + reject(err); } resolve(); diff --git a/src/coreclr-debug/activate.ts b/src/coreclr-debug/activate.ts index bde6d7227..a01930457 100644 --- a/src/coreclr-debug/activate.ts +++ b/src/coreclr-debug/activate.ts @@ -7,126 +7,50 @@ import * as vscode from 'vscode'; import * as fs from 'fs'; import TelemetryReporter from 'vscode-extension-telemetry'; -import { CoreClrDebugUtil } from './util'; +import { CoreClrDebugUtil, DotnetInfo, DotNetCliError } from './util'; import * as debugInstall from './install'; -import { PlatformInformation } from './../platform'; -import * as semver from 'semver'; +import * as path from 'path'; +import { Logger } from './../logger' -const MINIMUM_SUPPORTED_DOTNET_CLI: string = '1.0.0-preview2-003121'; - -let _reporter: TelemetryReporter = null; -let _channel: vscode.OutputChannel = null; let _util: CoreClrDebugUtil = null; +let _reporter: TelemetryReporter = null; +let _logger: Logger = null; -class DotnetInfo -{ - public Version: string; - public OsVersion: string; - public RuntimeId: string; -} - -export function activate(context: vscode.ExtensionContext, reporter: TelemetryReporter) { +export function activate(context: vscode.ExtensionContext, reporter: TelemetryReporter, logger: Logger) { + _util = new CoreClrDebugUtil(context.extensionPath, logger); _reporter = reporter; - _channel = vscode.window.createOutputChannel('coreclr-debug'); - _util = new CoreClrDebugUtil(context.extensionPath, _channel); - - if (CoreClrDebugUtil.existsSync(_util.installCompleteFilePath())) { - console.log('.NET Core Debugger tools already installed'); - return; - } - - checkForDotnetTools().then((dotnetInfo: DotnetInfo) => { - let installer = new debugInstall.DebugInstaller(_util); - _util.createInstallLog(); - - let statusBarMessage = vscode.window.setStatusBarMessage("Downloading and configuring the .NET Core Debugger..."); - - let runtimeId: string = ''; - let installStage: string = "installBegin"; - let installError: string = ''; - let moreErrors: string = ''; - - getPlatformRuntimeId().then(rid => { - runtimeId = rid; - }).then(() => { - return writeInstallBeginFile(); - }).then(() => { - return installer.install(runtimeId); - }).then(() => { - installStage = "completeSuccess"; - statusBarMessage.dispose(); - vscode.window.setStatusBarMessage('Successfully installed .NET Core Debugger.'); - }).catch((error: debugInstall.InstallError) => { - const viewLogMessage = "View Log"; - vscode.window.showErrorMessage('Error while installing .NET Core Debugger.', viewLogMessage).then(value => { - if (value === viewLogMessage) { - _channel.show(vscode.ViewColumn.Three); - } - }); - statusBarMessage.dispose(); - - installStage = error.installStage; - installError = error.errorMessage; - moreErrors = error.hasMoreErrors ? 'true' : 'false'; - }).then(() => { - // log telemetry and delete install begin file - logTelemetry('Acquisition', { - installStage: installStage, - installError: installError, - moreErrors: moreErrors, - dotnetVersion: dotnetInfo.Version, - osVersion: dotnetInfo.OsVersion, - osRID: dotnetInfo.RuntimeId + _logger = logger; + + if (!CoreClrDebugUtil.existsSync(_util.debugAdapterDir())) { + // We have been activated but it looks like our package was not installed. This is bad. + logger.appendLine("[ERROR]: C# Extension failed to install the debugger package"); + showInstallErrorMessage(); + } else if (!CoreClrDebugUtil.existsSync(_util.installCompleteFilePath())) { + _util.checkDotNetCli() + .then((dotnetInfo: DotnetInfo) => { + let installer = new debugInstall.DebugInstaller(_util); + installer.finishInstall() + .then(() => { + vscode.window.setStatusBarMessage('Successfully installed .NET Core Debugger.'); + }) + .catch((err) => { + logger.appendLine("[ERROR]: An error occured while installing the .NET Core Debugger:") + logger.appendLine(err); + showInstallErrorMessage(); + // TODO: log telemetry? + }); + }, (err) => { + // Check for dotnet tools failed. pop the UI + // err is a DotNetCliError but use defaults in the unexpected case that it's not + showDotnetToolsWarning(err.ErrorMessage || _util.defaultDotNetCliErrorMessage()); + _logger.appendLine(err.ErrorString || err); + // TODO: log telemetry? }); - try { - deleteInstallBeginFile(); - } catch (err) { - // if this throws there's really nothing we can do - } - _util.closeInstallLog(); - }); - }).catch((error) => { - // log errors from checkForDotnetTools - _util.log(error.message); - }); + } } -// This function checks for the presence of dotnet on the path and ensures the Version -// is new enough for us. Any error UI that needs to be displayed is handled by this function. -// Returns: a promise that returns a DotnetInfo class -// Throws: An Error() from the return promise if either dotnet does not exist or is too old. -function checkForDotnetTools() : Promise -{ - let dotnetInfo = new DotnetInfo(); - - return _util.spawnChildProcess('dotnet', ['--info'], _util.coreClrDebugDir(), (data: Buffer) => { - let lines: string[] = data.toString().replace(/\r/mg, '').split('\n'); - lines.forEach(line => { - let match: RegExpMatchArray; - if (match = /^\ Version:\s*([^\s].*)$/.exec(line)) { - dotnetInfo.Version = match[1]; - } else if (match = /^\ OS Version:\s*([^\s].*)$/.exec(line)) { - dotnetInfo.OsVersion = match[1]; - } else if (match = /^\ RID:\s*([\w\-\.]+)$/.exec(line)) { - dotnetInfo.RuntimeId = match[1]; - } - }); - }).catch((error) => { - // something went wrong with spawning 'dotnet --info' - let message = '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.'; - showDotnetToolsWarning(message); - throw new Error("Failed to spawn 'dotnet --info'"); - }).then(() => { - // succesfully spawned 'dotnet --info', check the Version - if (semver.lt(dotnetInfo.Version, MINIMUM_SUPPORTED_DOTNET_CLI)) - { - let message = 'The .NET CLI tools on the path are too old. .NET Core debugging will not be enabled. The minimum supported version is ' + MINIMUM_SUPPORTED_DOTNET_CLI + '.'; - showDotnetToolsWarning(message); - throw new Error("dotnet cli is too old"); - } - - return dotnetInfo; - }); +function showInstallErrorMessage() { + vscode.window.showErrorMessage("An error occured during installation of the .NET Core Debugger. The C# extension may need to be reinstalled."); } function showDotnetToolsWarning(message: string) : void @@ -153,27 +77,4 @@ function logTelemetry(eventName: string, properties?: {[prop: string]: string}): if (_reporter !== null) { _reporter.sendTelemetryEvent('coreclr-debug/' + eventName, properties); } -} - -function writeInstallBeginFile() : Promise { - return CoreClrDebugUtil.writeEmptyFile(_util.installBeginFilePath()); -} - -function deleteInstallBeginFile() { - if (CoreClrDebugUtil.existsSync(_util.installBeginFilePath())) { - fs.unlinkSync(_util.installBeginFilePath()); - } -} - -function getPlatformRuntimeId(): Promise { - return PlatformInformation.GetCurrent().then(info => { - if (info.runtimeId) { - return info.runtimeId; - } - - // If we got here, this isn't a support runtime ID. - const message = `Unsupported platform: ${info.toString()}` - _util.log(`Error: ${message}`); - throw new Error(message); - }); -} +} \ No newline at end of file diff --git a/src/coreclr-debug/install.ts b/src/coreclr-debug/install.ts index 17eb998dd..95df4ec37 100644 --- a/src/coreclr-debug/install.ts +++ b/src/coreclr-debug/install.ts @@ -2,12 +2,10 @@ * 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 { CoreClrDebugUtil } from './util'; import * as fs from 'fs'; import * as path from 'path'; -import * as fs_extra from 'fs-extra-promise'; export class InstallError extends Error { public installStage: string; @@ -44,84 +42,31 @@ export class InstallError extends Error { export class DebugInstaller { private _util: CoreClrDebugUtil = null; - private _isOffline; +// private _isOffline; - constructor(util: CoreClrDebugUtil, isOffline?: boolean) { + constructor(util: CoreClrDebugUtil) { this._util = util; - this._isOffline = isOffline || false; } - public install(runtimeId: string): Promise { - let errorBuilder: InstallError = new InstallError(); - errorBuilder.installStage = 'writeProjectJson'; + public finishInstall(): Promise { + let errorBuilder = new InstallError(); - return this.writeProjectJson(runtimeId).then(() => { - errorBuilder.installStage = 'dotnetRestore'; - return this._util.spawnChildProcess('dotnet', ['--verbose', 'restore', '--configfile', 'NuGet.config'], this._util.coreClrDebugDir(), - (data: Buffer) => { - let text: string = data.toString(); - this._util.logRaw(text); - - // Certain errors are only logged to stdout. - // Detect these and make a note of the kind of error. - DebugInstaller.parseRestoreErrors(text, errorBuilder); - }, - (data: Buffer) => { - let text: string = data.toString(); - this._util.logRaw(text); - - // Reference errors are sent to stderr at the end of restore. - DebugInstaller.parseReferenceErrors(text, errorBuilder); - }); - }).then(() => { - errorBuilder.installStage = 'dotnetPublish'; - return this._util.spawnChildProcess('dotnet', ['--verbose', 'publish', '-r', runtimeId, '-o', this._util.debugAdapterDir()], this._util.coreClrDebugDir(), - (data: Buffer) => { - let text: string = data.toString(); - this._util.logRaw(text); - - DebugInstaller.parsePublishErrors(text, errorBuilder); - }); - }).then(() => { - errorBuilder.installStage = 'ensureAd7'; - return this.ensureAd7EngineExists(this._util.debugAdapterDir()); - }).then(() => { - errorBuilder.installStage = 'renameDummyEntrypoint'; - return this.renameDummyEntrypoint(); - }).then(() => { + return Promise.resolve().then(() => { errorBuilder.installStage = 'rewriteManifest'; this.rewriteManifest(); - errorBuilder.installStage = 'writeCompletionFile'; - return this.writeCompletionFile(); - }).catch((e) => { + errorBuilder.installStage = 'writeCompletionFile' + return CoreClrDebugUtil.writeEmptyFile(this._util.installCompleteFilePath()); + }).catch((err) => { if (errorBuilder.errorMessage === null) { // Only give the error message if we don't have any better info, // as this is usually something similar to "Error: 1". - errorBuilder.errorMessage = e; + errorBuilder.errorMessage = err; } throw errorBuilder; }); } - public clean(): void { - let cleanItems: string[] = []; - - cleanItems.push(this._util.debugAdapterDir()); - cleanItems.push(this._util.installLogPath()); - cleanItems.push(path.join(this._util.coreClrDebugDir(), 'bin')); - cleanItems.push(path.join(this._util.coreClrDebugDir(), 'obj')); - cleanItems.push(path.join(this._util.coreClrDebugDir(), 'project.json')); - cleanItems.push(path.join(this._util.coreClrDebugDir(), 'project.lock.json')); - - cleanItems.forEach((item) => { - if (CoreClrDebugUtil.existsSync(item)) { - this._util.log(`Cleaning ${item}`); - fs_extra.removeSync(item); - } - }); - } - private rewriteManifest(): void { const manifestPath = path.join(this._util.extensionDir(), 'package.json'); let manifestString = fs.readFileSync(manifestPath, 'utf8'); @@ -129,7 +74,7 @@ export class DebugInstaller { delete manifestObject.contributes.debuggers[0].runtime; delete manifestObject.contributes.debuggers[0].program; - let programString = './coreclr-debug/debugAdapters/OpenDebugAD7'; + let programString = './.debugger/OpenDebugAD7'; manifestObject.contributes.debuggers[0].windows = { program: programString + '.exe' }; manifestObject.contributes.debuggers[0].osx = { program: programString }; manifestObject.contributes.debuggers[0].linux = { program: programString }; @@ -137,179 +82,4 @@ export class DebugInstaller { manifestString = JSON.stringify(manifestObject, null, 2); fs.writeFileSync(manifestPath, manifestString); } - - private writeCompletionFile(): Promise { - return CoreClrDebugUtil.writeEmptyFile(this._util.installCompleteFilePath()); - } - - private renameDummyEntrypoint(): Promise { - let src = path.join(this._util.debugAdapterDir(), 'dummy'); - let dest = path.join(this._util.debugAdapterDir(), 'OpenDebugAD7'); - - if (!CoreClrDebugUtil.existsSync(src)) { - if (CoreClrDebugUtil.existsSync(src + '.exe')) { - src += '.exe'; - dest += '.exe'; - } - } - - const promise = new Promise((resolve, reject) => { - fs.rename(src, dest, (err) => { - if (err) { - reject(err.code); - } else { - resolve(); - } - }); - }); - - return promise; - } - - private ensureAd7EngineExists(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 { - this._util.log(`${filePath} does not exist.`); - this._util.log(''); - // NOTE: The minimum build number is actually less than 1584, but this is the minimum tested build. - this._util.log('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.'); - } - }); - }); - } - - private writeProjectJson(runtimeId: string): Promise { - return new Promise((resolve, reject) => { - const projectJsonPath = path.join(this._util.coreClrDebugDir(), 'project.json'); - this._util.log('Creating ' + projectJsonPath); - - const projectJson = this.createProjectJson(runtimeId); - - fs.writeFile(projectJsonPath, JSON.stringify(projectJson, null, 2), { encoding: 'utf8' }, (err) => { - if (err) { - this._util.log('Error: Unable to write to project.json: ' + err.message); - reject(err.code); - } - else { - resolve(); - } - }); - }); - } - - private createProjectJson(targetRuntime: string): any { - let projectJson = { - name: "dummy", - buildOptions: { - emitEntryPoint: true - }, - dependencies: { - "Microsoft.VisualStudio.clrdbg": "15.0.25626-preview-3219185", - "Microsoft.VisualStudio.clrdbg.MIEngine": "14.0.31028-preview-1", - "Microsoft.VisualStudio.OpenDebugAD7": "1.0.21028-preview-2", - "NETStandard.Library": "1.6.0", - "Newtonsoft.Json": "7.0.1", - "Microsoft.VisualStudio.Debugger.Interop.Portable": "1.0.1", - "System.Collections.Specialized": "4.0.1", - "System.Collections.Immutable": "1.2.0", - "System.Diagnostics.Process": "4.1.0", - "System.Dynamic.Runtime": "4.0.11", - "Microsoft.CSharp": "4.0.1", - "System.Threading.Tasks.Dataflow": "4.6.0", - "System.Threading.Thread": "4.0.0", - "System.Xml.XDocument": "4.0.11", - "System.Xml.XmlDocument": "4.0.1", - "System.Xml.XmlSerializer": "4.0.11", - "System.ComponentModel": "4.0.1", - "System.ComponentModel.Annotations": "4.1.0", - "System.ComponentModel.EventBasedAsync": "4.0.11", - "System.Runtime.Serialization.Primitives": "4.1.1", - "System.Net.Http": "4.1.0" - }, - frameworks: { - "netcoreapp1.0": { - imports: ["dnxcore50", "portable-net45+win8"] - } - }, - runtimes: { - } - }; - - projectJson.runtimes[targetRuntime] = {}; - - if (this._isOffline) { - projectJson.dependencies["Microsoft.NetCore.DotNetHostPolicy"] = "1.0.1"; - } - - return projectJson; - } - - private static parseRestoreErrors(output: string, errorBuilder: InstallError): void { - let lines: string[] = output.replace(/\r/mg, '').split('\n'); - lines.forEach(line => { - if (line.startsWith('error')) { - const connectionError: string = 'The server name or address could not be resolved'; - if (line.indexOf(connectionError) !== -1) { - errorBuilder.errorMessage = connectionError; - } - - const parseVersionError: RegExp = /Error reading '.*' at line [0-9]+ column [0-9]+: '.*' is not a valid version string/; - if (parseVersionError.test(line)) { - errorBuilder.errorMessage = 'Invalid version string'; - } - } - }); - } - - private static parseReferenceErrors(output: string, errorBuilder: InstallError): void { - // Reference errors are restated at the end of the output. Find this section first. - let errorRegionRegExp: RegExp = /^Errors in .*project\.json$/gm; - let beginIndex: number = output.search(errorRegionRegExp); - let errorBlock: string = output.slice(beginIndex); - - let lines: string[] = errorBlock.replace(/\r/mg, '').split('\n'); - lines.forEach(line => { - let referenceRegExp: RegExp = /^(?:\t|\ \ \ \ )Unable to resolve '([^']+)'/g; - let match: RegExpMatchArray; - while (match = referenceRegExp.exec(line)) { - let reference: string = match[1]; - if (reference.startsWith('Microsoft') || - reference.startsWith('System') || - reference.startsWith('NETStandard') || - reference.startsWith('Newtonsoft')) { - errorBuilder.errorMessage = `Unable to restore reference '${reference}'`; - } else { - errorBuilder.errorMessage = 'Error(s) encountered restoring private references'; - } - } - }); - } - - private static parsePublishErrors(output: string, errorBuilder: InstallError): void { - let lines: string[] = output.replace(/\r/mg, '').split('\n'); - lines.forEach(line => { - const errorTypeRegExp: RegExp = /^([\w\.]+Exception)/g; - let typeMatch: RegExpMatchArray; - while (typeMatch = errorTypeRegExp.exec(line)) { - let type: string = typeMatch[1]; - if (type === 'System.IO.IOException') { - const ioExceptionRegExp: RegExp = /System\.IO\.IOException: The process cannot access the file '(.*)' because it is being used by another process./g; - let ioMatch: RegExpMatchArray; - if (ioMatch = ioExceptionRegExp.exec(line)) { - // Remove path as it may contain user information. - errorBuilder.errorMessage = `System.IO.IOException: unable to access '${path.basename(ioMatch[1])}'`; - } - } else { - errorBuilder.errorMessage = type; - } - } - }); - } } \ No newline at end of file diff --git a/src/coreclr-debug/proxy.ts b/src/coreclr-debug/proxy.ts index 80bccdeba..2a72701cb 100644 --- a/src/coreclr-debug/proxy.ts +++ b/src/coreclr-debug/proxy.ts @@ -8,6 +8,8 @@ import * as path from 'path'; import { DebugProtocol } from 'vscode-debugprotocol'; import * as child_process from 'child_process'; import { CoreClrDebugUtil } from './util'; +import * as common from './../common'; +import { Logger } from './../logger'; class ProxyErrorResponse implements DebugProtocol.ErrorResponse { public body: { error?: DebugProtocol.Message }; @@ -32,6 +34,18 @@ function serializeProtocolEvent(message: DebugProtocol.ProtocolMessage): string return finalPayload; } +function sendErrorMessage(message: string) { + process.stdout.write(serializeProtocolEvent(new ProxyErrorResponse(message))); +} + +function sendStillDownloadingMessage() { + sendErrorMessage('The .NET Core Debugger is still being downloaded. See the C# Output Window for more information.'); +} + +function sendDownloadingNotStartedMessage() { + sendErrorMessage('Run \'Debug: Download .NET Core Debugger\' in the Command Palette or open a .NET project directory to download the .NET Core Debugger'); +} + // 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 @@ -39,17 +53,53 @@ 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() { - let util = new CoreClrDebugUtil(path.resolve(__dirname, '../../../')); + let extensionPath = path.resolve(__dirname, '../../../'); + common.setExtensionPath(extensionPath); + + let logger = new Logger((text) => { console.log(text) }); + let util = new CoreClrDebugUtil(extensionPath, logger); if (!CoreClrDebugUtil.existsSync(util.installCompleteFilePath())) { - if (CoreClrDebugUtil.existsSync(util.installBeginFilePath())) { - 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 \'Debug: Download .NET Core Debugger\' in the Command Palette or open a .NET project directory to download the .NET Core Debugger'))); - } + // our install.complete file does not exist yet, meaning we have not rewritten our manifest yet. Try to figure out what if anything the package manager is doing + // the order in which files are dealt with is this: + // 1. install.Begin is created + // 2. install.Lock is created + // 3. install.Begin is deleted + // 4. install.complete is created + + //first check if dotnet is on the path and new enough + util.checkDotNetCli() + .then((dotnetInfo) => { + // next check if we have begun installing packages + common.installFileExists(common.InstallFileType.Begin) + .then((beginExists: boolean) => { + if (beginExists) { + // packages manager has begun + sendStillDownloadingMessage(); + } else { + // begin doesn't exist. There is a chance we finished downloading and begin had been deleted. Check if lock exists + common.installFileExists(common.InstallFileType.Lock) + .then((lockExists) => { + if (lockExists) { + // packages have finished installing but we had not finished rewriting our manifest when F5 came in + sendStillDownloadingMessage(); + } + else { + // no install files existed when we checked. we have likely not been activated + sendDownloadingNotStartedMessage(); + } + }); + } + }); + }, (err) => { + // error from checkDotNetCli + sendErrorMessage(err.ErrorMessage || util.defaultDotNetCliErrorMessage()); + }); } else { + // debugger has finished install and manifest has been rewritten, kick off our debugger process + new Promise((resolve, reject) => { let processPath = path.join(util.debugAdapterDir(), "OpenDebugAD7" + CoreClrDebugUtil.getPlatformExeExtension()); let args = process.argv.slice(2); @@ -70,7 +120,7 @@ function proxy() { process.stdin.setEncoding('utf8'); child.on('error', data => { - util.logToFile(`Child error: ${data}`); + logger.appendLine(`Child error: ${data}`); }); process.on('SIGTERM', () => { @@ -84,11 +134,11 @@ function proxy() { }); process.stdin.on('error', error => { - util.logToFile(`process.stdin error: ${error}`); + logger.appendLine(`process.stdin error: ${error}`); }); process.stdout.on('error', error => { - util.logToFile(`process.stdout error: ${error}`); + logger.appendLine(`process.stdout error: ${error}`); }); child.stdout.on('data', data => { @@ -101,7 +151,7 @@ function proxy() { process.stdin.resume(); }).catch(err => { - util.logToFile(`Promise failed: ${err}`); + logger.appendLine(err); }); } } diff --git a/src/coreclr-debug/util.ts b/src/coreclr-debug/util.ts index d15328ae5..3fc351290 100644 --- a/src/coreclr-debug/util.ts +++ b/src/coreclr-debug/util.ts @@ -8,32 +8,36 @@ import * as child_process from 'child_process'; import * as path from 'path'; import * as fs from 'fs'; import * as os from 'os'; -import * as vscode from 'vscode'; +import * as semver from 'semver'; +import { Logger } from './../logger' + +const MINIMUM_SUPPORTED_DOTNET_CLI: string = '1.0.0-preview2-003121'; + +export class DotnetInfo +{ + public Version: string; + public OsVersion: string; + public RuntimeId: string; +} + +export class DotNetCliError extends Error { + public ErrorMessage: string; // the message to display to the user + public ErrorString: string; // the string to log for this error +} export class CoreClrDebugUtil { private _extensionDir: string = ''; - private _coreClrDebugDir: string = ''; private _debugAdapterDir: string = ''; - private _installLogPath: string = ''; - private _installBeginFilePath: string = ''; private _installCompleteFilePath: string = ''; - private _installLog: fs.WriteStream = null; - private _channel: vscode.OutputChannel = null; - - constructor(extensionDir: string, channel?: vscode.OutputChannel) { + constructor(extensionDir: string, logger: Logger) { this._extensionDir = extensionDir; - this._coreClrDebugDir = path.join(this._extensionDir, 'coreclr-debug'); - this._debugAdapterDir = path.join(this._coreClrDebugDir, 'debugAdapters'); - this._installLogPath = path.join(this._coreClrDebugDir, 'install.log'); - this._installBeginFilePath = path.join(this._coreClrDebugDir, 'install.begin'); + this._debugAdapterDir = path.join(this._extensionDir, '.debugger'); this._installCompleteFilePath = path.join(this._debugAdapterDir, 'install.complete'); - - this._channel = channel; } - extensionDir(): string { + public extensionDir(): string { if (this._extensionDir === '') { throw new Error('Failed to set extension directory'); @@ -41,35 +45,14 @@ export class CoreClrDebugUtil return this._extensionDir; } - coreClrDebugDir(): string { - if (this._coreClrDebugDir === '') { - throw new Error('Failed to set coreclrdebug directory'); - } - return this._coreClrDebugDir; - } - - debugAdapterDir(): string { + public debugAdapterDir(): string { if (this._debugAdapterDir === '') { throw new Error('Failed to set debugadpter directory'); } return this._debugAdapterDir; } - installLogPath(): string { - if (this._installLogPath === '') { - throw new Error('Failed to set install log path'); - } - return this._installLogPath; - } - - installBeginFilePath(): string { - if (this._installBeginFilePath === '') { - throw new Error('Failed to set install begin file path'); - } - return this._installBeginFilePath; - } - - installCompleteFilePath(): string { + public installCompleteFilePath(): string { if (this._installCompleteFilePath === '') { throw new Error('Failed to set install complete file path'); @@ -77,41 +60,7 @@ export class CoreClrDebugUtil return this._installCompleteFilePath; } - createInstallLog(): void { - this._installLog = fs.createWriteStream(this.installLogPath()); - } - - closeInstallLog(): void { - if (this._installLog !== null) { - this._installLog.close(); - } - } - - logRaw(message: string): void { - console.log(message); - - if (this._installLog != null) { - this._installLog.write(message); - } - - if (this._channel != null) { - this._channel.append(message); - } - } - - log(message: string): void { - console.log(message); - - if (this._installLog != null) { - this._installLog.write(message); - } - - if (this._channel != null) { - this._channel.appendLine(message); - } - } - - static writeEmptyFile(path: string) : Promise { + public static writeEmptyFile(path: string) : Promise { return new Promise((resolve, reject) => { fs.writeFile(path, '', (err) => { if (err) { @@ -123,23 +72,67 @@ export class CoreClrDebugUtil }); } + public defaultDotNetCliErrorMessage(): string { + return 'Failed to find up to date dotnet cli on the path.'; + } + + // This function checks for the presence of dotnet on the path and ensures the Version + // is new enough for us. + // Returns: a promise that returns a DotnetInfo class + // Throws: An CotNetCliError() from the return promise if either dotnet does not exist or is too old. + public checkDotNetCli(): Promise + { + let dotnetInfo = new DotnetInfo(); + + return this.spawnChildProcess('dotnet', ['--info'], this.debugAdapterDir(), (data: Buffer) => { + let lines: string[] = data.toString().replace(/\r/mg, '').split('\n'); + lines.forEach(line => { + let match: RegExpMatchArray; + if (match = /^\ Version:\s*([^\s].*)$/.exec(line)) { + dotnetInfo.Version = match[1]; + } else if (match = /^\ OS Version:\s*([^\s].*)$/.exec(line)) { + dotnetInfo.OsVersion = match[1]; + } else if (match = /^\ RID:\s*([\w\-\.]+)$/.exec(line)) { + dotnetInfo.RuntimeId = match[1]; + } + }); + }).catch((error) => { + // something went wrong with spawning 'dotnet --info' + let dotnetError = new DotNetCliError(); + dotnetError.ErrorMessage = '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.'; + dotnetError.ErrorString = "Failed to spawn 'dotnet --info'"; + throw dotnetError; + }).then(() => { + // succesfully spawned 'dotnet --info', check the Version + if (semver.lt(dotnetInfo.Version, MINIMUM_SUPPORTED_DOTNET_CLI)) + { + let dotnetError = new DotNetCliError(); + dotnetError.ErrorMessage = 'The .NET CLI tools on the path are too old. .NET Core debugging will not be enabled. The minimum supported version is ' + MINIMUM_SUPPORTED_DOTNET_CLI + '.'; + dotnetError.ErrorString = "dotnet cli is too old"; + throw dotnetError; + } + + return dotnetInfo; + }); + } + public spawnChildProcess(process: string, args: string[], workingDirectory: string, onStdout?: (data: Buffer) => void, onStderr?: (data: Buffer) => void): Promise { const promise = new Promise((resolve, reject) => { const child = child_process.spawn(process, args, { cwd: workingDirectory }); if (!onStdout) { - onStdout = (data) => { this.logRaw(`${data}`); }; + onStdout = (data) => { console.log(`${data}`); }; } child.stdout.on('data', onStdout); if (!onStderr) { - onStderr = (data) => { this.logRaw(`${data}`); }; + onStderr = (data) => { console.error(`${data}`); }; } child.stderr.on('data', onStderr); child.on('close', (code: number) => { if (code != 0) { - this.log(`${process} exited with error code ${code}`);; + console.log(`${process} exited with error code ${code}`);; reject(new Error(code.toString())); } else { @@ -155,7 +148,7 @@ export class CoreClrDebugUtil return promise; } - static existsSync(path: string) : boolean { + public static existsSync(path: string) : boolean { try { fs.accessSync(path, fs.F_OK); return true; @@ -168,30 +161,11 @@ export class CoreClrDebugUtil } } - static getPlatformExeExtension() : string { + public static getPlatformExeExtension() : string { if (process.platform === 'win32') { return '.exe'; } return ''; } - - 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' }); - } } diff --git a/src/main.ts b/src/main.ts index 32159f172..f9f427444 100644 --- a/src/main.ts +++ b/src/main.ts @@ -15,6 +15,8 @@ import { Logger } from './logger'; import { PackageManager, Status } from './packages'; import { PlatformInformation } from './platform'; +let _channel: vscode.OutputChannel = null; + export function activate(context: vscode.ExtensionContext): any { const extensionId = 'ms-vscode.csharp'; @@ -25,31 +27,34 @@ export function activate(context: vscode.ExtensionContext): any { util.setExtensionPath(extension.extensionPath); - ensureRuntimeDependencies(extension) + _channel = vscode.window.createOutputChannel('C#'); + + let logger = new Logger(text => _channel.append(text)); + + ensureRuntimeDependencies(extension, logger) .then(() => { // activate language services OmniSharp.activate(context, reporter); // activate coreclr-debug - coreclrdebug.activate(context, reporter); + coreclrdebug.activate(context, reporter, logger); }); } -function ensureRuntimeDependencies(extension: vscode.Extension): Promise { - return util.lockFileExists() +function ensureRuntimeDependencies(extension: vscode.Extension, logger: Logger): Promise { + return util.installFileExists(util.InstallFileType.Lock) .then(exists => { if (!exists) { - return installRuntimeDependencies(extension); + return util.touchInstallFile(util.InstallFileType.Begin).then(() => { + return installRuntimeDependencies(extension, logger); + }); } }); } -function installRuntimeDependencies(extension: vscode.Extension): Promise { - let channel = vscode.window.createOutputChannel('C#'); - channel.show(); - - let logger = new Logger(text => channel.append(text)); - logger.appendLine('Updating C# dependencies...'); +function installRuntimeDependencies(extension: vscode.Extension, logger: Logger): Promise { + logger.append('Updating C# dependencies...'); + _channel.show(); let statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right); let status: Status = { @@ -65,10 +70,14 @@ function installRuntimeDependencies(extension: vscode.Extension): Promise { + installationStage = 'getPlatformInfo'; + return PlatformInformation.GetCurrent() + }) .then(info => { platformInfo = info; packageManager = new PackageManager(info, extension.packageJSON); @@ -90,7 +99,11 @@ function installRuntimeDependencies(extension: vscode.Extension): Promise { installationStage = 'touchLockFile'; - return util.touchLockFile(); + return util.touchInstallFile(util.InstallFileType.Lock); + }) + .then(() => { + installationStage = 'deleteBeginFile'; + return util.deleteInstallFile(util.InstallFileType.Begin) }) .catch(error => { errorMessage = error.toString(); From 981cf654a5ce78aedd4e424dc93d8755359f0b31 Mon Sep 17 00:00:00 2001 From: Chuck Ries Date: Fri, 4 Nov 2016 16:26:19 -0700 Subject: [PATCH 2/4] Always use the cwd of the process when checking for dotnet cli --- src/coreclr-debug/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr-debug/util.ts b/src/coreclr-debug/util.ts index 3fc351290..a1e9aa709 100644 --- a/src/coreclr-debug/util.ts +++ b/src/coreclr-debug/util.ts @@ -84,7 +84,7 @@ export class CoreClrDebugUtil { let dotnetInfo = new DotnetInfo(); - return this.spawnChildProcess('dotnet', ['--info'], this.debugAdapterDir(), (data: Buffer) => { + return this.spawnChildProcess('dotnet', ['--info'], process.cwd(), (data: Buffer) => { let lines: string[] = data.toString().replace(/\r/mg, '').split('\n'); lines.forEach(line => { let match: RegExpMatchArray; From f6f9d64277148fc7658d88c58636da2273ab6136 Mon Sep 17 00:00:00 2001 From: Chuck Ries Date: Mon, 7 Nov 2016 11:01:37 -0800 Subject: [PATCH 3/4] Fix offline package creation --- gulpfile.js | 82 ++++++++++++++++++++--------------------------------- 1 file changed, 30 insertions(+), 52 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index a1faae5f7..78c3d8a51 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -26,70 +26,49 @@ const PackageManager = packages.PackageManager; const LinuxDistribution = platform.LinuxDistribution; const PlatformInformation = platform.PlatformInformation; -/// used in offline packaging run so does not clean .vsix -function clean() { - cleanDebugger(); - cleanOmnisharp(); +function cleanSync(deleteVsix) { + del.sync('install.*'); + del.sync('.omnisharp-*'); + del.sync('.debugger'); + + if (deleteVsix) { + del.sync('*.vsix'); + } } -gulp.task('clean', ['omnisharp:clean', 'debugger:clean', 'package:clean'], () => { - del.sync('install.*'); +gulp.task('clean', () => { + cleanSync(true); }); -/// Omnisharp Tasks -function installOmnisharp(platformInfo, packageJSON) { +// Install Tasks +function install(platformInfo, packageJSON) { const packageManager = new PackageManager(platformInfo, packageJSON); const logger = new Logger(message => process.stdout.write(message)); + const debuggerUtil = new debugUtil.CoreClrDebugUtil(path.resolve('.'), logger); + const debugInstaller = new debugInstall.DebugInstaller(debuggerUtil); return packageManager.DownloadPackages(logger) .then(() => { return packageManager.InstallPackages(logger); + }) + .then(() => { + return util.touchInstallFile(util.InstallFileType.Lock) + }) + .then(() => { + return debugInstaller.finishInstall(); }); } -function cleanOmnisharp() { - del.sync('.omnisharp-*'); -} - -gulp.task('omnisharp:clean', () => { - cleanOmnisharp(); -}); +gulp.task('install', ['clean'], () => { + util.setExtensionPath(__dirname); -gulp.task('omnisharp:install', ['omnisharp:clean'], () => { return PlatformInformation.GetCurrent() .then(platformInfo => { - return installOmnisharp(platformInfo, getPackageJSON()); + return install(platformInfo, getPackageJSON()); }); }); -/// Debugger Tasks -function getDebugInstaller() { - return new debugInstall.DebugInstaller(new debugUtil.CoreClrDebugUtil(path.resolve('.')), true); -} - -function installDebugger(runtimeId) { - return getDebugInstaller().install(runtimeId); -} - -function cleanDebugger() { - del.sync('.debugger'); -} - -gulp.task('debugger:install', ['debugger:clean'], () => { - installDebugger(gulp.env.runtimeId) - .then(() => { - console.log('Installed Succesfully'); - }) - .catch((error) => { - console.error(error); - }); -}); - -gulp.task('debugger:clean', () => { - cleanDebugger(); -}); - -/// Packaging Tasks +/// Packaging (VSIX) Tasks function doPackageSync(packageName) { var vsceArgs = []; @@ -108,13 +87,12 @@ function doPackageSync(packageName) { } function doOfflinePackage(platformInfo, packageName, packageJSON) { - return clean() - .then(() => { - return installDebugger(platformInfo.runtimeId); - }) - .then(() => { - return installOmnisharp(platformInfo, packageJSON); - }) + if (process.platform === 'win32') { + throw new Error('Do not build offline packages on windows. Runtime executables will not be marked executable in *nix packages.'); + } + + cleanSync(false); + return install(platformInfo, packageJSON) .then(() => { doPackageSync(packageName + '-' + platformInfo.runtimeId + '.vsix'); }); From 248021447ab2f2132c18b81da000b4a0bbef0478 Mon Sep 17 00:00:00 2001 From: Chuck Ries Date: Mon, 7 Nov 2016 13:12:13 -0800 Subject: [PATCH 4/4] Respond to code review feedback --- gulpfile.js | 1 - package.json | 4 ++-- src/common.ts | 6 ++++++ src/coreclr-debug/activate.ts | 14 ++++++------ src/coreclr-debug/install.ts | 1 - src/coreclr-debug/util.ts | 40 +++++------------------------------ src/main.ts | 9 ++++---- 7 files changed, 25 insertions(+), 50 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 78c3d8a51..a700a1779 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -14,7 +14,6 @@ const tslint = require('gulp-tslint'); const vsce = require('vsce'); const debugUtil = require('./out/src/coreclr-debug/util'); const debugInstall = require('./out/src/coreclr-debug/install'); -const fs_extra = require('fs-extra-promise'); const packages = require('./out/src/packages'); const logger = require('./out/src/logger'); const platform = require('./out/src/platform'); diff --git a/package.json b/package.json index 6b425349e..b28f8d1e3 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ ] }, { - "description": "OmniSharp (.NET Core - OSX / x64)", + "description": "OmniSharp (.NET Core - macOS / x64)", "url": "https://omnisharpdownload.blob.core.windows.net/ext/omnisharp-1.9-beta19-osx-x64-netcoreapp1.0.zip", "installPath": ".omnisharp-coreclr", "runtimeIds": [ @@ -200,7 +200,7 @@ ] }, { - "description": ".NET Core Debugger (OSX / x64)", + "description": ".NET Core Debugger (macOS / x64)", "url": "https://vsdebugger.azureedge.net/coreclr-debug-1-5-0/coreclr-debug-osx.10.11-x64.zip", "installPath": ".debugger", "runtimeIds": [ diff --git a/src/common.ts b/src/common.ts index 034818cf4..0ca265554 100644 --- a/src/common.ts +++ b/src/common.ts @@ -21,6 +21,10 @@ export function getExtensionPath() { return extensionPath; } +export function getBinPath() { + return path.resolve(getExtensionPath(), "bin"); +} + export function buildPromiseChain(array: T[], builder: (item: T) => Promise): Promise { return array.reduce( (promise, n) => promise.then(() => builder(n)), @@ -75,6 +79,7 @@ export function touchInstallFile(type: InstallFileType): Promise { fs.writeFile(getInstallFilePath(type), '', err => { if (err) { reject(err); + return; } resolve(); @@ -87,6 +92,7 @@ export function deleteInstallFile(type: InstallFileType): Promise { fs.unlink(getInstallFilePath(type), err => { if (err) { reject(err); + return; } resolve(); diff --git a/src/coreclr-debug/activate.ts b/src/coreclr-debug/activate.ts index a01930457..fc8a828dc 100644 --- a/src/coreclr-debug/activate.ts +++ b/src/coreclr-debug/activate.ts @@ -12,23 +12,23 @@ import * as debugInstall from './install'; import * as path from 'path'; import { Logger } from './../logger' -let _util: CoreClrDebugUtil = null; +let _debugUtil: CoreClrDebugUtil = null; let _reporter: TelemetryReporter = null; let _logger: Logger = null; export function activate(context: vscode.ExtensionContext, reporter: TelemetryReporter, logger: Logger) { - _util = new CoreClrDebugUtil(context.extensionPath, logger); + _debugUtil = new CoreClrDebugUtil(context.extensionPath, logger); _reporter = reporter; _logger = logger; - if (!CoreClrDebugUtil.existsSync(_util.debugAdapterDir())) { + if (!CoreClrDebugUtil.existsSync(_debugUtil.debugAdapterDir())) { // We have been activated but it looks like our package was not installed. This is bad. logger.appendLine("[ERROR]: C# Extension failed to install the debugger package"); showInstallErrorMessage(); - } else if (!CoreClrDebugUtil.existsSync(_util.installCompleteFilePath())) { - _util.checkDotNetCli() + } else if (!CoreClrDebugUtil.existsSync(_debugUtil.installCompleteFilePath())) { + _debugUtil.checkDotNetCli() .then((dotnetInfo: DotnetInfo) => { - let installer = new debugInstall.DebugInstaller(_util); + let installer = new debugInstall.DebugInstaller(_debugUtil); installer.finishInstall() .then(() => { vscode.window.setStatusBarMessage('Successfully installed .NET Core Debugger.'); @@ -42,7 +42,7 @@ export function activate(context: vscode.ExtensionContext, reporter: TelemetryRe }, (err) => { // Check for dotnet tools failed. pop the UI // err is a DotNetCliError but use defaults in the unexpected case that it's not - showDotnetToolsWarning(err.ErrorMessage || _util.defaultDotNetCliErrorMessage()); + showDotnetToolsWarning(err.ErrorMessage || _debugUtil.defaultDotNetCliErrorMessage()); _logger.appendLine(err.ErrorString || err); // TODO: log telemetry? }); diff --git a/src/coreclr-debug/install.ts b/src/coreclr-debug/install.ts index 95df4ec37..c4116f2d2 100644 --- a/src/coreclr-debug/install.ts +++ b/src/coreclr-debug/install.ts @@ -42,7 +42,6 @@ export class InstallError extends Error { export class DebugInstaller { private _util: CoreClrDebugUtil = null; -// private _isOffline; constructor(util: CoreClrDebugUtil) { this._util = util; diff --git a/src/coreclr-debug/util.ts b/src/coreclr-debug/util.ts index a1e9aa709..c19b66842 100644 --- a/src/coreclr-debug/util.ts +++ b/src/coreclr-debug/util.ts @@ -9,6 +9,7 @@ import * as path from 'path'; import * as fs from 'fs'; import * as os from 'os'; import * as semver from 'semver'; +import { execChildProcess } from './../common' import { Logger } from './../logger' const MINIMUM_SUPPORTED_DOTNET_CLI: string = '1.0.0-preview2-003121'; @@ -79,13 +80,14 @@ export class CoreClrDebugUtil // This function checks for the presence of dotnet on the path and ensures the Version // is new enough for us. // Returns: a promise that returns a DotnetInfo class - // Throws: An CotNetCliError() from the return promise if either dotnet does not exist or is too old. + // Throws: An DotNetCliError() from the return promise if either dotnet does not exist or is too old. public checkDotNetCli(): Promise { let dotnetInfo = new DotnetInfo(); - return this.spawnChildProcess('dotnet', ['--info'], process.cwd(), (data: Buffer) => { - let lines: string[] = data.toString().replace(/\r/mg, '').split('\n'); + return execChildProcess('dotnet --info', process.cwd()) + .then((data: string) => { + let lines: string[] = data.replace(/\r/mg, '').split('\n'); lines.forEach(line => { let match: RegExpMatchArray; if (match = /^\ Version:\s*([^\s].*)$/.exec(line)) { @@ -116,38 +118,6 @@ export class CoreClrDebugUtil }); } - public spawnChildProcess(process: string, args: string[], workingDirectory: string, onStdout?: (data: Buffer) => void, onStderr?: (data: Buffer) => void): Promise { - const promise = new Promise((resolve, reject) => { - const child = child_process.spawn(process, args, { cwd: workingDirectory }); - - if (!onStdout) { - onStdout = (data) => { console.log(`${data}`); }; - } - child.stdout.on('data', onStdout); - - if (!onStderr) { - onStderr = (data) => { console.error(`${data}`); }; - } - child.stderr.on('data', onStderr); - - child.on('close', (code: number) => { - if (code != 0) { - console.log(`${process} exited with error code ${code}`);; - reject(new Error(code.toString())); - } - else { - resolve(); - } - }); - - child.on('error', (error: Error) => { - reject(error); - }); - }); - - return promise; - } - public static existsSync(path: string) : boolean { try { fs.accessSync(path, fs.F_OK); diff --git a/src/main.ts b/src/main.ts index f9f427444..e1b14a277 100644 --- a/src/main.ts +++ b/src/main.ts @@ -101,10 +101,6 @@ function installRuntimeDependencies(extension: vscode.Extension, logger: Lo installationStage = 'touchLockFile'; return util.touchInstallFile(util.InstallFileType.Lock); }) - .then(() => { - installationStage = 'deleteBeginFile'; - return util.deleteInstallFile(util.InstallFileType.Begin) - }) .catch(error => { errorMessage = error.toString(); logger.appendLine(`Failed at stage: ${installationStage}`); @@ -118,6 +114,11 @@ function installRuntimeDependencies(extension: vscode.Extension, logger: Lo // TODO: Send telemetry event statusItem.dispose(); + }) + .then(() => { + // We do this step at the end so that we clean up the begin file in the case that we hit above catch block + // Attach a an empty catch to this so that errors here do not propogate + return util.deleteInstallFile(util.InstallFileType.Begin).catch((error) => { }); }); }