diff --git a/README.md b/README.md index 6794d4c525..f08bf77321 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ Welcome to the C# extension for Visual Studio Code! This preview provides the fo The C# extension is powered by [OmniSharp](https://github.com/OmniSharp/omnisharp-roslyn). -### **Important!** Breaking Changes as of 1.0.10 +### **Important!** Breaking Changes as of 1.2 -* The C# extension now only supports [.NET Core RC2](https://blogs.msdn.microsoft.com/dotnet/2016/05/16/announcing-net-core-rc2/). It no longer supports .NET Core RC1 or ASP .NET 5 RC1. +* The C# extension now only supports .NET Core 1.0. Please upgrade any RC1 or RC2 projects to the 1.0.0 release build. * **Support for .csproj projects is partially functional on OS X and Linux.** This will impact anyone doing .csproj development on OS X or Linux (e.g. Unity, Xamarin, etc.). We are working to address this in the near future. However, for now, you can use the [Legacy C# Support extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.omnisharp). ### Found a Bug? @@ -24,17 +24,12 @@ Please file any issues at https://github.com/OmniSharp/omnisharp-vscode/issues. ### Debugging The C# extension now supports basic debugging capabilities! See http://aka.ms/vscclrdebugger for details. -### What's new in C# extension version 1.1 - -* Preliminary support for `dotnet test` -* Fix for OmniSharp installation problems on networks with an http proxy -* Debugger support for an external console -* Debugger support for environment variables -* Support for debugging .NET Core 1.0.0 post RC2 builds -* Automatic web vs. console debugger configuration detection -* Detach support -* Fix expression evaluation errors when referencing assemblies which aren't currently loaded -* Fix expression evaluation on Windows 7 +### What's new in C# extension version 1.2 + +* Adds debugger support for new Linux versions: Ubuntu 16.04, Fedora 23, openSUSE 13.2, and Oracle Linux 7.1 +* Enhanced debug console output: module loads are now output, and there are launch.json options for controlling what is output +* Source file checksum support for breakpoints. This ensures that the debugger only sets breakpoints in code that exactly matches the open document. +* Support for editing the value of variables in the watch and locals window (requires VS Code 1.3) ### Development diff --git a/coreclr-debug/NuGet.config b/coreclr-debug/NuGet.config index 5db080cb7d..dd1fa14307 100644 --- a/coreclr-debug/NuGet.config +++ b/coreclr-debug/NuGet.config @@ -4,5 +4,7 @@ + + diff --git a/debugger.md b/debugger.md index 691c1f5ab9..ff6c3d76a2 100644 --- a/debugger.md +++ b/debugger.md @@ -5,7 +5,7 @@ This page gives you detailed instructions on how to debug code running under .NE File bugs and feature requests [here](https://github.com/OmniSharp/omnisharp-vscode/issues) and [join our insiders group](http://landinghub.visualstudio.com/dotnetcoreinsiders) to help us build great tooling for .NET Core. ####Requirements -* Requires .NET Core RC2 (will not work with earlier versions) +* Requires .NET Core 1.0 RC2 or newer (will not work with earlier versions) * X64 only * Supports OSX, Ubuntu 14.04, Red Hat Enterprise Linux 7.2, Debian 8.2, Centos 7.1, and Windows 7+ @@ -26,7 +26,7 @@ Install the .NET Core command line tools (CLI) by following the installation par ##### 3: Install C# Extension for VS Code Open the command palette in VS Code (F1) and type "ext install C#" to trigger the installation of the extension. VS Code will show a message that the extension has been installed and it will restart. -If you have previously installed the C# extension, make sure that you have version 1.0.0-rc2 or newer. You can check this by opening the command palette (F1) and running 'Extensions: Show Installed Extensions'. +If you have previously installed the C# extension, make sure that you have version 1.1.6 or newer. You can check this by opening the command palette (F1) and running 'Extensions: Show Installed Extensions'. ##### 4: Wait for download of platform-specific files The first time that C# code is opened in VS Code, the extension will download the platform-specific files needed for debugging and editing. Debugging and editor features will not work until these steps finish. @@ -49,20 +49,22 @@ You can also find some example projects on https://github.com/aspnet/cli-samples ##### 2: Open the directory in VS Code Go to File->Open and open the directory in Visual Studio Code. If this is the first time that the C# extension has been activated, it will now download additional platform-specific dependencies. -**Troubleshooting 'Error while installing .NET Core Debugger':** If the debugger is failing to download its platform-specific dependencies, first verify that you have the RC2 build of the .NET CLI installed, and it is functioning. You can check this by starting a bash/command prompt and running 'dotnet --info'. +**Troubleshooting 'Error while installing .NET Core Debugger':** If the debugger is failing to download its platform-specific dependencies, first verify that you have the 1.0.0-preview1-002702 or newer build of the .NET CLI installed, and it is functioning. You can check this by starting a bash/command prompt and running 'dotnet --info'. If the CLI is installed, here are a few additional suggestions: -* If clicking on 'View Log' doesn't show a log this means that running the 'dotnet --info' command failed. If it succeeds in bash/command prompt, but fails from VS Code, this likely means that your computer once had an older build of .NET CLI installed, and there are still remnants of it which cause VS Code and other processes besides bash to use the older version instead of the current version. You can try to clean your computer using the uninstall suggestions from http://dotnet.github.io/getting-started/. +* If clicking on 'View Log' doesn't show a log this means that running the 'dotnet --info' command failed. If it succeeds in bash/command prompt, but fails from VS Code, this likely means that your computer once had an older build of .NET CLI installed, and there are still remnants of it which cause VS Code and other processes besides bash to use the older version instead of the current version. You can resolve this issue by uninstalling the .NET Core CLI, and reinstalling the version you want (see below for macOS). * If 'dotnet restore' is failing, make sure you have an internet connection to nuget.org, and make sure that if additional NuGet.Config files are being used, they have valid content. The log will indicate what NuGet.Config files were used. Try removing the files other than the one coming from the extension itself. +MacOS .NET CLI Reinstall Instructions: macOS doesn't have uninstall for pkg files (see [known issue](https://github.com/dotnet/core/blob/master/cli/known-issues.md#uninstallingreinstalling-the-pkg-on-os-x)), one option is to remove the dotnet cli directory with `sudo rm -rf /usr/local/share/dotnet` and then install the pkg again. + ##### 3: Add VS Code configuration files to the workspace VS Code needs to be configured so it understands how to build your project and debug it. For this there are two files which need to be added -- .vscode/tasks.json and .vscode/launch.json. * Tasks.json is used to configure what command line command is executed to build your project, and launch.json configures the type of debugger you want to use, and what program should be run under that debugger. * Launch.json configures VS Code to run the build task from tasks.json so that your program is automatically up-to-date each time you go to debug it. -If you open the folder containing your project.json, the C# extension can automatically generate these files for you. When you open a project and the C# extension is installed, you should see the following prompt in VS Code: +If you open the folder containing your project.json, the C# extension can automatically generate these files for you if you have a basic project. When you open a project and the C# extension is installed, you should see the following prompt in VS Code: ![Info: Required assets to build and debug are missing from your project. Add them? Yes | Close](https://raw.githubusercontent.com/wiki/OmniSharp/omnisharp-vscode/images/info-bar-add-required-assets.png) @@ -134,3 +136,7 @@ Environment variables may be passed to your program using this schema: The target process can optionally launch into a seperate console window. You will want this if your console app takes console input (ex: Console.ReadLine). This can be enabled with: "externalConsole": true + +#### Docker Support + +Using Visual Studio Code and the C# extension it is also possible to debug your code running in a [Docker container](https://en.wikipedia.org/wiki/Docker_(software)). To do so, follow instructions to install and run [yo docker](https://github.com/Microsoft/generator-docker#generator-docker). This will add files to your project to build a container, and it will add a new debug launch configuration which will invoke a container build, and then debug your app in the container. diff --git a/gulpfile.js b/gulpfile.js index ade7982408..b3a63f25c6 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -126,8 +126,11 @@ gulp.task('package:offline', ['clean'], () => { packages.push({rid: 'osx.10.11-x64', omni: `omnisharp-${OmniSharpVersion}-osx-x64-netcoreapp1.0.tar.gz`}); packages.push({rid: 'centos.7-x64', omni: `omnisharp-${OmniSharpVersion}-centos-x64-netcoreapp1.0.tar.gz`}); packages.push({rid: 'debian.8-x64', omni: `omnisharp-${OmniSharpVersion}-debian-x64-netcoreapp1.0.tar.gz`}); + packages.push({rid: 'fedora.23-x64', omni: `omnisharp-${OmniSharpVersion}-fedora-x64-netcoreapp1.0.tar.gz`}); + packages.push({rid: 'opensuse.13.2-x64', omni: `omnisharp-${OmniSharpVersion}-opensuse-x64-netcoreapp1.0.tar.gz`}); packages.push({rid: 'rhel.7.2-x64', omni: `omnisharp-${OmniSharpVersion}-rhel-x64-netcoreapp1.0.tar.gz`}); - packages.push({rid: 'ubuntu.14.04-x64', omni: `omnisharp-${OmniSharpVersion}-ubuntu-x64-netcoreapp1.0.tar.gz`}); + packages.push({rid: 'ubuntu.14.04-x64', omni: `omnisharp-${OmniSharpVersion}-ubuntu14-x64-netcoreapp1.0.tar.gz`}); + packages.push({rid: 'ubuntu.16.04-x64', omni: `omnisharp-${OmniSharpVersion}-ubuntu16-x64-netcoreapp1.0.tar.gz`}); var promise = Promise.resolve(); diff --git a/package.json b/package.json index cd356df3cb..81374d988e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "csharp", "publisher": "ms-vscode", - "version": "1.1.7", + "version": "1.2.0", "description": "C# for Visual Studio Code (powered by OmniSharp).", "displayName": "C#", "author": "Microsoft Corporation", @@ -19,7 +19,7 @@ ], "main": "./out/omnisharpMain", "scripts": { - "postinstall": "tsc" + "postinstall": "node ./node_modules/vscode/bin/install && tsc" }, "dependencies": { "decompress": "^4.0.0", @@ -39,7 +39,7 @@ "tslint": "^3.3.0", "tslint-microsoft-contrib": "^2.0.0", "typescript": "^1.7.3", - "vscode": "^0.10.1", + "vscode": "^0.11.3", "vsce": "^1.3.0" }, "engines": { @@ -52,6 +52,7 @@ "onCommand:o.showOutput", "onCommand:dotnet.restore", "onCommand:csharp.downloadDebugger", + "onCommand:csharp.listProcess", "workspaceContains:project.json" ], "contributes": { @@ -102,6 +103,11 @@ "command": "csharp.downloadDebugger", "title": "Download .NET Core Debugger", "category": "Debug" + }, + { + "command": "csharp.listProcess", + "title": "List process for attach", + "category": "CSharp" } ], "keybindings": [ @@ -143,6 +149,9 @@ }, "runtime": "node", "runtimeArgs": [], + "variables": { + "pickProcess": "csharp.listProcess" + }, "program": "./out/coreclr-debug/proxy.js", "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "configurationAttributes": { @@ -299,6 +308,44 @@ "description": "Optional flag to require current source code to match the pdb.", "default": true }, + "logging": { + "type": "object", + "required": [], + "default": {}, + "description": "Optional flags to determine what types of messages should be logged to the output window.", + "properties": { + "exceptions": { + "type": "boolean", + "description": "Optional flag to determine whether exception messages should be logged to the output window.", + "default": true + }, + "moduleLoad": { + "type": "boolean", + "description": "Optional flag to determine whether module load events should be logged to the output window.", + "default": true + }, + "programOutput": { + "type": "boolean", + "description": "Optional flag to determine whether program output should be logged to the output window when not using an external console.", + "default": true + }, + "engineLogging": { + "type": "boolean", + "description": "Optional flag to determine whether diagnostic engine logs should be logged to the output window.", + "default": false + }, + "trace": { + "type": "boolean", + "description": "Optional flag to determine whether diagnostic adapter command tracing should be logged to the output window.", + "default": false + }, + "traceResponse": { + "type": "boolean", + "description": "Optional flag to determine whether diagnostic adapter command and response tracing should be logged to the output window.", + "default": false + } + } + }, "pipeTransport": { "type": "object", "description": "When present, this tells the debugger to connect to a remote computer using another executable as a pipe that will relay standard input/output between VS Code and the .NET Core debugger backend executable (clrdbg).", @@ -472,12 +519,50 @@ "items": { "type": "string" }, + "default": [] + }, "requireExactSource": { "type": "boolean", "description": "Optional flag to require current source code to match the pdb.", "default": true }, - "default": [] + "logging": { + "type": "object", + "required": [], + "default": {}, + "description": "Optional flags to determine what types of messages should be logged to the output window.", + "properties": { + "exceptions": { + "type": "boolean", + "description": "Optional flag to determine whether exception messages should be logged to the output window.", + "default": true + }, + "moduleLoad": { + "type": "boolean", + "description": "Optional flag to determine whether module load events should be logged to the output window.", + "default": true + }, + "programOutput": { + "type": "boolean", + "description": "Optional flag to determine whether program output should be logged to the output window when not using an external console.", + "default": true + }, + "engineLogging": { + "type": "boolean", + "description": "Optional flag to determine whether diagnostic engine logs should be logged to the output window.", + "default": false + }, + "trace": { + "type": "boolean", + "description": "Optional flag to determine whether diagnostic adapter command tracing should be logged to the output window.", + "default": false + }, + "traceResponse": { + "type": "boolean", + "description": "Optional flag to determine whether diagnostic adapter command and response tracing should be logged to the output window.", + "default": false + } + } } } } diff --git a/src/coreclr-debug/install.ts b/src/coreclr-debug/install.ts index 58a9fa2718..dac7ff3353 100644 --- a/src/coreclr-debug/install.ts +++ b/src/coreclr-debug/install.ts @@ -162,33 +162,33 @@ export class DebugInstaller emitEntryPoint: true }, dependencies: { - "Microsoft.VisualStudio.clrdbg": "14.0.25406-preview-3044032", - "Microsoft.VisualStudio.clrdbg.MIEngine": "14.0.30610-preview-1", - "Microsoft.VisualStudio.OpenDebugAD7": "1.0.20614-preview-2", - "NETStandard.Library": "1.5.0-rc2-24027", + "Microsoft.VisualStudio.clrdbg": "14.0.25429-preview-3095034", + "Microsoft.VisualStudio.clrdbg.MIEngine": "14.0.30629-preview-1", + "Microsoft.VisualStudio.OpenDebugAD7": "1.0.20628-preview-1", + "NETStandard.Library": "1.6.0", "Newtonsoft.Json": "7.0.1", "Microsoft.VisualStudio.Debugger.Interop.Portable": "1.0.1", - "System.Collections.Specialized": "4.0.1-rc2-24027", - "System.Collections.Immutable": "1.2.0-rc2-24027", - "System.Diagnostics.Process" : "4.1.0-rc2-24027", - "System.Diagnostics.StackTrace": "4.0.1-rc2-24027", - "System.Dynamic.Runtime": "4.0.11-rc2-24027", - "Microsoft.CSharp": "4.0.1-rc2-24027", - "System.Threading.Tasks.Dataflow": "4.6.0-rc2-24027", - "System.Threading.Thread": "4.0.0-rc2-24027", - "System.Xml.XDocument": "4.0.11-rc2-24027", - "System.Xml.XmlDocument": "4.0.1-rc2-24027", - "System.Xml.XmlSerializer": "4.0.11-rc2-24027", - "System.ComponentModel": "4.0.1-rc2-24027", - "System.ComponentModel.Annotations": "4.1.0-rc2-24027", - "System.ComponentModel.EventBasedAsync": "4.0.11-rc2-24027", - "System.Runtime.Serialization.Primitives": "4.1.1-rc2-24027", - "System.Net.Http": "4.0.1-rc2-24027" + "System.Collections.Specialized": "4.0.1", + "System.Collections.Immutable": "1.2.0", + "System.Diagnostics.Process" : "4.1.0", + "System.Diagnostics.StackTrace": "4.0.1", + "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: { } @@ -197,7 +197,7 @@ export class DebugInstaller projectJson.runtimes[targetRuntime] = {}; if (this._isOffline) { - projectJson.dependencies["Microsoft.NetCore.DotNetHostPolicy"] = "1.0.1-rc-002702"; + projectJson.dependencies["Microsoft.NetCore.DotNetHostPolicy"] = "1.0.1"; } return projectJson; diff --git a/src/features/commands.ts b/src/features/commands.ts index 7036e9e494..75ba5edc86 100644 --- a/src/features/commands.ts +++ b/src/features/commands.ts @@ -14,6 +14,7 @@ import * as path from 'path'; import * as protocol from '../protocol'; import * as vscode from 'vscode'; import * as dotnetTest from './dotnetTest' +import {DotNetAttachItemsProviderFactory, AttachPicker} from './processPicker' let channel = vscode.window.createOutputChannel('.NET'); @@ -29,9 +30,14 @@ export default function registerCommands(server: OmnisharpServer, extensionPath: // register two commands for running and debugging xunit tests let d6 = dotnetTest.registerDotNetTestRunCommand(server); - let d7 = dotnetTest.registerDotNetTestDebugCommand(server); - - return vscode.Disposable.from(d1, d2, d3, d4, d5, d6, d7); + let d7 = dotnetTest.registerDotNetTestDebugCommand(server); + + // register process picker for attach + let attachItemsProvider = DotNetAttachItemsProviderFactory.Get(); + let attacher = new AttachPicker(attachItemsProvider); + let d8 = vscode.commands.registerCommand('csharp.listProcess', () => attacher.ShowAttachEntries()); + + return vscode.Disposable.from(d1, d2, d3, d4, d5, d6, d7, d8); } function pickProjectAndStart(server: OmnisharpServer) { diff --git a/src/features/processPicker.ts b/src/features/processPicker.ts new file mode 100644 index 0000000000..ac2ca79ff5 --- /dev/null +++ b/src/features/processPicker.ts @@ -0,0 +1,249 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * See LICENSE.md in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import * as os from 'os'; +import * as vscode from 'vscode'; +import * as child_process from 'child_process'; + +export interface AttachItem extends vscode.QuickPickItem { + id: string; +} + +export interface AttachItemsProvider { + getAttachItems(): Promise; +} + +export class AttachPicker { + constructor(private attachItemsProvider: AttachItemsProvider) { } + + public ShowAttachEntries(): Promise { + return this.attachItemsProvider.getAttachItems() + .then(processEntries => { + let attachPickOptions: vscode.QuickPickOptions = { + matchOnDescription: true, + matchOnDetail: true, + placeHolder: "Select the process to attach to" + }; + + return vscode.window.showQuickPick(processEntries, attachPickOptions) + .then(chosenProcess => { + return chosenProcess ? chosenProcess.id : null; + }); + }); + } +} + +class Process { + constructor(public name: string, public pid: string, public commandLine: string) { } + + public toAttachItem(): AttachItem { + return { + label: this.name, + description: this.pid, + detail: this.commandLine, + id: this.pid + }; + } +} + +export class DotNetAttachItemsProviderFactory { + static Get(): AttachItemsProvider { + if (os.platform() === 'win32') { + return new WmicAttachItemsProvider(); + } + else { + return new PsAttachItemsProvider(); + } + } +} + +abstract class DotNetAttachItemsProvider implements AttachItemsProvider { + protected abstract getInternalProcessEntries(): Promise; + + getAttachItems(): Promise { + return this.getInternalProcessEntries().then(processEntries => { + // localeCompare is significantly slower than < and > (2000 ms vs 80 ms for 10,000 elements) + // We can change to localeCompare if this becomes an issue + let dotnetProcessName = (os.platform() === 'win32') ? 'dotnet.exe' : 'dotnet'; + processEntries = processEntries.sort((a, b) => { + if (a.name.toLowerCase() === dotnetProcessName && b.name.toLowerCase() === dotnetProcessName) { + return a.commandLine.toLowerCase() < b.commandLine.toLowerCase() ? -1 : 1; + } else if (a.name.toLowerCase() === dotnetProcessName) { + return -1; + } else if (b.name.toLowerCase() === dotnetProcessName) { + return 1; + } else { + return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1 + } + }); + + let attachItems = processEntries.map(p => p.toAttachItem()); + return attachItems; + }); + } +} + +export class PsAttachItemsProvider extends DotNetAttachItemsProvider { + // Perf numbers: + // OS X 10.10 + // | # of processes | Time (ms) | + // |----------------+-----------| + // | 272 | 52 | + // | 296 | 49 | + // | 384 | 53 | + // | 784 | 116 | + // + // Ubuntu 16.04 + // | # of processes | Time (ms) | + // |----------------+-----------| + // | 232 | 26 | + // | 336 | 34 | + // | 736 | 62 | + // | 1039 | 115 | + // | 1239 | 182 | + + // ps outputs as a table. With the option "ww", ps will use as much width as necessary. + // However, that only applies to the right-most column. Here we use a hack of setting + // the column header to 50 a's so that the second column will have at least that many + // characters. 50 was chosen because that's the maximum length of a "label" in the + // QuickPick UI in VSCode. + private static get secondColumnCharacters() { return 50; } + + protected getInternalProcessEntries(): Promise { + const commColumnTitle = Array(PsAttachItemsProvider.secondColumnCharacters).join("a"); + // the BSD version of ps uses '-c' to have 'comm' only output the executable name and not + // the full path. The Linux version of ps has 'comm' to only display the name of the executable + // Note that comm on Linux systems is truncated to 16 characters: + // https://bugzilla.redhat.com/show_bug.cgi?id=429565 + // Since 'args' contains the full path to the executable, even if truncated, searching will work as desired. + const psCommand = `ps -axww -o pid=,comm=${commColumnTitle},args=` + (os.platform() === 'darwin' ? ' -c' : ''); + return execChildProcess(psCommand, null).then(processes => { + return this.parseProcessFromPs(processes); + }); + } + + // Only public for tests. + public parseProcessFromPs(processes: string): Process[] { + let lines = processes.split(os.EOL); + let processEntries: Process[] = []; + + // lines[0] is the header of the table + for (let i = 1; i < lines.length; i++) { + let line = lines[i]; + if (!line) { + continue; + } + + let process = this.parseLineFromPs(line); + processEntries.push(process); + } + + return processEntries; + } + + private parseLineFromPs(line: string): Process { + // Explanation of the regex: + // - any leading whitespace + // - PID + // - whitespace + // - executable name --> this is PsAttachItemsProvider.secondColumnCharacters - 1 because ps reserves one character + // for the whitespace separator + // - whitespace + // - args (might be empty) + const psEntry = new RegExp(`^\\s*([0-9]+)\\s+(.{${PsAttachItemsProvider.secondColumnCharacters - 1}})\\s+(.*)$`); + const matches = psEntry.exec(line); + if (matches && matches.length === 4) { + const pid = matches[1].trim(); + const executable = matches[2].trim(); + const cmdline = matches[3].trim(); + return new Process(executable, pid, cmdline); + } + } +} + +export class WmicAttachItemsProvider extends DotNetAttachItemsProvider { + // Perf numbers on Win10: + // | # of processes | Time (ms) | + // |----------------+-----------| + // | 309 | 413 | + // | 407 | 463 | + // | 887 | 746 | + // | 1308 | 1132 | + + private static get wmicNameTitle() { return 'Name'; } + private static get wmicCommandLineTitle() { return 'CommandLine'; } + private static get wmicPidTitle() { return 'ProcessId'; } + + protected getInternalProcessEntries(): Promise { + const wmicCommand = 'wmic process get Name,ProcessId,CommandLine /FORMAT:list'; + return execChildProcess(wmicCommand, null).then(processes => { + return this.parseProcessFromWmic(processes); + }); + } + + // Only public for tests. + public parseProcessFromWmic(processes: string): Process[] { + let lines = processes.split(os.EOL); + let currentProcess: Process = new Process(null, null, null); + let processEntries: Process[] = []; + + for (let i = 0; i < lines.length; i++) { + let line = lines[i]; + if (!line) { + continue; + } + + this.parseLineFromWmic(line, currentProcess); + + // Each entry of processes has ProcessId as the last line + if (line.startsWith(WmicAttachItemsProvider.wmicPidTitle)) { + processEntries.push(currentProcess); + currentProcess = new Process(null, null, null); + } + } + + return processEntries; + } + + private parseLineFromWmic(line: string, process: Process) { + let splitter = line.indexOf('='); + if (splitter >= 0) { + let key = line.slice(0, line.indexOf('=')); + let value = line.slice(line.indexOf('=') + 1); + if (key === WmicAttachItemsProvider.wmicNameTitle) { + process.name = value.trim(); + } + else if (key === WmicAttachItemsProvider.wmicPidTitle) { + process.pid = value.trim(); + } + else if (key === WmicAttachItemsProvider.wmicCommandLineTitle) { + const extendedLengthPath = '\\??\\'; + if (value.startsWith(extendedLengthPath)) { + value = value.slice(extendedLengthPath.length).trim(); + } + + process.commandLine = value.trim(); + } + } + } +} + +function execChildProcess(process: string, workingDirectory: string): Promise { + return new Promise((resolve, reject) => { + child_process.exec(process, { cwd: workingDirectory, maxBuffer: 500 * 1024 }, (error: Error, stdout: string, stderr: string) => { + if (error) { + reject(error); + return; + } + + if (stderr && stderr.length > 0) { + reject(new Error(stderr)); + return; + } + + resolve(stdout); + }); + }); +} \ No newline at end of file diff --git a/src/omnisharpDownload.ts b/src/omnisharpDownload.ts index 0af1e4615f..44a77d000c 100644 --- a/src/omnisharpDownload.ts +++ b/src/omnisharpDownload.ts @@ -18,7 +18,7 @@ const decompress = require('decompress'); const BaseDownloadUrl = 'https://omnisharpdownload.blob.core.windows.net/ext'; const DefaultInstallLocation = path.join(__dirname, '../.omnisharp'); -export const OmniSharpVersion = '1.9-beta10'; +export const OmniSharpVersion = '1.9-beta11'; tmp.setGracefulCleanup(); @@ -32,10 +32,16 @@ export function getOmnisharpAssetName(): string { return `omnisharp-${OmniSharpVersion}-centos-x64-netcoreapp1.0.tar.gz`; case SupportedPlatform.Debian: return `omnisharp-${OmniSharpVersion}-debian-x64-netcoreapp1.0.tar.gz`; + case SupportedPlatform.Fedora: + return `omnisharp-${OmniSharpVersion}-fedora-x64-netcoreapp1.0.tar.gz`; + case SupportedPlatform.OpenSUSE: + return `omnisharp-${OmniSharpVersion}-opensuse-x64-netcoreapp1.0.tar.gz`; case SupportedPlatform.RHEL: return `omnisharp-${OmniSharpVersion}-rhel-x64-netcoreapp1.0.tar.gz`; - case SupportedPlatform.Ubuntu: - return `omnisharp-${OmniSharpVersion}-ubuntu-x64-netcoreapp1.0.tar.gz`; + case SupportedPlatform.Ubuntu14: + return `omnisharp-${OmniSharpVersion}-ubuntu14-x64-netcoreapp1.0.tar.gz`; + case SupportedPlatform.Ubuntu16: + return `omnisharp-${OmniSharpVersion}-ubuntu16-x64-netcoreapp1.0.tar.gz`; default: if (process.platform === 'linux') { diff --git a/src/utils.ts b/src/utils.ts index c9a5958e88..f9708ce866 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -13,8 +13,11 @@ export enum SupportedPlatform { OSX, CentOS, Debian, + Fedora, + OpenSUSE, RHEL, - Ubuntu + Ubuntu14, + Ubuntu16 } export function getSupportedPlatform() { @@ -25,20 +28,58 @@ export function getSupportedPlatform() { return SupportedPlatform.OSX; } else if (process.platform === 'linux') { - // Get the text of /etc/*-release to discover which Linux distribution we're running on. - let release = child_process.execSync('cat /etc/os-release').toString().toLowerCase(); + // Get the text of /etc/os-release to discover which Linux distribution we're running on. + // For details: https://www.freedesktop.org/software/systemd/man/os-release.html + const text = child_process.execSync('cat /etc/os-release').toString(); + const lines = text.split('\n'); - if (release.indexOf('ubuntu') >= 0) { - return SupportedPlatform.Ubuntu; - } - else if (release.indexOf('centos') >= 0) { - return SupportedPlatform.CentOS; - } - else if (release.indexOf('rhel') >= 0) { - return SupportedPlatform.RHEL; + function getValue(name: string) { + for (let line of lines) { + line = line.trim(); + if (line.startsWith(name)) { + const equalsIndex = line.indexOf('='); + if (equalsIndex >= 0) { + let value = line.substring(equalsIndex + 1); + + // Strip double quotes if necessary + if (value.length > 1 && value.startsWith('"') && value.endsWith('"')) { + value = value.substring(1, value.length - 2); + } + + return value; + } + } + } + + return undefined; } - else if (release.indexOf('debian') >= 0) { - return SupportedPlatform.Debian; + + const id = getValue("ID"); + + switch (id) + { + case 'ubuntu': + const versionId = getValue("VERSION_ID"); + if (versionId.startsWith("14")) { + // This also works for Linux Mint + return SupportedPlatform.Ubuntu14; + } + else if (versionId.startsWith("16")) { + return SupportedPlatform.Ubuntu16; + } + case 'centos': + return SupportedPlatform.CentOS; + case 'fedora': + return SupportedPlatform.Fedora; + case 'opensuse': + return SupportedPlatform.OpenSUSE; + case 'rehl': + return SupportedPlatform.RHEL; + case 'debian': + return SupportedPlatform.Debian; + case 'ol': + // Oracle Linux is binary compatible with CentOS + return SupportedPlatform.CentOS; } }