Skip to content

Commit

Permalink
Add prerequisite check for running OmniSharp.
Browse files Browse the repository at this point in the history
  • Loading branch information
JoeRobich committed Oct 8, 2022
1 parent 9336244 commit f9b4682
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 12 deletions.
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,8 @@
"darwin"
],
"architectures": [
"x86_64"
"x86_64",
"arm64"
],
"binaries": [
"./mono.osx",
Expand Down Expand Up @@ -333,7 +334,7 @@
},
{
"id": "OmniSharp",
"description": "OmniSharp for Linux (Framework / arm64)",
"description": "OmniSharp for Linux (Mono / arm64)",
"url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.1/omnisharp-linux-arm64-1.39.1.zip",
"installPath": ".omnisharp/1.39.1",
"platforms": [
Expand Down Expand Up @@ -1013,7 +1014,7 @@
"default": true,
"description": "Specifies whether the OmniSharp server will be automatically started or not. If false, OmniSharp can be started with the 'Restart OmniSharp' command"
},
"omnisharp.projectFilesExcludePattern" :{
"omnisharp.projectFilesExcludePattern": {
"type": "string",
"default": "**/node_modules/**,**/.git/**,**/bower_components/**",
"description": "The exclude pattern used by OmniSharp to find all project files."
Expand Down Expand Up @@ -4100,4 +4101,4 @@
}
]
}
}
}
128 changes: 128 additions & 0 deletions src/omnisharp/requirementCheck.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as vscode from "vscode";
import * as semver from "semver";
import { getDotnetInfo } from "../utils/getDotnetInfo";
import { Options } from "./options";
import { getMonoVersion } from "../utils/getMonoVersion";
import { OmniSharpMonoResolver } from "./OmniSharpMonoResolver";
import { getMSBuildVersion } from "../utils/getMSBuildInfo";

export interface RequirementResult {
needsDotNetSdk: boolean;
needsMono: boolean;
needsMSBuildTools: boolean;
}

export async function validateRequirements(options: Options): Promise<boolean> {
const result = await checkRequirements(options);

if (result.needsDotNetSdk) {
const downloadSdk = await promptToDownloadDotNetSDK();

if (downloadSdk === PromptResult.Yes) {
let dotnetcoreURL = 'https://dot.net/core-sdk-vscode';
vscode.env.openExternal(vscode.Uri.parse(dotnetcoreURL));
} else if (downloadSdk === PromptResult.No) {
vscode.commands.executeCommand('workbench.action.openGlobalSettings');
}

return false;
}

if (result.needsMono
|| result.needsMSBuildTools) { // Since we are currently not checking for MSBuild Tools on Windows this indicates a partial install of Mono.

const downloadMono = await promptToDownloadMono();

if (downloadMono === PromptResult.Yes) {
let monoURL = 'https://www.mono-project.com/download/stable/';
vscode.env.openExternal(vscode.Uri.parse(monoURL));
} else if (downloadMono === PromptResult.No) {
vscode.commands.executeCommand('workbench.action.openGlobalSettings');
}

return false;
}

return true;
}

async function checkRequirements(options: Options): Promise<RequirementResult> {
if (options.useModernNet) {
const dotnetInfo = await getDotnetInfo(options.dotNetCliPaths);
const needsDotNetSdk = dotnetInfo.Version === undefined || semver.lt(dotnetInfo.Version, '6.0.0');
return {
needsDotNetSdk,
needsMono: false,
needsMSBuildTools: false,
};
}

if (process.platform === 'win32') {
// Need to determine way to surface missing MSBuild Tools for windows.
return {
needsMSBuildTools: false,
needsDotNetSdk: false,
needsMono: false,
};
}
else {
const monoResolver = new OmniSharpMonoResolver(getMonoVersion);
let monoError: Error | undefined;
try {
await monoResolver.getHostExecutableInfo(options);
} catch (e) {
monoError = e;
}

const msbuildVersion = await getMSBuildVersion();

return {
needsMono: monoError !== undefined,
needsDotNetSdk: false,
needsMSBuildTools: msbuildVersion !== undefined,
};
}
}

enum PromptResult {
Dismissed,
Yes,
No
}

interface PromptItem extends vscode.MessageItem {
result: PromptResult;
}

