5
5
6
6
import * as path from 'path' ;
7
7
import * as vscode from 'vscode' ;
8
- import * as semver from 'semver' ;
9
8
import { HostExecutableInformation } from '../shared/constants/hostExecutableInformation' ;
10
9
import { IHostExecutableResolver } from '../shared/constants/IHostExecutableResolver' ;
11
10
import { PlatformInformation } from '../shared/platform' ;
12
11
import { commonOptions , languageServerOptions } from '../shared/options' ;
13
12
import { existsSync } from 'fs' ;
14
13
import { CSharpExtensionId } from '../constants/csharpExtensionId' ;
15
- import { getDotnetInfo } from '../shared/utils/getDotnetInfo' ;
16
14
import { readFile } from 'fs/promises' ;
17
- import { RuntimeInfo } from '../shared/utils/dotnetInfo ' ;
15
+ import { IDotnetAcquireResult , IDotnetFindPathContext } from './dotnetRuntimeExtensionApi ' ;
18
16
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 } ` ;
24
21
25
22
/**
26
23
* 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 {
39
36
private hostInfo : HostExecutableInformation | undefined ;
40
37
41
38
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 ;
54
46
}
55
- }
56
47
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
+ }
62
69
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 ;
67
71
}
68
72
69
- return {
73
+ const hostInfo = {
70
74
version : '' /* We don't need to know the version - we've already downloaded the correct one */ ,
71
75
path : dotnetExecutablePath ,
72
76
env : this . getEnvironmentVariables ( dotnetExecutablePath ) ,
73
77
} ;
78
+ this . hostInfo = hostInfo ;
79
+ return hostInfo ;
74
80
}
75
81
76
82
private getEnvironmentVariables ( dotnetExecutablePath : string ) : NodeJS . ProcessEnv {
@@ -100,14 +106,10 @@ export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
100
106
* Acquires the .NET runtime if it is not already present.
101
107
* @returns The path to the .NET runtime
102
108
*/
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 } ` ;
111
113
let status = await vscode . commands . executeCommand < IDotnetAcquireResult > ( 'dotnet.acquireStatus' , {
112
114
version : dotnetAcquireVersion ,
113
115
requestingExtensionId : CSharpExtensionId ,
@@ -119,106 +121,29 @@ export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
119
121
version : dotnetAcquireVersion ,
120
122
requestingExtensionId : CSharpExtensionId ,
121
123
} ) ;
122
- if ( ! status ?. dotnetPath ) {
124
+ if ( ! status ) {
123
125
throw new Error ( 'Could not resolve the dotnet path!' ) ;
124
126
}
125
127
}
126
128
127
- return ( this . hostInfo = {
128
- version : DotNetRuntimeVersion ,
129
- path : status . dotnetPath ,
130
- env : process . env ,
131
- } ) ;
129
+ return status ;
132
130
}
133
131
134
132
/**
135
133
* Acquires the .NET runtime and any other dependencies required to spawn a particular .NET executable.
136
134
* @param path The path to the entrypoint assembly. Typically a .dll.
137
135
*/
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 ( ) ;
140
138
141
- const args = [ path ] ;
139
+ const args = [ this . getServerPath ( this . platformInfo ) ] ;
142
140
// This will install any missing Linux dependencies.
143
141
await vscode . commands . executeCommand ( 'dotnet.ensureDotnetDependencies' , {
144
- command : dotnetInfo . path ,
142
+ command : acquireResult . dotnetPath ,
145
143
arguments : args ,
146
144
} ) ;
147
145
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 ;
222
147
}
223
148
224
149
private async getArchitectureFromTargetPlatform ( ) : Promise < string | undefined > {
0 commit comments