55
66import * as path from 'path' ;
77import * as vscode from 'vscode' ;
8- import * as semver from 'semver' ;
98import { HostExecutableInformation } from '../shared/constants/hostExecutableInformation' ;
109import { IHostExecutableResolver } from '../shared/constants/IHostExecutableResolver' ;
1110import { PlatformInformation } from '../shared/platform' ;
1211import { commonOptions , languageServerOptions } from '../shared/options' ;
1312import { existsSync } from 'fs' ;
1413import { CSharpExtensionId } from '../constants/csharpExtensionId' ;
15- import { getDotnetInfo } from '../shared/utils/getDotnetInfo' ;
1614import { readFile } from 'fs/promises' ;
17- import { RuntimeInfo } from '../shared/utils/dotnetInfo ' ;
15+ import { IDotnetAcquireResult , IDotnetFindPathContext } from './dotnetRuntimeExtensionApi ' ;
1816
19- export const DotNetRuntimeVersion = '8.0.10' ;
20-
21- interface IDotnetAcquireResult {
22- dotnetPath : string ;
23- }
17+ const DotNetMajorVersion = '8' ;
18+ const DotNetMinorVersion = '0' ;
19+ const DotNetPatchVersion = '10' ;
20+ export const DotNetRuntimeVersion = `${ DotNetMajorVersion } .${ DotNetMinorVersion } .${ DotNetPatchVersion } ` ;
2421
2522/**
2623 * Resolves the dotnet runtime for a server executable from given options and the dotnet runtime VSCode extension.
@@ -39,38 +36,47 @@ export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
3936 private hostInfo : HostExecutableInformation | undefined ;
4037
4138 async getHostExecutableInfo ( ) : Promise < HostExecutableInformation > {
42- let dotnetRuntimePath = commonOptions . dotnetPath ;
43- const serverPath = this . getServerPath ( 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 : this . getEnvironmentVariables ( dotnetPath ) ,
53- } ;
39+ let dotnetExecutablePath : string ;
40+ if ( commonOptions . dotnetPath ) {
41+ const dotnetExecutableName = this . getDotnetExecutableName ( ) ;
42+ dotnetExecutablePath = path . join ( commonOptions . dotnetPath , dotnetExecutableName ) ;
43+ } else {
44+ if ( this . hostInfo ) {
45+ return this . hostInfo ;
5446 }
55- }
5647
57- // We didn't find it on the path, see if we can install the correct runtime using the runtime extension.
58- if ( ! dotnetRuntimePath ) {
59- const dotnetInfo = await this . acquireDotNetProcessDependencies ( serverPath ) ;
60- dotnetRuntimePath = path . dirname ( dotnetInfo . path ) ;
61- }
48+ this . channel . appendLine ( `Locating .NET runtime version ${ DotNetRuntimeVersion } ` ) ;
49+ const extensionArchitecture = ( await this . getArchitectureFromTargetPlatform ( ) ) ?? process . arch ;
50+ const findPathRequest : IDotnetFindPathContext = {
51+ acquireContext : {
52+ version : DotNetRuntimeVersion ,
53+ requestingExtensionId : CSharpExtensionId ,
54+ architecture : extensionArchitecture ,
55+ mode : 'runtime' ,
56+ } ,
57+ versionSpecRequirement : 'greater_than_or_equal' ,
58+ } ;
59+ let acquireResult = await vscode . commands . executeCommand < IDotnetAcquireResult | undefined > (
60+ 'dotnet.findPath' ,
61+ findPathRequest
62+ ) ;
63+ if ( acquireResult === undefined ) {
64+ this . channel . appendLine (
65+ `Did not find .NET ${ DotNetRuntimeVersion } on path, falling back to acquire runtime via ms-dotnettools.vscode-dotnet-runtime`
66+ ) ;
67+ acquireResult = await this . acquireDotNetProcessDependencies ( ) ;
68+ }
6269
63- const dotnetExecutableName = this . getDotnetExecutableName ( ) ;
64- const dotnetExecutablePath = path . join ( dotnetRuntimePath , dotnetExecutableName ) ;
65- if ( ! existsSync ( dotnetExecutablePath ) ) {
66- throw new Error ( `Cannot find dotnet path '${ dotnetExecutablePath } '` ) ;
70+ dotnetExecutablePath = acquireResult . dotnetPath ;
6771 }
6872
69- return {
73+ const hostInfo = {
7074 version : '' /* We don't need to know the version - we've already downloaded the correct one */ ,
7175 path : dotnetExecutablePath ,
7276 env : this . getEnvironmentVariables ( dotnetExecutablePath ) ,
7377 } ;
78+ this . hostInfo = hostInfo ;
79+ return hostInfo ;
7480 }
7581
7682 private getEnvironmentVariables ( dotnetExecutablePath : string ) : NodeJS . ProcessEnv {
@@ -100,14 +106,10 @@ export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
100106 * Acquires the .NET runtime if it is not already present.
101107 * @returns The path to the .NET runtime
102108 */
103- private async acquireRuntime ( ) : Promise < HostExecutableInformation > {
104- if ( this . hostInfo ) {
105- return this . hostInfo ;
106- }
107-
108- // We have to use '8.0' here because the runtme extension doesn't support acquiring patch versions.
109- // The acquisition will always acquire the latest however, so it will be at least 8.0.10.
110- const dotnetAcquireVersion = '8.0' ;
109+ private async acquireRuntime ( ) : Promise < IDotnetAcquireResult > {
110+ // The runtime extension doesn't support specifying a patch versions in the acquire API, so we only use major.minor here.
111+ // That is generally OK, as acquisition will always acquire the latest patch version.
112+ const dotnetAcquireVersion = `${ DotNetMajorVersion } .${ DotNetMinorVersion } ` ;
111113 let status = await vscode . commands . executeCommand < IDotnetAcquireResult > ( 'dotnet.acquireStatus' , {
112114 version : dotnetAcquireVersion ,
113115 requestingExtensionId : CSharpExtensionId ,
@@ -119,106 +121,29 @@ export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
119121 version : dotnetAcquireVersion ,
120122 requestingExtensionId : CSharpExtensionId ,
121123 } ) ;
122- if ( ! status ?. dotnetPath ) {
124+ if ( ! status ) {
123125 throw new Error ( 'Could not resolve the dotnet path!' ) ;
124126 }
125127 }
126128
127- return ( this . hostInfo = {
128- version : DotNetRuntimeVersion ,
129- path : status . dotnetPath ,
130- env : process . env ,
131- } ) ;
129+ return status ;
132130 }
133131
134132 /**
135133 * Acquires the .NET runtime and any other dependencies required to spawn a particular .NET executable.
136134 * @param path The path to the entrypoint assembly. Typically a .dll.
137135 */
138- private async acquireDotNetProcessDependencies ( path : string ) : Promise < HostExecutableInformation > {
139- const dotnetInfo = await this . acquireRuntime ( ) ;
136+ private async acquireDotNetProcessDependencies ( ) : Promise < IDotnetAcquireResult > {
137+ const acquireResult = await this . acquireRuntime ( ) ;
140138
141- const args = [ path ] ;
139+ const args = [ this . getServerPath ( this . platformInfo ) ] ;
142140 // This will install any missing Linux dependencies.
143141 await vscode . commands . executeCommand ( 'dotnet.ensureDotnetDependencies' , {
144- command : dotnetInfo . path ,
142+ command : acquireResult . dotnetPath ,
145143 arguments : args ,
146144 } ) ;
147145
148- return dotnetInfo ;
149- }
150-
151- /**
152- * Checks dotnet --version to see if the value on the path is greater than the minimum required version.
153- * This is adapated from similar O# server logic and should be removed when we have a stable acquisition extension.
154- * @returns true if the dotnet version is greater than the minimum required version, false otherwise.
155- */
156- private async findDotnetFromPath ( ) : Promise < string | undefined > {
157- try {
158- const dotnetInfo = await getDotnetInfo ( [ ] ) ;
159-
160- const extensionArchitecture = await this . getArchitectureFromTargetPlatform ( ) ;
161- const dotnetArchitecture = dotnetInfo . Architecture ;
162-
163- // If the extension arhcitecture is defined, we check that it matches the dotnet architecture.
164- // If its undefined we likely have a platform neutral server and assume it can run on any architecture.
165- if ( extensionArchitecture && extensionArchitecture !== dotnetArchitecture ) {
166- throw new Error (
167- `The architecture of the .NET runtime (${ dotnetArchitecture } ) does not match the architecture of the extension (${ extensionArchitecture } ).`
168- ) ;
169- }
170-
171- // Verify that the dotnet we found includes a runtime version that is compatible with our requirement.
172- const requiredRuntimeVersion = semver . parse ( `${ DotNetRuntimeVersion } ` ) ;
173- if ( ! requiredRuntimeVersion ) {
174- throw new Error ( `Unable to parse minimum required version ${ DotNetRuntimeVersion } ` ) ;
175- }
176-
177- const coreRuntimeVersions = dotnetInfo . Runtimes [ 'Microsoft.NETCore.App' ] ;
178- let matchingRuntime : RuntimeInfo | undefined = undefined ;
179- for ( const runtime of coreRuntimeVersions ) {
180- // We consider a match if the runtime is greater than or equal to the required version since we roll forward.
181- if ( semver . gte ( runtime . Version , requiredRuntimeVersion ) ) {
182- matchingRuntime = runtime ;
183- break ;
184- }
185- }
186-
187- if ( ! matchingRuntime ) {
188- throw new Error (
189- `No compatible .NET runtime found. Minimum required version is ${ DotNetRuntimeVersion } .`
190- ) ;
191- }
192-
193- // The .NET install layout is a well known structure on all platforms.
194- // See https://github.com/dotnet/designs/blob/main/accepted/2020/install-locations.md#net-core-install-layout
195- //
196- // Therefore we know that the runtime path is always in <install root>/shared/<runtime name>
197- // and the dotnet executable is always at <install root>/dotnet(.exe).
198- //
199- // Since dotnet --list-runtimes will always use the real assembly path to output the runtime folder (no symlinks!)
200- // we know the dotnet executable will be two folders up in the install root.
201- const runtimeFolderPath = matchingRuntime . Path ;
202- const installFolder = path . dirname ( path . dirname ( runtimeFolderPath ) ) ;
203- const dotnetExecutablePath = path . join ( installFolder , this . getDotnetExecutableName ( ) ) ;
204- if ( ! existsSync ( dotnetExecutablePath ) ) {
205- throw new Error (
206- `dotnet executable path does not exist: ${ dotnetExecutablePath } , dotnet installation may be corrupt.`
207- ) ;
208- }
209-
210- this . channel . appendLine ( `Using dotnet configured on PATH` ) ;
211- return dotnetExecutablePath ;
212- } catch ( e ) {
213- this . channel . appendLine (
214- 'Failed to find dotnet info from path, falling back to acquire runtime via ms-dotnettools.vscode-dotnet-runtime'
215- ) ;
216- if ( e instanceof Error ) {
217- this . channel . appendLine ( e . message ) ;
218- }
219- }
220-
221- return undefined ;
146+ return acquireResult ;
222147 }
223148
224149 private async getArchitectureFromTargetPlatform ( ) : Promise < string | undefined > {
0 commit comments