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

Try to find a valid dotnet version from the path before falling back to runtime extension #6074

Merged
merged 4 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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) {
gregg-miskelly marked this conversation as resolved.
Show resolved Hide resolved
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)) {
Copy link
Member Author

Choose a reason for hiding this comment

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

everything before here is the exact code O# uses which has been working

throw new Error(
gregg-miskelly marked this conversation as resolved.
Show resolved Hide resolved
`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