Skip to content

Commit

Permalink
Merge pull request #5397 from OmniSharp/add-prerequisite-check
Browse files Browse the repository at this point in the history
Add prerequisite check for running OmniSharp.
  • Loading branch information
JoeRobich authored Oct 10, 2022
2 parents 9336244 + 9f77d9c commit ef7d6cc
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 vscode. If you continue see this error after installing .NET and restarting vscode, you may need to log out and log back in or restart your system for changes to the PATH to take effect.';

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 ef7d6cc

Please sign in to comment.