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 { IDotnetFindPathContext } from './dotnetRuntimeExtensionApi ' ;
18
16
19
17
export const DotNetRuntimeVersion = '8.0.10' ;
20
18
21
- interface IDotnetAcquireResult {
22
- dotnetPath : string ;
23
- }
24
-
25
19
/**
26
20
* Resolves the dotnet runtime for a server executable from given options and the dotnet runtime VSCode extension.
27
21
*/
28
22
export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
29
23
constructor (
30
24
private platformInfo : PlatformInformation ,
31
- /**
32
- * This is a function instead of a string because the server path can change while the extension is active (when the option changes).
33
- */
34
- private getServerPath : ( platform : PlatformInformation ) => string ,
35
25
private channel : vscode . OutputChannel ,
36
26
private extensionPath : string
37
27
) { }
38
28
39
29
private hostInfo : HostExecutableInformation | undefined ;
40
30
41
31
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
- } ;
32
+ let dotnetExecutablePath : string ;
33
+ if ( commonOptions . dotnetPath ) {
34
+ const dotnetExecutableName = this . getDotnetExecutableName ( ) ;
35
+ dotnetExecutablePath = path . join ( commonOptions . dotnetPath , dotnetExecutableName ) ;
36
+ } else {
37
+ if ( this . hostInfo ) {
38
+ return this . hostInfo ;
54
39
}
55
- }
56
-
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
- }
62
40
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 } '` ) ;
41
+ this . channel . appendLine ( `Acquiring .NET runtime version ${ DotNetRuntimeVersion } ` ) ;
42
+ const extensionArchitecture = ( await this . getArchitectureFromTargetPlatform ( ) ) ?? process . arch ;
43
+ const findPathRequest : IDotnetFindPathContext = {
44
+ acquireContext : {
45
+ version : DotNetRuntimeVersion ,
46
+ requestingExtensionId : CSharpExtensionId ,
47
+ architecture : extensionArchitecture ,
48
+ mode : 'runtime' ,
49
+ } ,
50
+ versionSpecRequirement : 'greater_than_or_equal' ,
51
+ } ;
52
+ const result = await vscode . commands . executeCommand < string > ( 'dotnet.findPath' , findPathRequest ) ;
53
+ dotnetExecutablePath = result ;
67
54
}
68
55
69
- return {
56
+ const hostInfo = {
70
57
version : '' /* We don't need to know the version - we've already downloaded the correct one */ ,
71
58
path : dotnetExecutablePath ,
72
59
env : this . getEnvironmentVariables ( dotnetExecutablePath ) ,
73
60
} ;
61
+ this . hostInfo = hostInfo ;
62
+ return hostInfo ;
74
63
}
75
64
76
65
private getEnvironmentVariables ( dotnetExecutablePath : string ) : NodeJS . ProcessEnv {
@@ -96,128 +85,6 @@ export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
96
85
return env ;
97
86
}
98
87
99
- /**
100
- * Acquires the .NET runtime if it is not already present.
101
- * @returns The path to the .NET runtime
102
- */
103
- private async acquireRuntime ( ) : Promise < HostExecutableInformation > {
104
- if ( this . hostInfo ) {
105
- return this . hostInfo ;
106
- }
107
-
108
- let status = await vscode . commands . executeCommand < IDotnetAcquireResult > ( 'dotnet.acquireStatus' , {
109
- version : DotNetRuntimeVersion ,
110
- requestingExtensionId : CSharpExtensionId ,
111
- } ) ;
112
- if ( status === undefined ) {
113
- await vscode . commands . executeCommand ( 'dotnet.showAcquisitionLog' ) ;
114
-
115
- status = await vscode . commands . executeCommand < IDotnetAcquireResult > ( 'dotnet.acquire' , {
116
- version : DotNetRuntimeVersion ,
117
- requestingExtensionId : CSharpExtensionId ,
118
- } ) ;
119
- if ( ! status ?. dotnetPath ) {
120
- throw new Error ( 'Could not resolve the dotnet path!' ) ;
121
- }
122
- }
123
-
124
- return ( this . hostInfo = {
125
- version : DotNetRuntimeVersion ,
126
- path : status . dotnetPath ,
127
- env : process . env ,
128
- } ) ;
129
- }
130
-
131
- /**
132
- * Acquires the .NET runtime and any other dependencies required to spawn a particular .NET executable.
133
- * @param path The path to the entrypoint assembly. Typically a .dll.
134
- */
135
- private async acquireDotNetProcessDependencies ( path : string ) : Promise < HostExecutableInformation > {
136
- const dotnetInfo = await this . acquireRuntime ( ) ;
137
-
138
- const args = [ path ] ;
139
- // This will install any missing Linux dependencies.
140
- await vscode . commands . executeCommand ( 'dotnet.ensureDotnetDependencies' , {
141
- command : dotnetInfo . path ,
142
- arguments : args ,
143
- } ) ;
144
-
145
- return dotnetInfo ;
146
- }
147
-
148
- /**
149
- * Checks dotnet --version to see if the value on the path is greater than the minimum required version.
150
- * This is adapated from similar O# server logic and should be removed when we have a stable acquisition extension.
151
- * @returns true if the dotnet version is greater than the minimum required version, false otherwise.
152
- */
153
- private async findDotnetFromPath ( ) : Promise < string | undefined > {
154
- try {
155
- const dotnetInfo = await getDotnetInfo ( [ ] ) ;
156
-
157
- const extensionArchitecture = await this . getArchitectureFromTargetPlatform ( ) ;
158
- const dotnetArchitecture = dotnetInfo . Architecture ;
159
-
160
- // If the extension arhcitecture is defined, we check that it matches the dotnet architecture.
161
- // If its undefined we likely have a platform neutral server and assume it can run on any architecture.
162
- if ( extensionArchitecture && extensionArchitecture !== dotnetArchitecture ) {
163
- throw new Error (
164
- `The architecture of the .NET runtime (${ dotnetArchitecture } ) does not match the architecture of the extension (${ extensionArchitecture } ).`
165
- ) ;
166
- }
167
-
168
- // Verify that the dotnet we found includes a runtime version that is compatible with our requirement.
169
- const requiredRuntimeVersion = semver . parse ( `${ DotNetRuntimeVersion } ` ) ;
170
- if ( ! requiredRuntimeVersion ) {
171
- throw new Error ( `Unable to parse minimum required version ${ DotNetRuntimeVersion } ` ) ;
172
- }
173
-
174
- const coreRuntimeVersions = dotnetInfo . Runtimes [ 'Microsoft.NETCore.App' ] ;
175
- let matchingRuntime : RuntimeInfo | undefined = undefined ;
176
- for ( const runtime of coreRuntimeVersions ) {
177
- // We consider a match if the runtime is greater than or equal to the required version since we roll forward.
178
- if ( semver . gte ( runtime . Version , requiredRuntimeVersion ) ) {
179
- matchingRuntime = runtime ;
180
- break ;
181
- }
182
- }
183
-
184
- if ( ! matchingRuntime ) {
185
- throw new Error (
186
- `No compatible .NET runtime found. Minimum required version is ${ DotNetRuntimeVersion } .`
187
- ) ;
188
- }
189
-
190
- // The .NET install layout is a well known structure on all platforms.
191
- // See https://github.com/dotnet/designs/blob/main/accepted/2020/install-locations.md#net-core-install-layout
192
- //
193
- // Therefore we know that the runtime path is always in <install root>/shared/<runtime name>
194
- // and the dotnet executable is always at <install root>/dotnet(.exe).
195
- //
196
- // Since dotnet --list-runtimes will always use the real assembly path to output the runtime folder (no symlinks!)
197
- // we know the dotnet executable will be two folders up in the install root.
198
- const runtimeFolderPath = matchingRuntime . Path ;
199
- const installFolder = path . dirname ( path . dirname ( runtimeFolderPath ) ) ;
200
- const dotnetExecutablePath = path . join ( installFolder , this . getDotnetExecutableName ( ) ) ;
201
- if ( ! existsSync ( dotnetExecutablePath ) ) {
202
- throw new Error (
203
- `dotnet executable path does not exist: ${ dotnetExecutablePath } , dotnet installation may be corrupt.`
204
- ) ;
205
- }
206
-
207
- this . channel . appendLine ( `Using dotnet configured on PATH` ) ;
208
- return dotnetExecutablePath ;
209
- } catch ( e ) {
210
- this . channel . appendLine (
211
- 'Failed to find dotnet info from path, falling back to acquire runtime via ms-dotnettools.vscode-dotnet-runtime'
212
- ) ;
213
- if ( e instanceof Error ) {
214
- this . channel . appendLine ( e . message ) ;
215
- }
216
- }
217
-
218
- return undefined ;
219
- }
220
-
221
88
private async getArchitectureFromTargetPlatform ( ) : Promise < string | undefined > {
222
89
const vsixManifestFile = path . join ( this . extensionPath , '.vsixmanifest' ) ;
223
90
if ( ! existsSync ( vsixManifestFile ) ) {
0 commit comments