Skip to content

Commit 4f754ba

Browse files
committed
Try to find a valid dotnet version from the path before falling back to runtime extension
1 parent 1ca032a commit 4f754ba

File tree

2 files changed

+73
-2
lines changed

2 files changed

+73
-2
lines changed

src/lsptoolshost/dotnetRuntimeExtensionResolver.ts

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55

66
import * as path from 'path';
77
import * as vscode from 'vscode';
8+
import * as semver from 'semver';
89
import { HostExecutableInformation } from '../shared/constants/hostExecutableInformation';
910
import { IHostExecutableResolver } from '../shared/constants/IHostExecutableResolver';
1011
import { PlatformInformation } from '../shared/platform';
1112
import { Options } from '../shared/options';
1213
import { existsSync } from 'fs';
1314
import { CSharpExtensionId } from '../constants/csharpExtensionId';
15+
import { promisify } from 'util';
16+
import { exec } from 'child_process';
17+
import { pathExistsSync } from 'fs-extra';
1418

1519
export const DotNetRuntimeVersion = '7.0';
1620

@@ -22,19 +26,35 @@ interface IDotnetAcquireResult {
2226
* Resolves the dotnet runtime for a server executable from given options and the dotnet runtime VSCode extension.
2327
*/
2428
export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
29+
private readonly minimumDotnetVersion = '7.0.100';
2530
constructor(
2631
private platformInfo: PlatformInformation,
2732
/**
2833
* This is a function instead of a string because the server path can change while the extension is active (when the option changes).
2934
*/
30-
private getServerPath: (options: Options, platform: PlatformInformation) => string
35+
private getServerPath: (options: Options, platform: PlatformInformation) => string,
36+
private channel: vscode.OutputChannel
3137
) {}
3238

3339
private hostInfo: HostExecutableInformation | undefined;
3440

3541
async getHostExecutableInfo(options: Options): Promise<HostExecutableInformation> {
3642
let dotnetRuntimePath = options.commonOptions.dotnetPath;
3743
const serverPath = this.getServerPath(options, this.platformInfo);
44+
45+
// Check if we can find a valid dotnet from dotnet --version on the PATH.
46+
if (!dotnetRuntimePath) {
47+
const dotnetPath = await this.findDotnetFromPath();
48+
if (dotnetPath) {
49+
return {
50+
version: '' /* We don't need to know the version - we've already verified its high enough */,
51+
path: dotnetPath,
52+
env: process.env,
53+
};
54+
}
55+
}
56+
57+
// We didn't find it on the path, see if we can install the correct runtime using the runtime extension.
3858
if (!dotnetRuntimePath) {
3959
const dotnetInfo = await this.acquireDotNetProcessDependencies(serverPath);
4060
dotnetRuntimePath = path.dirname(dotnetInfo.path);
@@ -101,4 +121,55 @@ export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
101121

102122
return dotnetInfo;
103123
}
124+
125+
/**
126+
* Checks dotnet --version to see if the value on the path is greater than the minimum required version.
127+
* This is adapated from similar O# server logic and should be removed when we have a stable acquisition extension.
128+
* @returns true if the dotnet version is greater than the minimum required version, false otherwise.
129+
*/
130+
private async findDotnetFromPath(): Promise<string | undefined> {
131+
try {
132+
// Run dotnet version to see if there is a valid dotnet on the path with a high enough version.
133+
const result = await promisify(exec)(`dotnet --version`);
134+
135+
if (result.stderr) {
136+
throw new Error(`Unable to read dotnet version information. Error ${result.stderr}`);
137+
}
138+
139+
const dotnetVersion = semver.parse(result.stdout.trimEnd());
140+
if (!dotnetVersion) {
141+
throw new Error(`Unknown result output from 'dotnet --version'. Received ${result.stdout}`);
142+
}
143+
144+
if (semver.lt(dotnetVersion, this.minimumDotnetVersion)) {
145+
throw new Error(
146+
`Found dotnet version ${dotnetVersion}. Minimum required version is ${this.minimumDotnetVersion}.`
147+
);
148+
}
149+
150+
// Find the location of the dotnet on path.
151+
const command = this.platformInfo.isWindows() ? 'where' : 'which';
152+
const whereOutput = await promisify(exec)(`${command} dotnet`);
153+
if (!whereOutput.stdout) {
154+
throw new Error(`Unable to find dotnet from where.`);
155+
}
156+
157+
const path = whereOutput.stdout.trim();
158+
if (!pathExistsSync(path)) {
159+
throw new Error(`dotnet path does not exist: ${path}`);
160+
}
161+
162+
this.channel.appendLine(`Using dotnet configured on PATH`);
163+
return path;
164+
} catch (e) {
165+
this.channel.appendLine(
166+
'Failed to find dotnet info from path, falling back to acquire runtime via ms-dotnettools.vscode-dotnet-runtime'
167+
);
168+
if (e instanceof Error) {
169+
this.channel.appendLine(e.message);
170+
}
171+
}
172+
173+
return undefined;
174+
}
104175
}

src/lsptoolshost/roslynLanguageServer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,7 @@ export async function activateRoslynLanguageServer(
733733
// Create a separate channel for outputting trace logs - these are incredibly verbose and make other logs very difficult to see.
734734
_traceChannel = vscode.window.createOutputChannel('C# LSP Trace Logs');
735735

736-
const hostExecutableResolver = new DotnetRuntimeExtensionResolver(platformInfo, getServerPath);
736+
const hostExecutableResolver = new DotnetRuntimeExtensionResolver(platformInfo, getServerPath, outputChannel);
737737
const additionalExtensionPaths = scanExtensionPlugins();
738738
_languageServer = new RoslynLanguageServer(
739739
platformInfo,

0 commit comments

Comments
 (0)