Skip to content

Commit f9b4682

Browse files
committed
Add prerequisite check for running OmniSharp.
1 parent 9336244 commit f9b4682

File tree

5 files changed

+178
-12
lines changed

5 files changed

+178
-12
lines changed

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,8 @@
231231
"darwin"
232232
],
233233
"architectures": [
234-
"x86_64"
234+
"x86_64",
235+
"arm64"
235236
],
236237
"binaries": [
237238
"./mono.osx",
@@ -333,7 +334,7 @@
333334
},
334335
{
335336
"id": "OmniSharp",
336-
"description": "OmniSharp for Linux (Framework / arm64)",
337+
"description": "OmniSharp for Linux (Mono / arm64)",
337338
"url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.1/omnisharp-linux-arm64-1.39.1.zip",
338339
"installPath": ".omnisharp/1.39.1",
339340
"platforms": [
@@ -1013,7 +1014,7 @@
10131014
"default": true,
10141015
"description": "Specifies whether the OmniSharp server will be automatically started or not. If false, OmniSharp can be started with the 'Restart OmniSharp' command"
10151016
},
1016-
"omnisharp.projectFilesExcludePattern" :{
1017+
"omnisharp.projectFilesExcludePattern": {
10171018
"type": "string",
10181019
"default": "**/node_modules/**,**/.git/**,**/bower_components/**",
10191020
"description": "The exclude pattern used by OmniSharp to find all project files."
@@ -4100,4 +4101,4 @@
41004101
}
41014102
]
41024103
}
4103-
}
4104+
}

src/omnisharp/requirementCheck.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from "vscode";
7+
import * as semver from "semver";
8+
import { getDotnetInfo } from "../utils/getDotnetInfo";
9+
import { Options } from "./options";
10+
import { getMonoVersion } from "../utils/getMonoVersion";
11+
import { OmniSharpMonoResolver } from "./OmniSharpMonoResolver";
12+
import { getMSBuildVersion } from "../utils/getMSBuildInfo";
13+
14+
export interface RequirementResult {
15+
needsDotNetSdk: boolean;
16+
needsMono: boolean;
17+
needsMSBuildTools: boolean;
18+
}
19+
20+
export async function validateRequirements(options: Options): Promise<boolean> {
21+
const result = await checkRequirements(options);
22+
23+
if (result.needsDotNetSdk) {
24+
const downloadSdk = await promptToDownloadDotNetSDK();
25+
26+
if (downloadSdk === PromptResult.Yes) {
27+
let dotnetcoreURL = 'https://dot.net/core-sdk-vscode';
28+
vscode.env.openExternal(vscode.Uri.parse(dotnetcoreURL));
29+
} else if (downloadSdk === PromptResult.No) {
30+
vscode.commands.executeCommand('workbench.action.openGlobalSettings');
31+
}
32+
33+
return false;
34+
}
35+
36+
if (result.needsMono
37+
|| result.needsMSBuildTools) { // Since we are currently not checking for MSBuild Tools on Windows this indicates a partial install of Mono.
38+
39+
const downloadMono = await promptToDownloadMono();
40+
41+
if (downloadMono === PromptResult.Yes) {
42+
let monoURL = 'https://www.mono-project.com/download/stable/';
43+
vscode.env.openExternal(vscode.Uri.parse(monoURL));
44+
} else if (downloadMono === PromptResult.No) {
45+
vscode.commands.executeCommand('workbench.action.openGlobalSettings');
46+
}
47+
48+
return false;
49+
}
50+
51+
return true;
52+
}
53+
54+
async function checkRequirements(options: Options): Promise<RequirementResult> {
55+
if (options.useModernNet) {
56+
const dotnetInfo = await getDotnetInfo(options.dotNetCliPaths);
57+
const needsDotNetSdk = dotnetInfo.Version === undefined || semver.lt(dotnetInfo.Version, '6.0.0');
58+
return {
59+
needsDotNetSdk,
60+
needsMono: false,
61+
needsMSBuildTools: false,
62+
};
63+
}
64+
65+
if (process.platform === 'win32') {
66+
// Need to determine way to surface missing MSBuild Tools for windows.
67+
return {
68+
needsMSBuildTools: false,
69+
needsDotNetSdk: false,
70+
needsMono: false,
71+
};
72+
}
73+
else {
74+
const monoResolver = new OmniSharpMonoResolver(getMonoVersion);
75+
let monoError: Error | undefined;
76+
try {
77+
await monoResolver.getHostExecutableInfo(options);
78+
} catch (e) {
79+
monoError = e;
80+
}
81+
82+
const msbuildVersion = await getMSBuildVersion();
83+
84+
return {
85+
needsMono: monoError !== undefined,
86+
needsDotNetSdk: false,
87+
needsMSBuildTools: msbuildVersion !== undefined,
88+
};
89+
}
90+
}
91+
92+
enum PromptResult {
93+
Dismissed,
94+
Yes,
95+
No
96+
}
97+
98+
interface PromptItem extends vscode.MessageItem {
99+
result: PromptResult;
100+
}
101+
102+
async function promptToDownloadDotNetSDK() {
103+
return new Promise<PromptResult>((resolve, reject) => {
104+
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.';
105+
106+
const messageOptions: vscode.MessageOptions = { modal: true };
107+
108+
const yesItem: PromptItem = { title: 'Get the SDK', result: PromptResult.Yes };
109+
const noItem: PromptItem = { title: 'Open settings', result: PromptResult.No, isCloseAffordance: true };
110+
111+
vscode.window.showErrorMessage(message, messageOptions, noItem, yesItem)
112+
.then(selection => resolve(selection?.result ?? PromptResult.Dismissed));
113+
});
114+
}
115+
116+
async function promptToDownloadMono() {
117+
return new Promise<PromptResult>((resolve, reject) => {
118+
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.';
119+
120+
const messageOptions: vscode.MessageOptions = { modal: true };
121+
122+
const yesItem: PromptItem = { title: 'Download Mono', result: PromptResult.Yes };
123+
const noItem: PromptItem = { title: 'Open settings', result: PromptResult.No, isCloseAffordance: true };
124+
125+
vscode.window.showErrorMessage(message, messageOptions, noItem, yesItem)
126+
.then(selection => resolve(selection?.result ?? PromptResult.Dismissed));
127+
});
128+
}

