2
2
// Use of this source code is governed by a BSD-style license that can be
3
3
// found in the LICENSE file.
4
4
5
+ import 'package:meta/meta.dart' ;
5
6
import 'package:process/process.dart' ;
6
7
7
8
import '../base/common.dart' ;
@@ -33,7 +34,7 @@ class VisualStudio {
33
34
/// Versions older than 2017 Update 2 won't be detected, so error messages to
34
35
/// users should take into account that [false] may mean that the user may
35
36
/// have an old version rather than no installation at all.
36
- bool get isInstalled => _bestVisualStudioDetails.isNotEmpty ;
37
+ bool get isInstalled => _bestVisualStudioDetails != null ;
37
38
38
39
bool get isAtLeastMinimumVersion {
39
40
final int ? installedMajorVersion = _majorVersion;
@@ -42,30 +43,25 @@ class VisualStudio {
42
43
43
44
/// True if there is a version of Visual Studio with all the components
44
45
/// necessary to build the project.
45
- bool get hasNecessaryComponents => _usableVisualStudioDetails.isNotEmpty ;
46
+ bool get hasNecessaryComponents => _bestVisualStudioDetails ? .isUsable ?? false ;
46
47
47
48
/// The name of the Visual Studio install.
48
49
///
49
50
/// For instance: "Visual Studio Community 2019".
50
- String ? get displayName => _bestVisualStudioDetails[_displayNameKey] as String ? ;
51
+ String ? get displayName => _bestVisualStudioDetails? .displayName ;
51
52
52
53
/// The user-friendly version number of the Visual Studio install.
53
54
///
54
55
/// 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;
61
57
62
58
/// The directory where Visual Studio is installed.
63
- String ? get installLocation => _bestVisualStudioDetails[_installationPathKey] as String ? ;
59
+ String ? get installLocation => _bestVisualStudioDetails? .installationPath ;
64
60
65
61
/// The full version of the Visual Studio install.
66
62
///
67
63
/// For instance: "15.4.27004.2002".
68
- String ? get fullVersion => _bestVisualStudioDetails[_fullVersionKey] as String ? ;
64
+ String ? get fullVersion => _bestVisualStudioDetails? .fullVersion ;
69
65
70
66
// Properties that determine the status of the installation. There might be
71
67
// Visual Studio versions that don't include them, so default to a "valid" value to
@@ -75,27 +71,27 @@ class VisualStudio {
75
71
///
76
72
/// False if installation is not found.
77
73
bool get isComplete {
78
- if (_bestVisualStudioDetails.isEmpty ) {
74
+ if (_bestVisualStudioDetails == null ) {
79
75
return false ;
80
76
}
81
- return _bestVisualStudioDetails[_isCompleteKey] as bool ? ?? true ;
77
+ return _bestVisualStudioDetails! .isComplete ?? true ;
82
78
}
83
79
84
80
/// True if Visual Studio is launchable.
85
81
///
86
82
/// False if installation is not found.
87
83
bool get isLaunchable {
88
- if (_bestVisualStudioDetails.isEmpty ) {
84
+ if (_bestVisualStudioDetails == null ) {
89
85
return false ;
90
86
}
91
- return _bestVisualStudioDetails[_isLaunchableKey] as bool ? ?? true ;
87
+ return _bestVisualStudioDetails! .isLaunchable ?? true ;
92
88
}
93
89
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 ;
96
92
97
93
/// 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 ;
99
95
100
96
/// The name of the recommended Visual Studio installer workload.
101
97
String get workloadDescription => 'Desktop development with C++' ;
@@ -150,12 +146,13 @@ class VisualStudio {
150
146
/// The path to CMake, or null if no Visual Studio installation has
151
147
/// the components necessary to build.
152
148
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 ) {
155
151
return null ;
156
152
}
153
+
157
154
return _fileSystem.path.joinAll (< String > [
158
- _usableVisualStudioDetails[_installationPathKey] as String ,
155
+ details.installationPath ! ,
159
156
'Common7' ,
160
157
'IDE' ,
161
158
'CommonExtensions' ,
@@ -253,44 +250,18 @@ class VisualStudio {
253
250
/// vswhere argument to allow prerelease versions.
254
251
static const String _vswherePrereleaseArgument = '-prerelease' ;
255
252
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
-
283
253
/// The registry path for Windows 10 SDK installation details.
284
254
static const String _windows10SdkRegistryPath = r'HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0' ;
285
255
286
256
/// The registry key in _windows10SdkRegistryPath for the folder where the
287
257
/// SDKs are installed.
288
258
static const String _windows10SdkRegistryKey = 'InstallationFolder' ;
289
259
290
- /// Returns the details dictionary for the newest version of Visual Studio.
260
+ /// Returns the details of the newest version of Visual Studio.
261
+ ///
291
262
/// If [validateRequirements] is set, the search will be limited to versions
292
263
/// that have all of the required workloads and components.
293
- Map < String , dynamic > ? _visualStudioDetails ({
264
+ VswhereDetails ? _visualStudioDetails ({
294
265
bool validateRequirements = false ,
295
266
List <String >? additionalArguments,
296
267
String ? requiredWorkload
@@ -321,7 +292,7 @@ class VisualStudio {
321
292
final List <Map <String , dynamic >> installations =
322
293
(json.decode (whereResult.stdout) as List <dynamic >).cast <Map <String , dynamic >>();
323
294
if (installations.isNotEmpty) {
324
- return installations[0 ];
295
+ return VswhereDetails . fromJson (validateRequirements, installations[0 ]) ;
325
296
}
326
297
}
327
298
} on ArgumentError {
@@ -334,90 +305,39 @@ class VisualStudio {
334
305
return null ;
335
306
}
336
307
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.
361
309
///
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.
365
317
final List <String > minimumVersionArguments = < String > [
366
318
_vswhereMinVersionArgument,
367
319
_minimumSupportedVersion.toString (),
368
320
];
369
- Map <String , dynamic >? visualStudioDetails;
370
- // Check in the order of stable VS, stable BT, pre-release VS, pre-release BT
371
321
for (final bool checkForPrerelease in < bool > [false , true ]) {
372
322
for (final String requiredWorkload in _requiredWorkloads) {
373
- visualStudioDetails ?? = _visualStudioDetails (
323
+ final VswhereDetails ? result = _visualStudioDetails (
374
324
validateRequirements: true ,
375
325
additionalArguments: checkForPrerelease
376
326
? < String > [...minimumVersionArguments, _vswherePrereleaseArgument]
377
327
: minimumVersionArguments,
378
328
requiredWorkload: requiredWorkload);
379
- }
380
- }
381
329
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
+ }
388
333
}
389
334
}
390
- return usableVisualStudioDetails ?? < String , dynamic > {};
391
- }();
392
335
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 (
404
339
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
+ }();
421
341
422
342
/// Returns the installation location of the Windows 10 SDKs, or null if the
423
343
/// registry doesn't contain that information.
@@ -471,3 +391,87 @@ class VisualStudio {
471
391
return highestVersion == null ? null : '10.$highestVersion ' ;
472
392
}
473
393
}
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