async function promptToDownloadDotNetSDK() {
return new Promise<PromptResult>((resolve, reject) => {
const message = 'OmniSharp requires an install of the .NET SDK to provide language services when `omnisharp.useModernNet` is enabled in Settings. Please install the latest .NET SDK and restart.';

const messageOptions: vscode.MessageOptions = { modal: true };

const yesItem: PromptItem = { title: 'Get the SDK', result: PromptResult.Yes };
const noItem: PromptItem = { title: 'Open settings', result: PromptResult.No, isCloseAffordance: true };

vscode.window.showErrorMessage(message, messageOptions, noItem, yesItem)
.then(selection => resolve(selection?.result ?? PromptResult.Dismissed));
});
}

async function promptToDownloadMono() {
return new Promise<PromptResult>((resolve, reject) => {
const message = 'OmniSharp requires a complete install of Mono (including MSBuild) to provide language services when `omnisharp.useModernNet` is disabled in Settings. Please install the latest Mono and restart.';

const messageOptions: vscode.MessageOptions = { modal: true };

const yesItem: PromptItem = { title: 'Download Mono', result: PromptResult.Yes };
const noItem: PromptItem = { title: 'Open settings', result: PromptResult.No, isCloseAffordance: true };

vscode.window.showErrorMessage(message, messageOptions, noItem, yesItem)
.then(selection => resolve(selection?.result ?? PromptResult.Dismissed));
});
}
10 changes: 8 additions & 2 deletions src/omnisharp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import OptionProvider from '../observers/OptionProvider';
import { IHostExecutableResolver } from '../constants/IHostExecutableResolver';
import { showProjectSelector } from '../features/commands';
import { removeBOMFromBuffer, removeBOMFromString } from '../utils/removeBOM';
import { validateRequirements } from './requirementCheck';

enum ServerState {
Starting,
Expand Down Expand Up @@ -277,6 +278,13 @@ export class OmniSharpServer {
return;
}

const options = this.optionProvider.GetLatestOptions();

if (!await validateRequirements(options)) {
this.eventStream.post(new ObservableEvents.OmnisharpServerMessage("OmniSharp failed to start because of missing requirements."));
return;
}

const disposables = new CompositeDisposable();

disposables.add(this.onServerError(err =>
Expand Down Expand Up @@ -340,8 +348,6 @@ export class OmniSharpServer {
const solutionPath = launchTarget.target;
const cwd = path.dirname(solutionPath);

const options = this.optionProvider.GetLatestOptions();

const args = [
'-z',
'-s', solutionPath,
Expand Down
11 changes: 5 additions & 6 deletions src/utils/getDotnetInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ export async function getDotnetInfo(dotNetCliPaths: string[]): Promise<DotnetInf
return _dotnetInfo;
}

let dotnetExeName = CoreClrDebugUtil.getPlatformExeExtension();
let dotnetExecutablePath = undefined;
let dotnetExeName = join('dotnet', CoreClrDebugUtil.getPlatformExeExtension());
let dotnetExecutablePath: string | undefined = undefined;

for (const dotnetPath of dotNetCliPaths) {
let dotnetFullPath = join(dotnetPath, dotnetExeName);
Expand All @@ -31,13 +31,12 @@ export async function getDotnetInfo(dotNetCliPaths: string[]): Promise<DotnetInf
}
}

let dotnetInfo = new DotnetInfo();
const dotnetInfo = new DotnetInfo();

try {
let data = await execChildProcess(`${dotnetExecutablePath || 'dotnet'} --info`, process.cwd());

dotnetInfo.CliPath = dotnetExecutablePath;
let data = await execChildProcess(`${dotnetExecutablePath || 'dotnet'} --info`, process.cwd(), process.env);

dotnetInfo.CliPath = dotnetExecutablePath;
dotnetInfo.FullInfo = data;

let lines: string[] = data.replace(/\r/mg, '').split('\n');
Expand Down
32 changes: 32 additions & 0 deletions src/utils/getMSBuildInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { join } from "path";
import { execChildProcess } from "../common";
import { CoreClrDebugUtil } from "../coreclr-debug/util";

export const MSBUILD_MISSING_MESSAGE = "A valid msbuild installation could not be found.";

let _msbuildVersion: string | undefined;

export async function getMSBuildVersion(): Promise<string | undefined> {
if (_msbuildVersion !== undefined) {
return _msbuildVersion;
}

const msbuildExeName = join('msbuild', CoreClrDebugUtil.getPlatformExeExtension());

try {
let data = await execChildProcess(`${msbuildExeName} --version --nologo`, process.cwd(), process.env);
const match = /^(\d+\.\d+\.\d+\.\d+)$/.exec(data);
if (match.length > 0) {
_msbuildVersion = match[1];
}
}
catch {
}

return _msbuildVersion;
}

0 comments on commit f9b4682

Please sign in to comment.