Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add prerequisite check for running OmniSharp. #5397

Merged
merged 2 commits into from
Oct 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allow x64 Mono build of O# to install on Apple Silicon.

],
"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());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is currently broken in main.

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;
}