22// Use of this source code is governed by a BSD-style license that can be
33// found in the LICENSE file.
44
5+ import 'package:meta/meta.dart' ;
56import 'package:process/process.dart' ;
67
78import '../base/common.dart' ;
@@ -33,7 +34,7 @@ class VisualStudio {
3334 /// Versions older than 2017 Update 2 won't be detected, so error messages to
3435 /// users should take into account that [false] may mean that the user may
3536 /// have an old version rather than no installation at all.
36- bool get isInstalled => _bestVisualStudioDetails.isNotEmpty ;
37+ bool get isInstalled => _bestVisualStudioDetails != null ;
3738
3839 bool get isAtLeastMinimumVersion {
3940 final int ? installedMajorVersion = _majorVersion;
@@ -42,30 +43,25 @@ class VisualStudio {
4243
4344 /// True if there is a version of Visual Studio with all the components
4445 /// necessary to build the project.
45- bool get hasNecessaryComponents => _usableVisualStudioDetails.isNotEmpty ;
46+ bool get hasNecessaryComponents => _bestVisualStudioDetails ? .isUsable ?? false ;
4647
4748 /// The name of the Visual Studio install.
4849 ///
4950 /// For instance: "Visual Studio Community 2019".
50- String ? get displayName => _bestVisualStudioDetails[_displayNameKey] as String ? ;
51+ String ? get displayName => _bestVisualStudioDetails? .displayName ;
5152
5253 /// The user-friendly version number of the Visual Studio install.
5354 ///
5455 /// For instance: "15.4.0".
55- String ? get displayVersion {
56- if (_bestVisualStudioDetails[_catalogKey] == null ) {
57- return null ;
58- }
59- return (_bestVisualStudioDetails[_catalogKey] as Map <String , dynamic >)[_catalogDisplayVersionKey] as String ? ;
60- }
56+ String ? get displayVersion => _bestVisualStudioDetails? .catalogDisplayVersion;
6157
6258 /// The directory where Visual Studio is installed.
63- String ? get installLocation => _bestVisualStudioDetails[_installationPathKey] as String ? ;
59+ String ? get installLocation => _bestVisualStudioDetails? .installationPath ;
6460
6561 /// The full version of the Visual Studio install.
6662 ///
6763 /// For instance: "15.4.27004.2002".
68- String ? get fullVersion => _bestVisualStudioDetails[_fullVersionKey] as String ? ;
64+ String ? get fullVersion => _bestVisualStudioDetails? .fullVersion ;
6965
7066 // Properties that determine the status of the installation. There might be
7167 // Visual Studio versions that don't include them, so default to a "valid" value to
@@ -75,27 +71,27 @@ class VisualStudio {
7571 ///
7672 /// False if installation is not found.
7773 bool get isComplete {
78- if (_bestVisualStudioDetails.isEmpty ) {
74+ if (_bestVisualStudioDetails == null ) {
7975 return false ;
8076 }
81- return _bestVisualStudioDetails[_isCompleteKey] as bool ? ?? true ;
77+ return _bestVisualStudioDetails! .isComplete ?? true ;
8278 }
8379
8480 /// True if Visual Studio is launchable.
8581 ///
8682 /// False if installation is not found.
8783 bool get isLaunchable {
88- if (_bestVisualStudioDetails.isEmpty ) {
84+ if (_bestVisualStudioDetails == null ) {
8985 return false ;
9086 }
91- return _bestVisualStudioDetails[_isLaunchableKey] as bool ? ?? true ;
87+ return _bestVisualStudioDetails! .isLaunchable ?? true ;
9288 }
9389
94- /// True if the Visual Studio installation is as pre-release version.
95- bool get isPrerelease => _bestVisualStudioDetails[_isPrereleaseKey] as bool ? ?? false ;
90+ /// True if the Visual Studio installation is a pre-release version.
91+ bool get isPrerelease => _bestVisualStudioDetails? .isPrerelease ?? false ;
9692
9793 /// True if a reboot is required to complete the Visual Studio installation.
98- bool get isRebootRequired => _bestVisualStudioDetails[_isRebootRequiredKey] as bool ? ?? false ;
94+ bool get isRebootRequired => _bestVisualStudioDetails? .isRebootRequired ?? false ;
9995
10096 /// The name of the recommended Visual Studio installer workload.
10197 String get workloadDescription => 'Desktop development with C++' ;
@@ -150,12 +146,13 @@ class VisualStudio {
150146 /// The path to CMake, or null if no Visual Studio installation has
151147 /// the components necessary to build.
152148 String ? get cmakePath {
153- final Map < String , dynamic > details = _usableVisualStudioDetails ;
154- if (details.isEmpty || _usableVisualStudioDetails[_installationPathKey] == null ) {
149+ final VswhereDetails ? details = _bestVisualStudioDetails ;
150+ if (details == null || ! details.isUsable || details.installationPath == null ) {
155151 return null ;
156152 }
153+
157154 return _fileSystem.path.joinAll (< String > [
158- _usableVisualStudioDetails[_installationPathKey] as String ,
155+ details.installationPath ! ,
159156 'Common7' ,
160157 'IDE' ,
161158 'CommonExtensions' ,
@@ -253,44 +250,18 @@ class VisualStudio {
253250 /// vswhere argument to allow prerelease versions.
254251 static const String _vswherePrereleaseArgument = '-prerelease' ;
255252
256- // Keys in a VS details dictionary returned from vswhere.
257-
258- /// The root directory of the Visual Studio installation.
259- static const String _installationPathKey = 'installationPath' ;
260-
261- /// The user-friendly name of the installation.
262- static const String _displayNameKey = 'displayName' ;
263-
264- /// The complete version.
265- static const String _fullVersionKey = 'installationVersion' ;
266-
267- /// Keys for the status of the installation.
268- static const String _isCompleteKey = 'isComplete' ;
269- static const String _isLaunchableKey = 'isLaunchable' ;
270- static const String _isRebootRequiredKey = 'isRebootRequired' ;
271-
272- /// The 'catalog' entry containing more details.
273- static const String _catalogKey = 'catalog' ;
274-
275- /// The key for a pre-release version.
276- static const String _isPrereleaseKey = 'isPrerelease' ;
277-
278- /// The user-friendly version.
279- ///
280- /// This key is under the 'catalog' entry.
281- static const String _catalogDisplayVersionKey = 'productDisplayVersion' ;
282-
283253 /// The registry path for Windows 10 SDK installation details.
284254 static const String _windows10SdkRegistryPath = r'HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0' ;
285255
286256 /// The registry key in _windows10SdkRegistryPath for the folder where the
287257 /// SDKs are installed.
288258 static const String _windows10SdkRegistryKey = 'InstallationFolder' ;
289259
290- /// Returns the details dictionary for the newest version of Visual Studio.
260+ /// Returns the details of the newest version of Visual Studio.
261+ ///
291262 /// If [validateRequirements] is set, the search will be limited to versions
292263 /// that have all of the required workloads and components.
293- Map < String , dynamic > ? _visualStudioDetails ({
264+ VswhereDetails ? _visualStudioDetails ({
294265 bool validateRequirements = false ,
295266 List <String >? additionalArguments,
296267 String ? requiredWorkload
@@ -321,7 +292,7 @@ class VisualStudio {
321292 final List <Map <String , dynamic >> installations =
322293 (json.decode (whereResult.stdout) as List <dynamic >).cast <Map <String , dynamic >>();
323294 if (installations.isNotEmpty) {
324- return installations[0 ];
295+ return VswhereDetails . fromJson (validateRequirements, installations[0 ]) ;
325296 }
326297 }
327298 } on ArgumentError {
@@ -334,90 +305,39 @@ class VisualStudio {
334305 return null ;
335306 }
336307
337- /// Checks if the given installation has issues that the user must resolve.
338- ///
339- /// Returns false if the required information is missing since older versions
340- /// of Visual Studio might not include them.
341- bool installationHasIssues (Map <String , dynamic >installationDetails) {
342- assert (installationDetails != null );
343- if (installationDetails[_isCompleteKey] != null && ! (installationDetails[_isCompleteKey] as bool )) {
344- return true ;
345- }
346-
347- if (installationDetails[_isLaunchableKey] != null && ! (installationDetails[_isLaunchableKey] as bool )) {
348- return true ;
349- }
350-
351- if (installationDetails[_isRebootRequiredKey] != null && installationDetails[_isRebootRequiredKey] as bool ) {
352- return true ;
353- }
354-
355- return false ;
356- }
357-
358- /// Returns the details dictionary for the latest version of Visual Studio
359- /// that has all required components and is a supported version, or {} if
360- /// there is no such installation.
308+ /// Returns the details of the best available version of Visual Studio.
361309 ///
362- /// If no installation is found, the cached VS details are set to an empty map
363- /// to avoid repeating vswhere queries that have already not found an installation.
364- late final Map <String , dynamic > _usableVisualStudioDetails = (){
310+ /// If there's a version that has all the required components, that
311+ /// will be returned, otherwise returns the latest installed version regardless
312+ /// of components and version, or null if no such installation is found.
313+ late final VswhereDetails ? _bestVisualStudioDetails = () {
314+ // First, attempt to find the latest version of Visual Studio that satifies
315+ // both the minimum supported version and the required workloads.
316+ // Check in the order of stable VS, stable BT, pre-release VS, pre-release BT.
365317 final List <String > minimumVersionArguments = < String > [
366318 _vswhereMinVersionArgument,
367319 _minimumSupportedVersion.toString (),
368320 ];
369- Map <String , dynamic >? visualStudioDetails;
370- // Check in the order of stable VS, stable BT, pre-release VS, pre-release BT
371321 for (final bool checkForPrerelease in < bool > [false , true ]) {
372322 for (final String requiredWorkload in _requiredWorkloads) {
373- visualStudioDetails ?? = _visualStudioDetails (
323+ final VswhereDetails ? result = _visualStudioDetails (
374324 validateRequirements: true ,
375325 additionalArguments: checkForPrerelease
376326 ? < String > [...minimumVersionArguments, _vswherePrereleaseArgument]
377327 : minimumVersionArguments,
378328 requiredWorkload: requiredWorkload);
379- }
380- }
381329
382- Map <String , dynamic >? usableVisualStudioDetails;
383- if (visualStudioDetails != null ) {
384- if (installationHasIssues (visualStudioDetails)) {
385- _cachedAnyVisualStudioDetails = visualStudioDetails;
386- } else {
387- usableVisualStudioDetails = visualStudioDetails;
330+ if (result != null ) {
331+ return result;
332+ }
388333 }
389334 }
390- return usableVisualStudioDetails ?? < String , dynamic > {};
391- }();
392335
393- /// Returns the details dictionary of the latest version of Visual Studio,
394- /// regardless of components and version, or {} if no such installation is
395- /// found.
396- ///
397- /// If no installation is found, the cached VS details are set to an empty map
398- /// to avoid repeating vswhere queries that have already not found an
399- /// installation.
400- Map <String , dynamic >? _cachedAnyVisualStudioDetails;
401- Map <String , dynamic > get _anyVisualStudioDetails {
402- // Search for all types of installations.
403- _cachedAnyVisualStudioDetails ?? = _visualStudioDetails (
336+ // An installation that satifies requirements could not be found.
337+ // Fallback to the latest Visual Studio installation.
338+ return _visualStudioDetails (
404339 additionalArguments: < String > [_vswherePrereleaseArgument, '-all' ]);
405- // Add a sentinel empty value to avoid querying vswhere again.
406- _cachedAnyVisualStudioDetails ?? = < String , dynamic > {};
407- return _cachedAnyVisualStudioDetails! ;
408- }
409-
410- /// Returns the details dictionary of the best available version of Visual
411- /// Studio.
412- ///
413- /// If there's a version that has all the required components, that
414- /// will be returned, otherwise returns the latest installed version (if any).
415- Map <String , dynamic > get _bestVisualStudioDetails {
416- if (_usableVisualStudioDetails.isNotEmpty) {
417- return _usableVisualStudioDetails;
418- }
419- return _anyVisualStudioDetails;
420- }
340+ }();
421341
422342 /// Returns the installation location of the Windows 10 SDKs, or null if the
423343 /// registry doesn't contain that information.
@@ -471,3 +391,87 @@ class VisualStudio {
471391 return highestVersion == null ? null : '10.$highestVersion ' ;
472392 }
473393}
394+
395+ /// The details of a Visual Studio installation according to vswhere.
396+ @visibleForTesting
397+ class VswhereDetails {
398+ const VswhereDetails ({
399+ required this .meetsRequirements,
400+ required this .installationPath,
401+ required this .displayName,
402+ required this .fullVersion,
403+ required this .isComplete,
404+ required this .isLaunchable,
405+ required this .isRebootRequired,
406+ required this .isPrerelease,
407+ required this .catalogDisplayVersion,
408+ });
409+
410+ /// Create a `VswhereDetails` from the JSON output of vswhere.exe.
411+ factory VswhereDetails .fromJson (
412+ bool meetsRequirements,
413+ Map <String , dynamic > details
414+ ) {
415+ final Map <String , dynamic >? catalog = details['catalog' ] as Map <String , dynamic >? ;
416+
417+ return VswhereDetails (
418+ meetsRequirements: meetsRequirements,
419+ installationPath: details['installationPath' ] as String ? ,
420+ displayName: details['displayName' ] as String ? ,
421+ fullVersion: details['installationVersion' ] as String ? ,
422+ isComplete: details['isComplete' ] as bool ? ,
423+ isLaunchable: details['isLaunchable' ] as bool ? ,
424+ isRebootRequired: details['isRebootRequired' ] as bool ? ,
425+ isPrerelease: details['isPrerelease' ] as bool ? ,
426+ catalogDisplayVersion: catalog == null ? null : catalog['productDisplayVersion' ] as String ? ,
427+ );
428+ }
429+
430+ /// Whether the installation satisfies the required workloads and minimum version.
431+ final bool meetsRequirements;
432+
433+ /// The root directory of the Visual Studio installation.
434+ final String ? installationPath;
435+
436+ /// The user-friendly name of the installation.
437+ final String ? displayName;
438+
439+ /// The complete version.
440+ final String ? fullVersion;
441+
442+ /// Keys for the status of the installation.
443+ final bool ? isComplete;
444+ final bool ? isLaunchable;
445+ final bool ? isRebootRequired;
446+
447+ /// The key for a pre-release version.
448+ final bool ? isPrerelease;
449+
450+ /// The user-friendly version.
451+ final String ? catalogDisplayVersion;
452+
453+ /// Checks if the Visual Studio installation can be used by Flutter.
454+ ///
455+ /// Returns false if the installation has issues the user must resolve.
456+ /// This may return true even if required information is missing as older
457+ /// versions of Visual Studio might not include them.
458+ bool get isUsable {
459+ if (! meetsRequirements) {
460+ return false ;
461+ }
462+
463+ if (! (isComplete ?? true )) {
464+ return false ;
465+ }
466+
467+ if (! (isLaunchable ?? true )) {
468+ return false ;
469+ }
470+
471+ if (isRebootRequired ?? false ) {
472+ return false ;
473+ }
474+
475+ return true ;
476+ }
477+ }
0 commit comments