diff --git a/.gitignore b/.gitignore index 5bf6ff89b..10415c3b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ bin node_modules out + +*.vsix diff --git a/.vscodeignore b/.vscodeignore new file mode 100644 index 000000000..d8cdc7b5b --- /dev/null +++ b/.vscodeignore @@ -0,0 +1,13 @@ +**/*.gitignore +tsconfig.json + +src/** +**/*.map + +.vscode/** + +coreclr-debug/debugAdapters/** +coreclr-debug/bin/** +coreclr-debug/obj/** +coreclr-debug/project.lock.json +coreclr-debug/install.log \ No newline at end of file diff --git a/coreclr-debug/.gitignore b/coreclr-debug/.gitignore new file mode 100644 index 000000000..0541f8ebe --- /dev/null +++ b/coreclr-debug/.gitignore @@ -0,0 +1,5 @@ +bin +obj +project.lock.json +debugAdapters +install.log \ No newline at end of file diff --git a/coreclr-debug/NuGet.config b/coreclr-debug/NuGet.config new file mode 100644 index 000000000..9567dad4b --- /dev/null +++ b/coreclr-debug/NuGet.config @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/coreclr-debug/dummy.cs b/coreclr-debug/dummy.cs new file mode 100644 index 000000000..15513fc2c --- /dev/null +++ b/coreclr-debug/dummy.cs @@ -0,0 +1,13 @@ +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/coreclr-debug/project.json b/coreclr-debug/project.json new file mode 100644 index 000000000..5524d23c4 --- /dev/null +++ b/coreclr-debug/project.json @@ -0,0 +1,33 @@ +{ + "name": "dummy", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "Microsoft.VisualStudio.clrdbg": "14.0.25025-preview-2839567", + "Microsoft.VisualStudio.clrdbg.MIEngine": "14.0.30225-preview-2", + "Microsoft.VisualStudio.OpenDebugAD7": "1.0.20225-preview-2", + "NETStandard.Library": "1.0.0-rc3-23819", + "Newtonsoft.Json": "7.0.1", + "Microsoft.VisualStudio.Debugger.Interop.Portable": "1.0.1", + "System.Collections.Specialized": "4.0.1-rc3-23819", + "System.Collections.Immutable": "1.2.0-rc3-23819", + "System.Diagnostics.Process" : "4.1.0-rc3-23819", + "System.Diagnostics.StackTrace": "4.0.1-rc3-23819", + "System.Dynamic.Runtime": "4.0.11-rc3-23819", + "Microsoft.CSharp": "4.0.1-rc3-23819", + "System.Threading.Tasks.Dataflow": "4.6.0-rc3-23819", + "System.Threading.Thread": "4.0.0-rc3-23819", + "System.Xml.XDocument": "4.0.11-rc3-23819", + "System.Xml.XmlDocument": "4.0.1-rc3-23819", + "System.Xml.XmlSerializer": "4.0.11-rc3-23819", + "System.ComponentModel": "4.0.1-rc3-23819", + "System.ComponentModel.Annotations": "4.1.0-rc3-23819", + "System.ComponentModel.EventBasedAsync": "4.0.11-rc3-23819", + "System.Runtime.Serialization.Primitives": "4.1.0-rc3-23819", + "System.Net.Http": "4.0.1-rc3-23819" + }, + "frameworks": { + "dnxcore50": { } + } +} diff --git a/package.json b/package.json index 698e39dcc..b0c6a0254 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "tslint-microsoft-contrib": "^2.0.0" }, "engines": { - "vscode": "^0.10.1" + "vscode": "^0.10.10" }, "activationEvents": [ "onLanguage:csharp", @@ -128,6 +128,228 @@ "language": "csharp", "path": "./snippets/csharp.json" } + ], + "debuggers": [ + { + "type": "coreclr", + "label": ".NET Core", + "enableBreakpointsFor": { "languageIds": [ "csharp" ] }, + + "program": "./coreclr-debug/debugAdapters/OpenDebugAD7", + "windows": { + "program": "./coreclr-debug/debugAdapters/OpenDebugAD7.exe" + }, + + "configurationAttributes": { + "launch": { + "required": [ "program", "cwd" ], + "properties": { + "program": { + "type": "string", + "description": "Path to the program (executable file) to launch. On Windows, a '.exe' suffix is appended if not specified already.", + "default": "${workspaceRoot}/bin/Debug/dnxcore50/" + }, + "cwd": { + "type": "string", + "description": "Path to the working directory of the program being debugged. Default is the current workspace.", + "default": "${workspaceRoot}" + }, + "args": { + "type": "array", + "description": "Command line arguments passed to the program.", + "items": { "type": "string" }, + "default": [ ] + }, + "stopAtEntry": { + "type": "boolean", + "description": "If true, the debugger should stop at the entry point of the target.", + "default": false + }, + "launchBrowser": { + "type": "object", + "description": "Describes options to launch a web browser as part of launch", + "default": { + "enabled": true, + "args": "${auto-detect-url}", + "windows": { + "command": "cmd.exe", + "args": "/C start ${auto-detect-url}" + }, + "osx": { + "command": "open" + }, + "linux": { + "command": "xdg-open" + } + }, + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether web browser launch is enabled", + "default": true + }, + "args": { + "type": "string", + "description": "The arguments to pass to the command to open the browser. Use ${auto-detect-url} to automatically use the address the server is listening to", + "default": "${auto-detect-url}" + }, + "osx": { + "type": "object", + "description": "OSX-specific web launch configuration options", + "default": { + "command": "open" + }, + "properties": { + "command": { + "type": "string", + "description": "The command to execute for launching the web browser", + "default": "open" + }, + "args": { + "type": "string", + "description": "The arguments to pass to the command to open the browser. Use ${auto-detect-url} to automatically use the address the server is listening to", + "default": "${auto-detect-url}" + } + } + }, + "linux": { + "type": "object", + "description": "Linux-specific web launch configuration options", + "default": { + "command": "xdg-open" + }, + "properties": { + "command": { + "type": "string", + "description": "The command to execute for launching the web browser", + "default": "xdg-open" + }, + "args": { + "type": "string", + "description": "The arguments to pass to the command to open the browser. Use ${auto-detect-url} to automatically use the address the server is listening to", + "default": "${auto-detect-url}" + } + } + }, + "windows": { + "type": "object", + "description": "Windows-specific web launch configuration options", + "default": { + "command": "cmd.exe", + "args": "/C start ${auto-detect-url}" + }, + "properties": { + "command": { + "type": "string", + "description": "The command to execute for launching the web browser", + "default": "cmd.exe" + }, + "args": { + "type": "string", + "description": "The arguments to pass to the command to open the browser. Use ${auto-detect-url} to automatically use the address the server is listening to", + "default": "/C start ${auto-detect-url}" + } + } + } + } + }, + "sourceFileMap": { + "type": "object", + "description": "Optional source file mappings passed to the debug engine.", + "default": { } + }, + "justMyCode": { + "type": "boolean", + "description": "Optional flag to only show user code.", + "default": true + }, + "symbolPath": { + "type": "array", + "description": "Array of directories to use to search for .pdb files. These directories will be searched in addition to the default locations -- next to the module and the path where the pdb was originally dropped to. Example: '[ \"/Volumes/symbols\" ]", + "items": { "type": "string" }, + "default": [ ] + } + } + }, + "attach": { + "required": [ ], + "properties": { + "processName": { + "type": "string", + "description": "", + "default": "The process name to attach to. If this is used, 'processId' should not be used." + }, + "processId": { + "type": "integer", + "description": "The process id to attach to. If this is used, 'processName' should not be used.", + "default": "" + }, + "sourceFileMap": { + "type": "object", + "description": "Optional source file mappings passed to the debug engine.", + "default": { } + }, + "justMyCode": { + "type": "boolean", + "description": "Optional flag to only show user code.", + "default": true + }, + "symbolPath": { + "type": "array", + "description": "Array of directories to use to search for .pdb files. These directories will be searched in addition to the default locations -- next to the module and the path where the pdb was originally dropped to. Example: '[ \"~/symbols\" ]", + "items": { "type": "string" }, + "default": [ ] + } + } + } + }, + + "initialConfigurations": [ + { + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceRoot}/bin/Debug/dnxcore50/", + "args": [ ], + "cwd": "${workspaceRoot}", + "stopAtEntry": false, + "sourceFileMap": { } + }, + { + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceRoot}/bin/Debug/dnxcore50/", + "args": [ ], + "cwd": "${workspaceRoot}", + "stopAtEntry": false, + "sourceFileMap": { }, + "launchBrowser": { + "enabled": true, + "args": "${auto-detect-url}", + "windows": { + "command": "cmd.exe", + "args": "/C start ${auto-detect-url}" + }, + "osx": { + "command": "open" + }, + "linux": { + "command": "xdg-open" + } + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processName": "", + "sourceFileMap": { } + } + ] + } ] } } \ No newline at end of file diff --git a/src/coreclr-debug.ts b/src/coreclr-debug.ts new file mode 100644 index 000000000..afa8e2a56 --- /dev/null +++ b/src/coreclr-debug.ts @@ -0,0 +1,174 @@ +/*--------------------------------------------------------------------------------------------- + * 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'; + +let _coreClrDebugDir: string; +let _debugAdapterDir: string; +let _channel: vscode.OutputChannel; +let _installLog: NodeJS.WritableStream; +const _completionFileName: string = 'install.complete'; + +export function installCoreClrDebug(context: vscode.ExtensionContext) { + _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; + } + + _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() { + var proxied = _channel.append; + _channel.append = function(val: string) { + _installLog.write(val); + proxied.apply(this, arguments); + }; + })(); + + _channel.appendLine("Downloading and configuring the .NET Core Debugger..."); + _channel.show(vscode.ViewColumn.Three); + + spawnChildProcess('dotnet', ['--verbose', 'restore'], _channel, _coreClrDebugDir) + .then(function() { + return spawnChildProcess('dotnet', ['--verbose', 'publish', '-o', _debugAdapterDir], _channel, _coreClrDebugDir); + }).then(function() { + var promises: Promise[] = []; + + promises.push(renameDummyEntrypoint()); + promises.push(removeLibCoreClrTraceProvider()); + + return Promise.all(promises); + }).then(function() { + return writeCompletionFile(); + }).then(function() { + _channel.appendLine('Succesfully installed .NET Core Debugger.'); + }) + .catch(function(error) { + _channel.appendLine('Error while installing .NET Core Debugger.'); + console.log(error); + }); +} + +function writeCompletionFile() : Promise { + return new Promise(function(resolve, reject) { + fs.writeFile(path.join(_debugAdapterDir, _completionFileName), '', function(err) { + if (err) { + reject(err); + } + else { + resolve(); + } + }); + }); +} + +function renameDummyEntrypoint() : Promise { + var src = path.join(_debugAdapterDir, 'dummy'); + var dest = path.join(_debugAdapterDir, 'OpenDebugAD7'); + + src += getPlatformExeExtension(); + dest += getPlatformExeExtension(); + + var promise = new Promise(function(resolve, reject) { + fs.rename(src, dest, function(err) { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + + return promise; +} + +function removeLibCoreClrTraceProvider() : Promise +{ + var 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); + } 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 err; + } + } +} + +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); + } +} + +function spawnChildProcess(process: string, args: string[], channel: vscode.OutputChannel, workingDirectory: string) : Promise { + var 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/omnisharpMain.ts b/src/omnisharpMain.ts index 5c31ee507..6ce667295 100644 --- a/src/omnisharpMain.ts +++ b/src/omnisharpMain.ts @@ -24,6 +24,7 @@ import forwardChanges from './features/changeForwarding'; import reportStatus from './features/omnisharpStatus'; import findLaunchTargets from './launchTargetFinder'; import {Disposable, ExtensionContext, DocumentSelector, languages, extensions} from 'vscode'; +import {installCoreClrDebug} from './coreclr-debug'; export function activate(context: ExtensionContext): any { @@ -75,6 +76,9 @@ export function activate(context: ExtensionContext): any { advisor.dispose(); server.stop(); })); + + // install coreclr-debug + installCoreClrDebug(context); context.subscriptions.push(...disposables); } diff --git a/tsconfig.json b/tsconfig.json index 73d8ec0fc..a6e763bd2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "noLib": true, - "target": "ES5", + "target": "es5", "module": "commonjs", "outDir": "out", "sourceMap": true