Skip to content

Commit

Permalink
Try to find a valid dotnet version from the path before falling back …
Browse files Browse the repository at this point in the history
…to runtime extension
  • Loading branch information
dibarbet committed Aug 8, 2023
1 parent 1ca032a commit 4f754ba
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 2 deletions.
73 changes: 72 additions & 1 deletion src/lsptoolshost/dotnetRuntimeExtensionResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@

import * as path from 'path';
import * as vscode from 'vscode';
import * as semver from 'semver';
import { HostExecutableInformation } from '../shared/constants/hostExecutableInformation';
import { IHostExecutableResolver } from '../shared/constants/IHostExecutableResolver';
import { PlatformInformation } from '../shared/platform';
import { Options } from '../shared/options';
import { existsSync } from 'fs';
import { CSharpExtensionId } from '../constants/csharpExtensionId';
import { promisify } from 'util';
import { exec } from 'child_process';
import { pathExistsSync } from 'fs-extra';

export const DotNetRuntimeVersion = '7.0';

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

private hostInfo: HostExecutableInformation | undefined;

async getHostExecutableInfo(options: Options): Promise<HostExecutableInformation> {
let dotnetRuntimePath = options.commonOptions.dotnetPath;
const serverPath = this.getServerPath(options, this.platformInfo);

// Check if we can find a valid dotnet from dotnet --version on the PATH.
if (!dotnetRuntimePath) {
const dotnetPath = await this.findDotnetFromPath();
if (dotnetPath) {
return {
version: '' /* We don't need to know the version - we've already verified its high enough */,
path: dotnetPath,
env: process.env,
};
}
}

// We didn't find it on the path, see if we can install the correct runtime using the runtime extension.
if (!dotnetRuntimePath) {
const dotnetInfo = await this.acquireDotNetProcessDependencies(serverPath);
dotnetRuntimePath = path.dirname(dotnetInfo.path);
Expand Down Expand Up @@ -101,4 +121,55 @@ export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {

return dotnetInfo;
}

/**
* Checks dotnet --version to see if the value on the path is greater than the minimum required version.
* This is adapated from similar O# server logic and should be removed when we have a stable acquisition extension.
* @returns true if the dotnet version is greater than the minimum required version, false otherwise.
*/
private async findDotnetFromPath(): Promise<string | undefined> {
try {
// Run dotnet version to see if there is a valid dotnet on the path with a high enough version.
const result = await promisify(exec)(`dotnet --version`);

if (result.stderr) {
throw new Error(`Unable to read dotnet version information. Error ${result.stderr}`);
}

const dotnetVersion = semver.parse(result.stdout.trimEnd());
if (!dotnetVersion) {
throw new Error(`Unknown result output from 'dotnet --version'. Received ${result.stdout}`);
}

if (semver.lt(dotnetVersion, this.minimumDotnetVersion)) {
throw new Error(
`Found dotnet version ${dotnetVersion}. Minimum required version is ${this.minimumDotnetVersion}.`
);
}

// Find the location of the dotnet on path.
const command = this.platformInfo.isWindows() ? 'where' : 'which';
const whereOutput = await promisify(exec)(`${command} dotnet`);
if (!whereOutput.stdout) {
throw new Error(`Unable to find dotnet from where.`);
}

const path = whereOutput.stdout.trim();
if (!pathExistsSync(path)) {
throw new Error(`dotnet path does not exist: ${path}`);
}

this.channel.appendLine(`Using dotnet configured on PATH`);
return path;
} catch (e) {
this.channel.appendLine(
'Failed to find dotnet info from path, falling back to acquire runtime via ms-dotnettools.vscode-dotnet-runtime'
);
if (e instanceof Error) {
this.channel.appendLine(e.message);
}
}

return undefined;
}
}
2 changes: 1 addition & 1 deletion src/lsptoolshost/roslynLanguageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,7 @@ export async function activateRoslynLanguageServer(
// Create a separate channel for outputting trace logs - these are incredibly verbose and make other logs very difficult to see.
_traceChannel = vscode.window.createOutputChannel('C# LSP Trace Logs');

const hostExecutableResolver = new DotnetRuntimeExtensionResolver(platformInfo, getServerPath);
const hostExecutableResolver = new DotnetRuntimeExtensionResolver(platformInfo, getServerPath, outputChannel);
const additionalExtensionPaths = scanExtensionPlugins();
_languageServer = new RoslynLanguageServer(
platformInfo,
Expand Down

0 comments on commit 4f754ba

Please sign in to comment.