55
66import * as path from 'path' ;
77import * as vscode from 'vscode' ;
8+ import * as semver from 'semver' ;
89import { HostExecutableInformation } from '../shared/constants/hostExecutableInformation' ;
910import { IHostExecutableResolver } from '../shared/constants/IHostExecutableResolver' ;
1011import { PlatformInformation } from '../shared/platform' ;
1112import { Options } from '../shared/options' ;
1213import { existsSync } from 'fs' ;
1314import { CSharpExtensionId } from '../constants/csharpExtensionId' ;
15+ import { promisify } from 'util' ;
16+ import { exec } from 'child_process' ;
17+ import { pathExistsSync } from 'fs-extra' ;
1418
1519export const DotNetRuntimeVersion = '7.0' ;
1620
@@ -22,19 +26,35 @@ interface IDotnetAcquireResult {
2226 * Resolves the dotnet runtime for a server executable from given options and the dotnet runtime VSCode extension.
2327 */
2428export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
29+ private readonly minimumDotnetVersion = '7.0.100' ;
2530 constructor (
2631 private platformInfo : PlatformInformation ,
2732 /**
2833 * This is a function instead of a string because the server path can change while the extension is active (when the option changes).
2934 */
30- private getServerPath : ( options : Options , platform : PlatformInformation ) => string
35+ private getServerPath : ( options : Options , platform : PlatformInformation ) => string ,
36+ private channel : vscode . OutputChannel
3137 ) { }
3238
3339 private hostInfo : HostExecutableInformation | undefined ;
3440
3541 async getHostExecutableInfo ( options : Options ) : Promise < HostExecutableInformation > {
3642 let dotnetRuntimePath = options . commonOptions . dotnetPath ;
3743 const serverPath = this . getServerPath ( options , 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 : process . env ,
53+ } ;
54+ }
55+ }
56+
57+ // We didn't find it on the path, see if we can install the correct runtime using the runtime extension.
3858 if ( ! dotnetRuntimePath ) {
3959 const dotnetInfo = await this . acquireDotNetProcessDependencies ( serverPath ) ;
4060 dotnetRuntimePath = path . dirname ( dotnetInfo . path ) ;
@@ -101,4 +121,55 @@ export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver {
101121
102122 return dotnetInfo ;
103123 }
124+
125+ /**
126+ * Checks dotnet --version to see if the value on the path is greater than the minimum required version.
127+ * This is adapated from similar O# server logic and should be removed when we have a stable acquisition extension.
128+ * @returns true if the dotnet version is greater than the minimum required version, false otherwise.
129+ */
130+ private async findDotnetFromPath ( ) : Promise < string | undefined > {
131+ try {
132+ // Run dotnet version to see if there is a valid dotnet on the path with a high enough version.
133+ const result = await promisify ( exec ) ( `dotnet --version` ) ;
134+
135+ if ( result . stderr ) {
136+ throw new Error ( `Unable to read dotnet version information. Error ${ result . stderr } ` ) ;
137+ }
138+
139+ const dotnetVersion = semver . parse ( result . stdout . trimEnd ( ) ) ;
140+ if ( ! dotnetVersion ) {
141+ throw new Error ( `Unknown result output from 'dotnet --version'. Received ${ result . stdout } ` ) ;
142+ }
143+
144+ if ( semver . lt ( dotnetVersion , this . minimumDotnetVersion ) ) {
145+ throw new Error (
146+ `Found dotnet version ${ dotnetVersion } . Minimum required version is ${ this . minimumDotnetVersion } .`
147+ ) ;
148+ }
149+
150+ // Find the location of the dotnet on path.
151+ const command = this . platformInfo . isWindows ( ) ? 'where' : 'which' ;
152+ const whereOutput = await promisify ( exec ) ( `${ command } dotnet` ) ;
153+ if ( ! whereOutput . stdout ) {
154+ throw new Error ( `Unable to find dotnet from where.` ) ;
155+ }
156+
157+ const path = whereOutput . stdout . trim ( ) ;
158+ if ( ! pathExistsSync ( path ) ) {
159+ throw new Error ( `dotnet path does not exist: ${ path } ` ) ;
160+ }
161+
162+ this . channel . appendLine ( `Using dotnet configured on PATH` ) ;
163+ return path ;
164+ } catch ( e ) {
165+ this . channel . appendLine (
166+ 'Failed to find dotnet info from path, falling back to acquire runtime via ms-dotnettools.vscode-dotnet-runtime'
167+ ) ;
168+ if ( e instanceof Error ) {
169+ this . channel . appendLine ( e . message ) ;
170+ }
171+ }
172+
173+ return undefined ;
174+ }
104175}
0 commit comments