diff --git a/.gitignore b/.gitignore
index 5bf6ff89bf..10415c3b8e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
bin
node_modules
out
+
+*.vsix
diff --git a/.vscode/launch.json b/.vscode/launch.json
index fe98036b66..8e2238222f 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -11,7 +11,7 @@
],
"stopOnEntry": false,
"sourceMaps": true,
- "outDir": "out"
+ "outDir": "${workspaceRoot}/out"
}
]
}
\ No newline at end of file
diff --git a/.vscodeignore b/.vscodeignore
new file mode 100644
index 0000000000..76287b7c1c
--- /dev/null
+++ b/.vscodeignore
@@ -0,0 +1,12 @@
+**/*.gitignore
+tsconfig.json
+
+src/**
+**/*.map
+
+.vscode/**
+
+coreclr-debug/debugAdapters/**
+coreclr-debug/bin/**
+coreclr-debug/obj/**
+coreclr-debug/project.lock.json
\ No newline at end of file
diff --git a/coreclr-debug/.gitignore b/coreclr-debug/.gitignore
new file mode 100644
index 0000000000..4486b48fcd
--- /dev/null
+++ b/coreclr-debug/.gitignore
@@ -0,0 +1,4 @@
+bin
+obj
+project.lock.json
+debugAdapters
\ No newline at end of file
diff --git a/coreclr-debug/NuGet.config b/coreclr-debug/NuGet.config
new file mode 100644
index 0000000000..ae37d1c524
--- /dev/null
+++ b/coreclr-debug/NuGet.config
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/coreclr-debug/dummy.cs b/coreclr-debug/dummy.cs
new file mode 100644
index 0000000000..15513fc2c5
--- /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 0000000000..a4d8a8eb1f
--- /dev/null
+++ b/coreclr-debug/project.json
@@ -0,0 +1,33 @@
+{
+ "name": "dummy",
+ "compilationOptions": {
+ "emitEntryPoint": true
+ },
+ "dependencies": {
+ "Microsoft.VisualStudio.clrdbg": "14.0.25017-pb-2822693",
+ "Microsoft.VisualStudio.clrdbg.MIEngine": "14.0.30217-pb-1",
+ "Microsoft.VisualStudio.OpenDebugAD7": "1.0.20217-pb-1",
+ "NETStandard.Library": "1.0.0-rc3-23803",
+ "Newtonsoft.Json": "7.0.1",
+ "Microsoft.VisualStudio.Debugger.Interop.Portable": "1.0.1",
+ "System.Collections.Specialized": "4.0.1-rc3-23803",
+ "System.Collections.Immutable": "1.2.0-rc3-23803",
+ "System.Diagnostics.Process" : "4.1.0-rc3-23803",
+ "System.Diagnostics.StackTrace": "4.0.1-rc3-23803",
+ "System.Dynamic.Runtime": "4.0.11-rc3-23803",
+ "Microsoft.CSharp": "4.0.1-rc3-23803",
+ "System.Threading.Tasks.Dataflow": "4.6.0-rc3-23803",
+ "System.Threading.Thread": "4.0.0-rc3-23803",
+ "System.Xml.XDocument": "4.0.11-rc3-23803",
+ "System.Xml.XmlDocument": "4.0.1-rc3-23803",
+ "System.Xml.XmlSerializer": "4.0.11-rc3-23803",
+ "System.ComponentModel": "4.0.1-rc3-23803",
+ "System.ComponentModel.Annotations": "4.1.0-rc3-23803",
+ "System.ComponentModel.EventBasedAsync": "4.0.11-rc3-23803",
+ "System.Runtime.Serialization.Primitives": "4.1.0-rc3-23803",
+ "System.Net.Http": "4.0.1-rc3-23803"
+ },
+ "frameworks": {
+ "dnxcore50": { }
+ }
+}
diff --git a/package.json b/package.json
index ffb0e4fc40..be8ef75e37 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
},
"activationEvents": [
"onLanguage:csharp",
+ "onLanguage:fsharp",
"onCommand:o.restart",
"onCommand:o.pickProjectAndStart",
"onCommand:o.restore",
@@ -128,6 +129,228 @@
"language": "csharp",
"path": "./snippets/csharp.json"
}
+ ],
+ "debuggers": [
+ {
+ "type": "coreclr",
+ "label": ".NET Core",
+ "enableBreakpointsFor": { "languageIds": [ "csharp", "fsharp" ] },
+
+ "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 0000000000..ded6f8096b
--- /dev/null
+++ b/src/coreclr-debug.ts
@@ -0,0 +1,243 @@
+'use strict';
+
+import * as vscode from 'vscode';
+import * as child_process from 'child_process';
+import * as fs from 'fs';
+import * as path from 'path';
+
+var _coreClrDebugDir: string;
+var _debugAdapterDir: string;
+var _channel: vscode.OutputChannel;
+
+export function installCoreClrDebug(context: vscode.ExtensionContext) {
+ _coreClrDebugDir = path.join(context.extensionPath, 'coreclr-debug');
+ _debugAdapterDir = path.join(_coreClrDebugDir, 'debugAdapters');
+ _channel = vscode.window.createOutputChannel('coreclr-debug');
+
+ if (existsSync(_debugAdapterDir)) {
+ console.log('.NET Core Debugger tools already installed');
+ return;
+ }
+
+ _channel.appendLine("Downloading and configuring the .NET Core Debugger...");
+ _channel.show(vscode.ViewColumn.Three);
+
+ spawnChildProcess('dotnet', ['restore'], _channel, _coreClrDebugDir)
+ .then(function() {
+ return spawnChildProcess('dotnet', ['publish', '-o', _debugAdapterDir], _channel, _coreClrDebugDir);
+ }).then(function() {
+ var promises: Promise[] = [];
+
+ promises.push(renameDummyEntrypoint());
+ promises = promises.concat(copyFiles(context));
+ promises.push(removeLibCoreClrTraceProvider());
+
+ return Promise.all(promises);
+ }).then(function() {
+ _channel.appendLine('Succesfully installed .NET Core Debugger.');
+ })
+ .catch(function(error) {
+ _channel.appendLine('Error while installing .NET Core Debugger.');
+ console.log(error);
+ });
+}
+
+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 copyFiles(context: vscode.ExtensionContext): Promise[] {
+ // TODO: This method and all invocations can be removed once
+ // the dotnet cli tools support nuget packages with
+ // contentFiles metadata
+ // https://docs.nuget.org/create/nuspec-reference#contentfiles-with-visual-studio-2015-update-1-and-later
+
+ var projectJson = JSON.parse(fs.readFileSync(path.join(_coreClrDebugDir, 'project.json')).toString());
+
+ var clrdbgId = 'Microsoft.VisualStudio.clrdbg';
+ var clrdbgVersion: string = projectJson.dependencies[clrdbgId];
+
+ var MIEngineId = 'Microsoft.VisualStudio.clrdbg.MIEngine';
+ var MIEngineVersion: string = projectJson.dependencies[MIEngineId];
+
+ var destRoot: string = _debugAdapterDir;
+
+ var packagesRoot = getPackagesRoot();
+ var clrdbgRoot = path.join(packagesRoot, clrdbgId, clrdbgVersion);
+ var MIEngineRoot = path.join(packagesRoot, MIEngineId, MIEngineVersion);
+
+ var promises: Promise[] = [];
+
+ function copyClrdbg(src: string, dest?: string) { promises.push(copy(path.join(clrdbgRoot, src), dest)); }
+ function copyMIEngine(src: string, dest?: string) { promises.push(copy(path.join(MIEngineRoot, src), dest)); }
+
+ copyClrdbg(path.join('1033', 'clrdbg.resources.dll'), '1033');
+ copyClrdbg(path.join('1033', 'vsdebugeng.impl.resources.dll'), '1033');
+ copyClrdbg(path.join('1033', 'VSDebugUI.dll'), '1033');
+ copyClrdbg('libclrdbg.vsdconfig');
+ copyClrdbg('Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator.ExpressionCompiler.vsdconfig');
+ copyClrdbg('Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator.ResultProvider.vsdconfig');
+ copyClrdbg('version.txt');
+ copyClrdbg('vsdebugeng.impl.vsdconfig');
+ copyClrdbg('vsdebugeng.manimpl.vsdconfig');
+
+ copyMIEngine('coreclr.ad7Engine.json');
+
+ return promises;
+}
+
+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 copy(src: string, dest?: string) : Promise {
+ var destination = _debugAdapterDir;
+ if (dest) {
+ destination = path.join(destination, dest);
+ }
+
+ return new Promise(function(resolve, reject) {
+
+ if (!existsSync(destination)) {
+ fs.mkdirSync(destination);
+ }
+
+ var destFile = path.join(destination, path.basename(src))
+
+ var sourceStream = fs.createReadStream(src);
+ sourceStream.on('error', reject);
+ var destStream = fs.createWriteStream(destFile);
+ destStream.on('error', reject);
+ destStream.on('finish', function() {
+ _channel.appendLine('Succesfully copied ' + src + ' to ' + destination);
+ resolve();
+ });
+ sourceStream.pipe(destStream);
+ });
+}
+
+// TODO: not currently used but may need to be used for updating.
+// TODO: wrap this in try catch for i/o errors
+function deleteDirectoryRecursivelySync(dirPath: string) {
+ if (existsSync(dirPath)) {
+ fs.readdirSync(dirPath).forEach(function(file, index) {
+ var currentPath = path.join(dirPath, file);
+ if (fs.lstatSync(currentPath).isDirectory()) {
+ deleteDirectoryRecursivelySync(currentPath);
+ }
+ else {
+ fs.unlinkSync(currentPath);
+ }
+ });
+ fs.rmdirSync(dirPath);
+ }
+}
+
+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 getPackagesRoot() : string {
+ var homedir = getHomeDir();
+ return path.join(homedir, '.nuget', 'packages');
+}
+
+function getHomeDir() : string {
+ switch (process.platform) {
+ case "win32":
+ return process.env['USERPROFILE'];
+ case "darwin":
+ case "linux":
+ return process.env['HOME'];
+ default:
+ throw new Error('Unsupported platform: ' + process.platform);
+ }
+}
+
+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 5c31ee507c..6ce667295d 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 73d8ec0fc6..a6e763bd2e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"noLib": true,
- "target": "ES5",
+ "target": "es5",
"module": "commonjs",
"outDir": "out",
"sourceMap": true