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

Allow rzls to launch using standard dotnet #5855

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
41 changes: 6 additions & 35 deletions src/lsptoolshost/roslynLanguageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import {
CompletionItem,
} from 'vscode-languageclient/node';
import { PlatformInformation } from '../shared/platform';
import { acquireDotNetProcessDependencies } from './dotnetRuntime';
import { getDotNetProcessInfo } from '../shared/dotnetRuntime';
import { readConfigurations } from './configurationMiddleware';
import OptionProvider from '../shared/observers/OptionProvider';
import { DynamicFileInfoHandler } from '../razor/src/DynamicFile/DynamicFileInfoHandler';
Expand Down Expand Up @@ -209,7 +209,7 @@ export class RoslynLanguageServer {
/**
* Restarts the language server. This does not wait until the server has been restarted.
* Note that since some options affect how the language server is initialized, we must
* re-create the LanguageClient instance instead of just stopping/starting it.
* re-create the LanguageClient instance instead of just stopping/starting it.
*/
public async restart(): Promise<void> {
await this.stop();
Expand Down Expand Up @@ -356,31 +356,9 @@ export class RoslynLanguageServer {

let options = this.optionProvider.GetLatestOptions();
let serverPath = this.getServerPath(options);
let [appPath, env, args] = await getDotNetProcessInfo(serverPath, this.platformInfo, options);

let dotnetRuntimePath = options.commonOptions.dotnetPath;
if (!dotnetRuntimePath)
{
let dotnetPath = await acquireDotNetProcessDependencies(serverPath);
dotnetRuntimePath = path.dirname(dotnetPath);
}

const dotnetExecutableName = this.platformInfo.isWindows() ? 'dotnet.exe' : 'dotnet';
const dotnetExecutablePath = path.join(dotnetRuntimePath, dotnetExecutableName);
if (!fs.existsSync(dotnetExecutablePath)) {
throw new Error(`Cannot find dotnet path '${dotnetExecutablePath}'`);
}

_channel.appendLine("Dotnet path: " + dotnetExecutablePath);

// Take care to always run .NET processes on the runtime that we intend.
// The dotnet.exe we point to should not go looking for other runtimes.
const env: NodeJS.ProcessEnv = { ...process.env };
env.DOTNET_ROOT = dotnetRuntimePath;
env.DOTNET_MULTILEVEL_LOOKUP = '0';
// Save user's DOTNET_ROOT env-var value so server can recover the user setting when needed
env.DOTNET_ROOT_USER = process.env.DOTNET_ROOT ?? 'EMPTY';

let args: string[] = [ ];
_channel.appendLine("Dotnet path: " + appPath);

if (options.commonOptions.waitForDebugger) {
args.push("--debug");
Expand Down Expand Up @@ -427,17 +405,10 @@ export class RoslynLanguageServer {
let cpOptions: cp.SpawnOptionsWithoutStdio = {
detached: true,
windowsHide: true,
env: env
env: env,
};

if (serverPath.endsWith('.dll')) {
// If we were given a path to a dll, launch that via dotnet.
const argsWithPath = [ serverPath ].concat(args);
childProcess = cp.spawn(dotnetExecutablePath, argsWithPath, cpOptions);
} else {
// Otherwise assume we were given a path to an executable.
childProcess = cp.spawn(serverPath, args, cpOptions);
}
childProcess = cp.spawn(appPath, args, cpOptions);

return childProcess;
}
Expand Down
54 changes: 34 additions & 20 deletions src/razor/src/RazorLanguageServerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { EventEmitter } from 'events';
import * as vscode from 'vscode';
import * as cp from 'child_process';
import {
RequestHandler,
RequestType,
Expand All @@ -25,14 +26,16 @@ import { resolveRazorLanguageServerOptions } from './RazorLanguageServerOptionsR
import { resolveRazorLanguageServerTrace } from './RazorLanguageServerTraceResolver';
import { RazorLogger } from './RazorLogger';
import { TelemetryReporter } from './TelemetryReporter';
import { getDotNetProcessInfo } from '../../shared/dotnetRuntime';
import { PlatformInformation } from '../../shared/platform';
import OptionProvider from '../../shared/observers/OptionProvider';

const events = {
ServerStop: 'ServerStop',
};

export class RazorLanguageServerClient implements vscode.Disposable {
private clientOptions!: LanguageClientOptions;
private serverOptions!: ServerOptions;
private client!: LanguageClient;
private onStartListeners: Array<() => Promise<any>> = [];
private onStartedListeners: Array<() => Promise<any>> = [];
Expand All @@ -45,6 +48,8 @@ export class RazorLanguageServerClient implements vscode.Disposable {
private readonly vscodeType: typeof vscode,
private readonly languageServerDir: string,
private readonly telemetryReporter: TelemetryReporter,
private readonly platformInfo: PlatformInformation,
private readonly optionProvider: OptionProvider,
private readonly logger: RazorLogger) {
this.isStarted = false;

Expand Down Expand Up @@ -208,26 +213,12 @@ export class RazorLanguageServerClient implements vscode.Disposable {
return this.stopHandle;
}

private setupLanguageServer() {
const languageServerTrace = resolveRazorLanguageServerTrace(this.vscodeType);
const options: RazorLanguageServerOptions = resolveRazorLanguageServerOptions(this.vscodeType, this.languageServerDir, languageServerTrace, this.logger);
private async startServer(options: RazorLanguageServerOptions): Promise<cp.ChildProcess> {

this.clientOptions = {
outputChannel: options.outputChannel,
documentSelector: [ { language: RazorLanguage.id, pattern: RazorLanguage.globbingPattern } ],
};

const args: string[] = [];
let command = options.serverPath;
if (options.serverPath.endsWith('.dll')) {
this.logger.logMessage('Razor Language Server path is an assembly. ' +
'Using \'dotnet\' from the current path to start the server.');

command = 'dotnet';
args.push(options.serverPath);
}
let [appPath, env, args] = await getDotNetProcessInfo(options.serverPath, this.platformInfo, this.optionProvider.GetLatestOptions());

this.logger.logMessage(`Razor language server path: ${options.serverPath}`);
this.logger.logMessage(`Razor language app path: ${appPath}`);

args.push('--trace');
args.push(options.trace.toString());
Expand All @@ -252,7 +243,30 @@ export class RazorLanguageServerClient implements vscode.Disposable {
args.push('true');
}

this.serverOptions = { command, args };
this.client = new LanguageClient('razorLanguageServer', 'Razor Language Server', this.serverOptions, this.clientOptions);
let childProcess: cp.ChildProcessWithoutNullStreams;
let cpOptions: cp.SpawnOptionsWithoutStdio = {
detached: true,
windowsHide: true,
env: env,
};

childProcess = cp.spawn(appPath, args, cpOptions);

return childProcess;
}

private setupLanguageServer() {
const languageServerTrace = resolveRazorLanguageServerTrace(this.vscodeType);
const options: RazorLanguageServerOptions = resolveRazorLanguageServerOptions(this.vscodeType, this.languageServerDir, languageServerTrace, this.logger);

this.clientOptions = {
outputChannel: options.outputChannel,
documentSelector: [ { language: RazorLanguage.id, pattern: RazorLanguage.globbingPattern } ],
};

let serverOptions: ServerOptions = async () => {
return await this.startServer(options);
};
this.client = new LanguageClient('razorLanguageServer', 'Razor Language Server', serverOptions, this.clientOptions);
}
}
24 changes: 8 additions & 16 deletions src/razor/src/RazorLanguageServerOptionsResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,17 @@ export function resolveRazorLanguageServerOptions(
}

function findLanguageServerExecutable(withinDir: string) {
const extension = isWindows() ? '.exe' : '';
const executablePath = path.join(
withinDir,
`rzls${extension}`);
const isSelfContained = fs.existsSync(path.join(withinDir, 'coreclr.dll'));
let fullPath = '';

if (fs.existsSync(executablePath)) {
fullPath = executablePath;
if (isSelfContained) {
const fileName = isWindows() ? 'rzls.exe' : 'rzls';
fullPath = path.join(withinDir, fileName);
} else {
// Exe doesn't exist.
const dllPath = path.join(
withinDir,
'rzls.dll');

if (!fs.existsSync(dllPath)) {
throw new Error(`Could not find Razor Language Server executable within directory '${withinDir}'`);
}
fullPath = path.join(withinDir, 'rzls.dll');
}

fullPath = dllPath;
if (!fs.existsSync(fullPath)) {
throw new Error(`Could not find Razor Language Server executable within directory '${withinDir}'`);
}

return fullPath;
Expand Down
8 changes: 7 additions & 1 deletion src/razor/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ import { SemanticTokensRangeHandler } from './Semantic/SemanticTokensRangeHandle
import { RazorSignatureHelpProvider } from './SignatureHelp/RazorSignatureHelpProvider';
import { TelemetryReporter } from './TelemetryReporter';
import { RazorDiagnosticHandler } from './Diagnostics/RazorDiagnosticHandler';
import { PlatformInformation } from '../../shared/platform';
import createOptionStream from '../../shared/observables/CreateOptionStream';
import OptionProvider from '../../shared/observers/OptionProvider';

// We specifically need to take a reference to a particular instance of the vscode namespace,
// otherwise providers attempt to operate on the null extension.
Expand All @@ -52,11 +55,14 @@ export async function activate(vscodeType: typeof vscodeapi, context: ExtensionC
create: <T>() => new vscode.EventEmitter<T>(),
};

const optionStream = createOptionStream(vscode);
let optionProvider = new OptionProvider(optionStream);
const languageServerTrace = resolveRazorLanguageServerTrace(vscodeType);
const logger = new RazorLogger(vscodeType, eventEmitterFactory, languageServerTrace);

try {
const languageServerClient = new RazorLanguageServerClient(vscodeType, languageServerDir, telemetryReporter, logger);
const platformInfo = await PlatformInformation.GetCurrent();
const languageServerClient = new RazorLanguageServerClient(vscodeType, languageServerDir, telemetryReporter, platformInfo, optionProvider, logger);
const languageServiceClient = new RazorLanguageServiceClient(languageServerClient);

const documentManager = new RazorDocumentManager(languageServerClient, logger);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { CSharpExtensionId } from '../constants/CSharpExtensionId';
import { PlatformInformation } from './platform';
import { Options } from './options';

export const DotNetRuntimeVersion = '7.0';

Expand Down Expand Up @@ -56,3 +60,33 @@ export async function acquireDotNetProcessDependencies(path: string) {

return dotnetPath;
}

export async function getDotNetProcessInfo(appPath: string, platformInfo: PlatformInformation, options: Options): Promise<[appPath: string, env: NodeJS.ProcessEnv, args: string[]]> {
let dotnetRuntimePath = options.commonOptions.dotnetPath;
if (!dotnetRuntimePath) {
let dotnetPath = await acquireDotNetProcessDependencies(appPath);
dotnetRuntimePath = path.dirname(dotnetPath);
}

// Take care to always run .NET processes on the runtime that we intend.
// The dotnet.exe we point to should not go looking for other runtimes.
const env: NodeJS.ProcessEnv = { ...process.env };
env.DOTNET_ROOT = dotnetRuntimePath;
env.DOTNET_MULTILEVEL_LOOKUP = '0';
// Save user's DOTNET_ROOT env-var value so server can recover the user setting when needed
env.DOTNET_ROOT_USER = process.env.DOTNET_ROOT ?? 'EMPTY';

let args: string[] = [ ];
if (!appPath.endsWith('.dll')) {
return [appPath, env, args];
}

const dotnetFileName = platformInfo.isWindows() ? 'dotnet.exe' : 'dotnet';
const dotnetPath = path.join(dotnetRuntimePath, dotnetFileName);
if (!fs.existsSync(dotnetPath)) {
throw new Error(`Cannot find dotnet path '${dotnetPath}'`);
}

args.push(appPath);
return [dotnetPath, env, args];
}