5
5
6
6
import * as path from 'path' ;
7
7
import * as vscode from 'vscode' ;
8
+ import * as semver from 'semver' ;
8
9
import { HostExecutableInformation } from '../shared/constants/hostExecutableInformation' ;
9
10
import { IHostExecutableResolver } from '../shared/constants/IHostExecutableResolver' ;
10
11
import { PlatformInformation } from '../shared/platform' ;
11
12
import { Options } from '../shared/options' ;
12
13
import { existsSync } from 'fs' ;
13
14
import { CSharpExtensionId } from '../constants/csharpExtensionId' ;
15
+ import { promisify } from 'util' ;
16
+ import { exec } from 'child_process' ;
17
+ import { getDotnetInfo } from '../shared/utils/getDotnetInfo' ;
18
+ import { readFile } from 'fs/promises' ;
14
19
15
20
export const DotNetRuntimeVersion = '7.0' ;
16
21
@@ -22,19 +27,36 @@ interface IDotnetAcquireResult {
22
27
* Resolves the dotnet runtime for a server executable from given options and the dotnet runtime VSCode extension.
23
28
*/
24
29
export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
30
+ private readonly minimumDotnetVersion = '7.0.100' ;
25
31
constructor (
26
32
private platformInfo : PlatformInformation ,
27
33
/**
28
34
* This is a function instead of a string because the server path can change while the extension is active (when the option changes).
29
35
*/
30
- private getServerPath : ( options : Options , platform : PlatformInformation ) => string
36
+ private getServerPath : ( options : Options , platform : PlatformInformation ) => string ,
37
+ private channel : vscode . OutputChannel ,
38
+ private extensionPath : string
31
39
) { }
32
40
33
41
private hostInfo : HostExecutableInformation | undefined ;
34
42
35
43
async getHostExecutableInfo ( options : Options ) : Promise < HostExecutableInformation > {
36
44
let dotnetRuntimePath = options . commonOptions . dotnetPath ;
37
45
const serverPath = this . getServerPath ( options , this . platformInfo ) ;
46
+
47
+ // Check if we can find a valid dotnet from dotnet --version on the PATH.
48
+ if ( ! dotnetRuntimePath ) {
49
+ const dotnetPath = await this . findDotnetFromPath ( ) ;
50
+ if ( dotnetPath ) {
51
+ return {
52
+ version : '' /* We don't need to know the version - we've already verified its high enough */ ,
53
+ path : dotnetPath ,
54
+ env : process . env ,
55
+ } ;
56
+ }
57
+ }
58
+
59
+ // We didn't find it on the path, see if we can install the correct runtime using the runtime extension.
38
60
if ( ! dotnetRuntimePath ) {
39
61
const dotnetInfo = await this . acquireDotNetProcessDependencies ( serverPath ) ;
40
62
dotnetRuntimePath = path . dirname ( dotnetInfo . path ) ;
@@ -101,4 +123,103 @@ export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
101
123
102
124
return dotnetInfo ;
103
125
}
126
+
127
+ /**
128
+ * Checks dotnet --version to see if the value on the path is greater than the minimum required version.
129
+ * This is adapated from similar O# server logic and should be removed when we have a stable acquisition extension.
130
+ * @returns true if the dotnet version is greater than the minimum required version, false otherwise.
131
+ */
132
+ private async findDotnetFromPath ( ) : Promise < string | undefined > {
133
+ try {
134
+ const dotnetInfo = await getDotnetInfo ( [ ] ) ;
135
+ const dotnetVersionStr = dotnetInfo . Version ;
136
+
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
+ ) ;
146
+ }
147
+
148
+ const dotnetVersion = semver . parse ( dotnetVersionStr ) ;
149
+ if ( ! dotnetVersion ) {
150
+ throw new Error ( `Unknown result output from 'dotnet --version'. Received ${ dotnetVersionStr } ` ) ;
151
+ }
152
+
153
+ if ( semver . lt ( dotnetVersion , this . minimumDotnetVersion ) ) {
154
+ throw new Error (
155
+ `Found dotnet version ${ dotnetVersion } . Minimum required version is ${ this . minimumDotnetVersion } .`
156
+ ) ;
157
+ }
158
+
159
+ // Find the location of the dotnet on path.
160
+ const command = this . platformInfo . isWindows ( ) ? 'where' : 'which' ;
161
+ const whereOutput = await promisify ( exec ) ( `${ command } dotnet` ) ;
162
+ if ( ! whereOutput . stdout ) {
163
+ throw new Error ( `Unable to find dotnet from ${ command } .` ) ;
164
+ }
165
+
166
+ const path = whereOutput . stdout . trim ( ) ;
167
+ if ( ! existsSync ( path ) ) {
168
+ throw new Error ( `dotnet path does not exist: ${ path } ` ) ;
169
+ }
170
+
171
+ this . channel . appendLine ( `Using dotnet configured on PATH` ) ;
172
+ return path ;
173
+ } catch ( e ) {
174
+ this . channel . appendLine (
175
+ 'Failed to find dotnet info from path, falling back to acquire runtime via ms-dotnettools.vscode-dotnet-runtime'
176
+ ) ;
177
+ if ( e instanceof Error ) {
178
+ this . channel . appendLine ( e . message ) ;
179
+ }
180
+ }
181
+
182
+ return undefined ;
183
+ }
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 = / T a r g e t P l a t f o r m = " ( .* ) " / . 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
+ }
104
225
}
0 commit comments