@@ -12,10 +12,9 @@ import { PlatformInformation } from '../shared/platform';
12
12
import { commonOptions } from '../shared/options' ;
13
13
import { existsSync } from 'fs' ;
14
14
import { CSharpExtensionId } from '../constants/csharpExtensionId' ;
15
- import { promisify } from 'util' ;
16
- import { exec } from 'child_process' ;
17
15
import { getDotnetInfo } from '../shared/utils/getDotnetInfo' ;
18
- import { readFile , realpath } from 'fs/promises' ;
16
+ import { readFile } from 'fs/promises' ;
17
+ import { RuntimeInfo } from '../shared/utils/dotnetInfo' ;
19
18
20
19
export const DotNetRuntimeVersion = '7.0' ;
21
20
@@ -62,7 +61,7 @@ export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
62
61
dotnetRuntimePath = path . dirname ( dotnetInfo . path ) ;
63
62
}
64
63
65
- const dotnetExecutableName = this . platformInfo . isWindows ( ) ? 'dotnet.exe' : 'dotnet' ;
64
+ const dotnetExecutableName = this . getDotnetExecutableName ( ) ;
66
65
const dotnetExecutablePath = path . join ( dotnetRuntimePath , dotnetExecutableName ) ;
67
66
if ( ! existsSync ( dotnetExecutablePath ) ) {
68
67
throw new Error ( `Cannot find dotnet path '${ dotnetExecutablePath } '` ) ;
@@ -151,39 +150,40 @@ export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
151
150
}
152
151
153
152
const coreRuntimeVersions = dotnetInfo . Runtimes [ 'Microsoft.NETCore.App' ] ;
154
- let foundRuntimeVersion = false ;
155
- for ( const version of coreRuntimeVersions ) {
153
+ let matchingRuntime : RuntimeInfo | undefined = undefined ;
154
+ for ( const runtime of coreRuntimeVersions ) {
156
155
// We consider a match if the runtime is greater than or equal to the required version since we roll forward.
157
- if ( semver . gt ( version , requiredRuntimeVersion ) ) {
158
- foundRuntimeVersion = true ;
156
+ if ( semver . gt ( runtime . Version , requiredRuntimeVersion ) ) {
157
+ matchingRuntime = runtime ;
159
158
break ;
160
159
}
161
160
}
162
161
163
- if ( ! foundRuntimeVersion ) {
162
+ if ( ! matchingRuntime ) {
164
163
throw new Error (
165
164
`No compatible .NET runtime found. Minimum required version is ${ this . minimumDotnetRuntimeVersion } .`
166
165
) ;
167
166
}
168
167
169
- // Find the location of the dotnet on path.
170
- const command = this . platformInfo . isWindows ( ) ? 'where' : 'which' ;
171
- const whereOutput = await promisify ( exec ) ( `${ command } dotnet` ) ;
172
- if ( ! whereOutput . stdout ) {
173
- throw new Error ( `Unable to find dotnet from ${ command } .` ) ;
174
- }
175
-
176
- // There could be multiple paths output from where. Take the first since that is what we used to run dotnet --info.
177
- const path = whereOutput . stdout . trim ( ) . replace ( / \r / gm, '' ) . split ( '\n' ) [ 0 ] ;
178
- if ( ! existsSync ( path ) ) {
179
- throw new Error ( `dotnet path does not exist: ${ path } ` ) ;
168
+ // The .NET install layout is a well known structure on all platforms.
169
+ // See https://github.com/dotnet/designs/blob/main/accepted/2020/install-locations.md#net-core-install-layout
170
+ //
171
+ // Therefore we know that the runtime path is always in <install root>/shared/<runtime name>
172
+ // and the dotnet executable is always at <install root>/dotnet(.exe).
173
+ //
174
+ // Since dotnet --list-runtimes will always use the real assembly path to output the runtime folder (no symlinks!)
175
+ // we know the dotnet executable will be two folders up in the install root.
176
+ const runtimeFolderPath = matchingRuntime . Path ;
177
+ const installFolder = path . dirname ( path . dirname ( runtimeFolderPath ) ) ;
178
+ const dotnetExecutablePath = path . join ( installFolder , this . getDotnetExecutableName ( ) ) ;
179
+ if ( ! existsSync ( dotnetExecutablePath ) ) {
180
+ throw new Error (
181
+ `dotnet executable path does not exist: ${ dotnetExecutablePath } , dotnet installation may be corrupt.`
182
+ ) ;
180
183
}
181
184
182
185
this . channel . appendLine ( `Using dotnet configured on PATH` ) ;
183
-
184
- // If dotnet is just a symlink, resolve it to the actual executable so
185
- // callers will be able to get the actual directory containing the exe.
186
- return await realpath ( path ) ;
186
+ return dotnetExecutablePath ;
187
187
} catch ( e ) {
188
188
this . channel . appendLine (
189
189
'Failed to find dotnet info from path, falling back to acquire runtime via ms-dotnettools.vscode-dotnet-runtime'
@@ -236,4 +236,8 @@ export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
236
236
throw new Error ( `Unknown extension target platform: ${ targetPlatform } ` ) ;
237
237
}
238
238
}
239
+
240
+ private getDotnetExecutableName ( ) : string {
241
+ return this . platformInfo . isWindows ( ) ? 'dotnet.exe' : 'dotnet' ;
242
+ }
239
243
}
0 commit comments