src/omnisharp/server.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import OptionProvider from '../observers/OptionProvider';
3333
import { IHostExecutableResolver } from '../constants/IHostExecutableResolver';
3434
import { showProjectSelector } from '../features/commands';
3535
import { removeBOMFromBuffer, removeBOMFromString } from '../utils/removeBOM';
36+
import { validateRequirements } from './requirementCheck';
3637

3738
enum ServerState {
3839
Starting,
@@ -277,6 +278,13 @@ export class OmniSharpServer {
277278
return;
278279
}
279280

281+
const options = this.optionProvider.GetLatestOptions();
282+
283+
if (!await validateRequirements(options)) {
284+
this.eventStream.post(new ObservableEvents.OmnisharpServerMessage("OmniSharp failed to start because of missing requirements."));
285+
return;
286+
}
287+
280288
const disposables = new CompositeDisposable();
281289

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

343-
const options = this.optionProvider.GetLatestOptions();
344-
345351
const args = [
346352
'-z',
347353
'-s', solutionPath,

src/utils/getDotnetInfo.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ export async function getDotnetInfo(dotNetCliPaths: string[]): Promise<DotnetInf
2020
return _dotnetInfo;
2121
}
2222

23-
let dotnetExeName = CoreClrDebugUtil.getPlatformExeExtension();
24-
let dotnetExecutablePath = undefined;
23+
let dotnetExeName = join('dotnet', CoreClrDebugUtil.getPlatformExeExtension());
24+
let dotnetExecutablePath: string | undefined = undefined;
2525

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

34-
let dotnetInfo = new DotnetInfo();
34+
const dotnetInfo = new DotnetInfo();
3535

3636
try {
37-
let data = await execChildProcess(`${dotnetExecutablePath || 'dotnet'} --info`, process.cwd());
38-
39-
dotnetInfo.CliPath = dotnetExecutablePath;
37+
let data = await execChildProcess(`${dotnetExecutablePath || 'dotnet'} --info`, process.cwd(), process.env);
4038

39+
dotnetInfo.CliPath = dotnetExecutablePath;
4140
dotnetInfo.FullInfo = data;
4241

4342
let lines: string[] = data.replace(/\r/mg, '').split('\n');

src/utils/getMSBuildInfo.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { join } from "path";
7+
import { execChildProcess } from "../common";
8+
import { CoreClrDebugUtil } from "../coreclr-debug/util";
9+
10+
export const MSBUILD_MISSING_MESSAGE = "A valid msbuild installation could not be found.";
11+
12+
let _msbuildVersion: string | undefined;
13+
14+
export async function getMSBuildVersion(): Promise<string | undefined> {
15+
if (_msbuildVersion !== undefined) {
16+
return _msbuildVersion;
17+
}
18+
19+
const msbuildExeName = join('msbuild', CoreClrDebugUtil.getPlatformExeExtension());
20+
21+
try {
22+
let data = await execChildProcess(`${msbuildExeName} --version --nologo`, process.cwd(), process.env);
23+
const match = /^(\d+\.\d+\.\d+\.\d+)$/.exec(data);
24+
if (match.length > 0) {
25+
_msbuildVersion = match[1];
26+
}
27+
}
28+
catch {
29+
}
30+
31+
return _msbuildVersion;
32+
}

0 commit comments

Comments
 (0)