Unverified Commit 362cde43 authored by Francisco Magdaleno's avatar Francisco Magdaleno Committed by GitHub

[windows] Searches for pre-release and 'all' Visual Studio installations (#40011)

parent f7d86a5b
...@@ -193,6 +193,12 @@ class UserMessages { ...@@ -193,6 +193,12 @@ class UserMessages {
String get visualStudioMissing => String get visualStudioMissing =>
'Visual Studio not installed; this is necessary for Windows development.\n' 'Visual Studio not installed; this is necessary for Windows development.\n'
'Download at https://visualstudio.microsoft.com/downloads/.'; 'Download at https://visualstudio.microsoft.com/downloads/.';
String get visualStudioIsPrerelease => 'The current Visual Studio installation is a pre-release version. It may not be '
'supported by Flutter yet.';
String get visualStudioNotLaunchable =>
'The current Visual Studio installation is not launchable. Please reinstall Visual Studio.';
String get visualStudioIsIncomplete => 'The current Visual Studio installation is incomplete. Please reinstall Visual Studio.';
String get visualStudioRebootRequired => 'Visual Studio requires a reboot of your system to complete installation.';
// Messages used in FlutterCommand // Messages used in FlutterCommand
String flutterElapsedTime(String name, String elapsedTime) => '"flutter $name" took $elapsedTime.'; String flutterElapsedTime(String name, String elapsedTime) => '"flutter $name" took $elapsedTime.';
......
...@@ -35,6 +35,10 @@ class VisualStudio { ...@@ -35,6 +35,10 @@ class VisualStudio {
String get displayVersion => String get displayVersion =>
_bestVisualStudioDetails[_catalogKey][_catalogDisplayVersionKey]; _bestVisualStudioDetails[_catalogKey][_catalogDisplayVersionKey];
/// True if the Visual Studio installation is as pre-release version.
bool get isPrerelease =>
_bestVisualStudioDetails[_catalogKey][_isPrereleaseKey];
/// The directory where Visual Studio is installed. /// The directory where Visual Studio is installed.
String get installLocation => _bestVisualStudioDetails[_installationPathKey]; String get installLocation => _bestVisualStudioDetails[_installationPathKey];
...@@ -43,6 +47,15 @@ class VisualStudio { ...@@ -43,6 +47,15 @@ class VisualStudio {
/// For instance: "15.4.27004.2002". /// For instance: "15.4.27004.2002".
String get fullVersion => _bestVisualStudioDetails[_fullVersionKey]; String get fullVersion => _bestVisualStudioDetails[_fullVersionKey];
/// True there is complete installation of Visual Studio.
bool get isComplete => _bestVisualStudioDetails[_isCompleteKey];
/// True if Visual Studio is launchable.
bool get isLaunchable => _bestVisualStudioDetails[_isLaunchableKey];
/// True if a reboot is required to complete the Visual Studio installation.
bool get isRebootRequired => _bestVisualStudioDetails[_isRebootRequiredKey];
/// The name of the recommended Visual Studio installer workload. /// The name of the recommended Visual Studio installer workload.
String get workloadDescription => 'Desktop development with C++'; String get workloadDescription => 'Desktop development with C++';
...@@ -120,6 +133,11 @@ class VisualStudio { ...@@ -120,6 +133,11 @@ class VisualStudio {
/// The complete version. /// The complete version.
static const String _fullVersionKey = 'installationVersion'; static const String _fullVersionKey = 'installationVersion';
/// Keys for the status of the installation.
static const String _isCompleteKey = 'isComplete';
static const String _isLaunchableKey = 'isLaunchable';
static const String _isRebootRequiredKey = 'isRebootRequired';
/// The 'catalog' entry containing more details. /// The 'catalog' entry containing more details.
static const String _catalogKey = 'catalog'; static const String _catalogKey = 'catalog';
...@@ -128,23 +146,36 @@ class VisualStudio { ...@@ -128,23 +146,36 @@ class VisualStudio {
/// This key is under the 'catalog' entry. /// This key is under the 'catalog' entry.
static const String _catalogDisplayVersionKey = 'productDisplayVersion'; static const String _catalogDisplayVersionKey = 'productDisplayVersion';
/// The key for a pre-release version.
///
/// This key is under the 'catalog' entry.
static const String _isPrereleaseKey = 'productMilestoneIsPreRelease';
/// vswhere argument keys
static const String _prereleaseKey = '-prerelease';
/// Returns the details dictionary for the newest version of Visual Studio /// Returns the details dictionary for the newest version of Visual Studio
/// that includes all of [requiredComponents], if there is one. /// that includes all of [requiredComponents], if there is one.
Map<String, dynamic> _visualStudioDetails({Iterable<String> requiredComponents}) { Map<String, dynamic> _visualStudioDetails(
{Iterable<String> requiredComponents, List<String> additionalArguments}) {
final List<String> requirementArguments = requiredComponents == null final List<String> requirementArguments = requiredComponents == null
? <String>[] ? <String>[]
: <String>['-requires', ...requiredComponents]; : <String>['-requires', ...requiredComponents];
try { try {
final ProcessResult whereResult = processManager.runSync(<String>[ final List<String> defaultArguments = <String>[
_vswherePath,
'-format', 'json', '-format', 'json',
'-utf8', '-utf8',
'-latest', '-latest',
];
final ProcessResult whereResult = processManager.runSync(<String>[
_vswherePath,
...defaultArguments,
...?additionalArguments,
...?requirementArguments, ...?requirementArguments,
]); ]);
if (whereResult.exitCode == 0) { if (whereResult.exitCode == 0) {
final List<Map<String, dynamic>> installations = json.decode(whereResult.stdout) final List<Map<String, dynamic>> installations =
.cast<Map<String, dynamic>>(); json.decode(whereResult.stdout).cast<Map<String, dynamic>>();
if (installations.isNotEmpty) { if (installations.isNotEmpty) {
return installations[0]; return installations[0];
} }
...@@ -157,12 +188,34 @@ class VisualStudio { ...@@ -157,12 +188,34 @@ class VisualStudio {
return null; return null;
} }
/// Checks if the given installation has issues that the user must resolve.
bool installationHasIssues(Map<String, dynamic>installationDetails) {
assert(installationDetails != null);
assert(installationDetails[_isCompleteKey] != null);
assert(installationDetails[_isRebootRequiredKey] != null);
assert(installationDetails[_isLaunchableKey] != null);
return installationDetails[_isCompleteKey] == false ||
installationDetails[_isRebootRequiredKey] == true ||
installationDetails[_isLaunchableKey] == false;
}
/// Returns the details dictionary for the latest version of Visual Studio /// Returns the details dictionary for the latest version of Visual Studio
/// that has all required components. /// that has all required components.
Map<String, dynamic> _cachedUsableVisualStudioDetails; Map<String, dynamic> _cachedUsableVisualStudioDetails;
Map<String, dynamic> get _usableVisualStudioDetails { Map<String, dynamic> get _usableVisualStudioDetails {
_cachedUsableVisualStudioDetails ??= _cachedUsableVisualStudioDetails ??=
_visualStudioDetails(requiredComponents: _requiredComponents().keys); _visualStudioDetails(requiredComponents: _requiredComponents().keys);
// If a stable version is not found, try searching for a pre-release version.
_cachedUsableVisualStudioDetails ??= _visualStudioDetails(
requiredComponents: _requiredComponents().keys,
additionalArguments: <String>[_prereleaseKey]);
if (_cachedUsableVisualStudioDetails != null) {
if (installationHasIssues(_cachedUsableVisualStudioDetails)) {
_cachedAnyVisualStudioDetails = _cachedUsableVisualStudioDetails;
return null;
}
}
return _cachedUsableVisualStudioDetails; return _cachedUsableVisualStudioDetails;
} }
...@@ -170,7 +223,9 @@ class VisualStudio { ...@@ -170,7 +223,9 @@ class VisualStudio {
/// regardless of components. /// regardless of components.
Map<String, dynamic> _cachedAnyVisualStudioDetails; Map<String, dynamic> _cachedAnyVisualStudioDetails;
Map<String, dynamic> get _anyVisualStudioDetails { Map<String, dynamic> get _anyVisualStudioDetails {
_cachedAnyVisualStudioDetails ??= _visualStudioDetails(); // Search for all types of installations.
_cachedAnyVisualStudioDetails ??= _visualStudioDetails(
additionalArguments: <String>[_prereleaseKey, '-all']);
return _cachedAnyVisualStudioDetails; return _cachedAnyVisualStudioDetails;
} }
......
...@@ -30,7 +30,21 @@ class VisualStudioValidator extends DoctorValidator { ...@@ -30,7 +30,21 @@ class VisualStudioValidator extends DoctorValidator {
visualStudio.fullVersion, visualStudio.fullVersion,
))); )));
if (!visualStudio.hasNecessaryComponents) { if (visualStudio.isPrerelease) {
messages.add(ValidationMessage(userMessages.visualStudioIsPrerelease));
}
// Messages for faulty installations.
if (visualStudio.isRebootRequired) {
status = ValidationType.partial;
messages.add(ValidationMessage.error(userMessages.visualStudioRebootRequired));
} else if (!visualStudio.isComplete) {
status = ValidationType.partial;
messages.add(ValidationMessage.error(userMessages.visualStudioIsIncomplete));
} else if (!visualStudio.isLaunchable) {
status = ValidationType.partial;
messages.add(ValidationMessage.error(userMessages.visualStudioNotLaunchable));
} else if (!visualStudio.hasNecessaryComponents) {
status = ValidationType.partial; status = ValidationType.partial;
final int majorVersion = int.tryParse(visualStudio.fullVersion.split('.')[0]); final int majorVersion = int.tryParse(visualStudio.fullVersion.split('.')[0]);
messages.add(ValidationMessage.error( messages.add(ValidationMessage.error(
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/doctor.dart';
import 'package:flutter_tools/src/windows/visual_studio.dart'; import 'package:flutter_tools/src/windows/visual_studio.dart';
import 'package:flutter_tools/src/windows/visual_studio_validator.dart'; import 'package:flutter_tools/src/windows/visual_studio_validator.dart';
...@@ -18,6 +19,24 @@ void main() { ...@@ -18,6 +19,24 @@ void main() {
setUp(() { setUp(() {
mockVisualStudio = MockVisualStudio(); mockVisualStudio = MockVisualStudio();
// Mock a valid VS installation.
when(mockVisualStudio.isInstalled).thenReturn(true);
when(mockVisualStudio.isPrerelease).thenReturn(false);
when(mockVisualStudio.isComplete).thenReturn(true);
when(mockVisualStudio.isLaunchable).thenReturn(true);
when(mockVisualStudio.isRebootRequired).thenReturn(false);
when(mockVisualStudio.hasNecessaryComponents).thenReturn(true);
});
testUsingContext('Emits a message when Visual Studio is a pre-release version', () async {
when(visualStudio.isPrerelease).thenReturn(true);
const VisualStudioValidator validator = VisualStudioValidator();
final ValidationResult result = await validator.validate();
final ValidationMessage expectedMessage = ValidationMessage(userMessages.visualStudioIsPrerelease);
expect(result.messages.contains(expectedMessage), true);
}, overrides: <Type, Generator>{
VisualStudio: () => mockVisualStudio,
}); });
testUsingContext('Emits missing status when Visual Studio is not installed', () async { testUsingContext('Emits missing status when Visual Studio is not installed', () async {
...@@ -29,8 +48,44 @@ void main() { ...@@ -29,8 +48,44 @@ void main() {
VisualStudio: () => mockVisualStudio, VisualStudio: () => mockVisualStudio,
}); });
testUsingContext('Emits a partial status when Visual Studio installation is incomplete', () async {
when(visualStudio.isComplete).thenReturn(false);
const VisualStudioValidator validator = VisualStudioValidator();
final ValidationResult result = await validator.validate();
final ValidationMessage expectedMessage = ValidationMessage.error(userMessages.visualStudioIsIncomplete);
expect(result.messages.contains(expectedMessage), true);
expect(result.type, ValidationType.partial);
}, overrides: <Type, Generator>{
VisualStudio: () => mockVisualStudio,
});
testUsingContext('Emits a partial status when Visual Studio installation needs rebooting', () async {
when(visualStudio.isRebootRequired).thenReturn(true);
const VisualStudioValidator validator = VisualStudioValidator();
final ValidationResult result = await validator.validate();
final ValidationMessage expectedMessage = ValidationMessage.error(userMessages.visualStudioRebootRequired);
expect(result.messages.contains(expectedMessage), true);
expect(result.type, ValidationType.partial);
}, overrides: <Type, Generator>{
VisualStudio: () => mockVisualStudio,
});
testUsingContext('Emits a partial status when Visual Studio installation is not launchable', () async {
when(visualStudio.isLaunchable).thenReturn(false);
const VisualStudioValidator validator = VisualStudioValidator();
final ValidationResult result = await validator.validate();
final ValidationMessage expectedMessage = ValidationMessage.error(userMessages.visualStudioNotLaunchable);
expect(result.messages.contains(expectedMessage), true);
expect(result.type, ValidationType.partial);
}, overrides: <Type, Generator>{
VisualStudio: () => mockVisualStudio,
});
testUsingContext('Emits partial status when Visual Studio is installed without necessary components', () async { testUsingContext('Emits partial status when Visual Studio is installed without necessary components', () async {
when(visualStudio.isInstalled).thenReturn(true);
when(visualStudio.hasNecessaryComponents).thenReturn(false); when(visualStudio.hasNecessaryComponents).thenReturn(false);
when(visualStudio.workloadDescription).thenReturn('Desktop development'); when(visualStudio.workloadDescription).thenReturn('Desktop development');
when(visualStudio.necessaryComponentDescriptions(any)).thenReturn(<String>['A', 'B']); when(visualStudio.necessaryComponentDescriptions(any)).thenReturn(<String>['A', 'B']);
...@@ -43,13 +98,22 @@ void main() { ...@@ -43,13 +98,22 @@ void main() {
}); });
testUsingContext('Emits installed status when Visual Studio is installed with necessary components', () async { testUsingContext('Emits installed status when Visual Studio is installed with necessary components', () async {
when(visualStudio.isInstalled).thenReturn(true);
when(visualStudio.hasNecessaryComponents).thenReturn(true);
const VisualStudioValidator validator = VisualStudioValidator(); const VisualStudioValidator validator = VisualStudioValidator();
final ValidationResult result = await validator.validate(); final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.installed); expect(result.type, ValidationType.installed);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
VisualStudio: () => mockVisualStudio, VisualStudio: () => mockVisualStudio,
}); });
testUsingContext('Emits missing status when Visual Studio is not installed', () async {
when(visualStudio.isInstalled).thenReturn(false);
const VisualStudioValidator validator = VisualStudioValidator();
final ValidationResult result = await validator.validate();
final ValidationMessage expectedMessage = ValidationMessage.error(userMessages.visualStudioMissing);
expect(result.messages.contains(expectedMessage), true);
expect(result.type, ValidationType.missing);
}, overrides: <Type, Generator>{
VisualStudio: () => mockVisualStudio,
});
}); });
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment