Skip to content

Commit 8c51451

Browse files
committed
Verify dotnet architecture matches
1 parent 4f754ba commit 8c51451

File tree

3 files changed

+70
-10
lines changed

3 files changed

+70
-10
lines changed

src/lsptoolshost/dotnetRuntimeExtensionResolver.ts

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import { existsSync } from 'fs';
1414
import { CSharpExtensionId } from '../constants/csharpExtensionId';
1515
import { promisify } from 'util';
1616
import { exec } from 'child_process';
17-
import { pathExistsSync } from 'fs-extra';
17+
import { getDotnetInfo } from '../shared/utils/getDotnetInfo';
18+
import { readFile } from 'fs/promises';
1819

1920
export const DotNetRuntimeVersion = '7.0';
2021

@@ -33,7 +34,8 @@ export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
3334
* This is a function instead of a string because the server path can change while the extension is active (when the option changes).
3435
*/
3536
private getServerPath: (options: Options, platform: PlatformInformation) => string,
36-
private channel: vscode.OutputChannel
37+
private channel: vscode.OutputChannel,
38+
private extensionPath: string
3739
) {}
3840

3941
private hostInfo: HostExecutableInformation | undefined;
@@ -129,16 +131,23 @@ export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
129131
*/
130132
private async findDotnetFromPath(): Promise<string | undefined> {
131133
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+
const dotnetInfo = await getDotnetInfo([]);
135+
const dotnetVersionStr = dotnetInfo.Version;
134136

135-
if (result.stderr) {
136-
throw new Error(`Unable to read dotnet version information. Error ${result.stderr}`);
137+
const extensionArchitecture = await this.getArchitectureFromTargetPlatform();
138+
const dotnetArchitecture = dotnetInfo.Architecture;
139+
140+
// If the extension arhcitecture is defined, we check that it matches the dotnet architecture.
141+
// If its undefined we likely have a platform neutral server and assume it can run on any architecture.
142+
if (extensionArchitecture && extensionArchitecture !== dotnetArchitecture) {
143+
throw new Error(
144+
`The architecture of the .NET runtime (${dotnetArchitecture}) does not match the architecture of the extension (${extensionArchitecture}).`
145+
);
137146
}
138147

139-
const dotnetVersion = semver.parse(result.stdout.trimEnd());
148+
const dotnetVersion = semver.parse(dotnetVersionStr);
140149
if (!dotnetVersion) {
141-
throw new Error(`Unknown result output from 'dotnet --version'. Received ${result.stdout}`);
150+
throw new Error(`Unknown result output from 'dotnet --version'. Received ${dotnetVersionStr}`);
142151
}
143152

144153
if (semver.lt(dotnetVersion, this.minimumDotnetVersion)) {
@@ -155,7 +164,7 @@ export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
155164
}
156165

157166
const path = whereOutput.stdout.trim();
158-
if (!pathExistsSync(path)) {
167+
if (!existsSync(path)) {
159168
throw new Error(`dotnet path does not exist: ${path}`);
160169
}
161170

@@ -172,4 +181,45 @@ export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
172181

173182
return undefined;
174183
}
184+
185+
private async getArchitectureFromTargetPlatform(): Promise<string | undefined> {
186+
const vsixManifestFile = path.join(this.extensionPath, '.vsixmanifest');
187+
if (!existsSync(vsixManifestFile)) {
188+
// This is not an error as normal development F5 builds do not generate a .vsixmanifest file.
189+
this.channel.appendLine(
190+
`Unable to find extension target platform - no vsix manifest file exists at ${vsixManifestFile}`
191+
);
192+
return undefined;
193+
}
194+
195+
const contents = await readFile(vsixManifestFile, 'utf-8');
196+
const targetPlatformMatch = /TargetPlatform="(.*)"/.exec(contents);
197+
if (!targetPlatformMatch) {
198+
throw new Error(`Could not find extension target platform in ${vsixManifestFile}`);
199+
}
200+
201+
const targetPlatform = targetPlatformMatch[1];
202+
203+
// The currently known extension platforms are taken from here:
204+
// https://code.visualstudio.com/api/working-with-extensions/publishing-extension#platformspecific-extensions
205+
switch (targetPlatform) {
206+
case 'win32-x64':
207+
case 'linux-x64':
208+
case 'alpine-x64':
209+
case 'darwin-x64':
210+
return 'x64';
211+
case 'win32-ia32':
212+
return 'x86';
213+
case 'win32-arm64':
214+
case 'linux-arm64':
215+
case 'alpine-arm64':
216+
case 'darwin-arm64':
217+
return 'arm64';
218+
case 'linux-armhf':
219+
case 'web':
220+
return undefined;
221+
default:
222+
throw new Error(`Unknown extension target platform: ${targetPlatform}`);
223+
}
224+
}
175225
}

src/lsptoolshost/roslynLanguageServer.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,12 @@ 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, outputChannel);
736+
const hostExecutableResolver = new DotnetRuntimeExtensionResolver(
737+
platformInfo,
738+
getServerPath,
739+
outputChannel,
740+
context.extensionPath
741+
);
737742
const additionalExtensionPaths = scanExtensionPlugins();
738743
_languageServer = new RoslynLanguageServer(
739744
platformInfo,

src/shared/utils/getDotnetInfo.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export async function getDotnetInfo(dotNetCliPaths: string[]): Promise<DotnetInf
2525

2626
let version: string | undefined;
2727
let runtimeId: string | undefined;
28+
let architecture: string | undefined;
2829

2930
const lines = data.replace(/\r/gm, '').split('\n');
3031
for (const line of lines) {
@@ -33,6 +34,8 @@ export async function getDotnetInfo(dotNetCliPaths: string[]): Promise<DotnetInf
3334
version = match[1];
3435
} else if ((match = /^ RID:\s*([\w\-.]+)$/.exec(line))) {
3536
runtimeId = match[1];
37+
} else if ((match = /^\s*Architecture:\s*(.*)/.exec(line))) {
38+
architecture = match[1];
3639
}
3740
}
3841

@@ -42,6 +45,7 @@ export async function getDotnetInfo(dotNetCliPaths: string[]): Promise<DotnetInf
4245
FullInfo: fullInfo,
4346
Version: version,
4447
RuntimeId: runtimeId,
48+
Architecture: architecture,
4549
};
4650
return _dotnetInfo;
4751
}
@@ -73,4 +77,5 @@ export interface DotnetInfo {
7377
Version: string;
7478
/* a runtime-only install of dotnet will not output a runtimeId in dotnet --info. */
7579
RuntimeId?: string;
80+
Architecture?: string;
7681
}

0 commit comments

Comments
 (0)