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 {
String get visualStudioMissing =>
'Visual Studio not installed; this is necessary for Windows development.\n'
'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
String flutterElapsedTime(String name, String elapsedTime) => '"flutter $name" took $elapsedTime.';
......
......@@ -35,6 +35,10 @@ class VisualStudio {
String get displayVersion =>
_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.
String get installLocation => _bestVisualStudioDetails[_installationPathKey];
......@@ -43,6 +47,15 @@ class VisualStudio {
/// For instance: "15.4.27004.2002".
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.
String get workloadDescription => 'Desktop development with C++';
......@@ -120,6 +133,11 @@ class VisualStudio {
/// The complete version.
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.
static const String _catalogKey = 'catalog';
......@@ -128,23 +146,36 @@ class VisualStudio {
/// This key is under the 'catalog' entry.
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
/// 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
? <String>[]
: <String>['-requires', ...requiredComponents];
try {
final ProcessResult whereResult = processManager.runSync(<String>[
_vswherePath,
final List<String> defaultArguments = <String>[
'-format', 'json',
'-utf8',
'-latest',
];
final ProcessResult whereResult = processManager.runSync(<String>[
_vswherePath,
...defaultArguments,
...?additionalArguments,
...?requirementArguments,
]);
if (whereResult.exitCode == 0) {
final List<Map<String, dynamic>> installations = json.decode(whereResult.stdout)
.cast<Map<String, dynamic>>();
final List<Map<String, dynamic>> installations =
json.decode(whereResult.stdout).cast<Map<String, dynamic>>();
if (installations.isNotEmpty) {
return installations[0];
}
......@@ -157,12 +188,34 @@ class VisualStudio {
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
/// that has all required components.
Map<String, dynamic> _cachedUsableVisualStudioDetails;
Map<String, dynamic> get _usableVisualStudioDetails {
_cachedUsableVisualStudioDetails ??=
_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;
}
......@@ -170,7 +223,9 @@ class VisualStudio {
/// regardless of components.
Map<String, dynamic> _cachedAnyVisualStudioDetails;
Map<String, dynamic> get _anyVisualStudioDetails {
_cachedAnyVisualStudioDetails ??= _visualStudioDetails();
// Search for all types of installations.
_cachedAnyVisualStudioDetails ??= _visualStudioDetails(
additionalArguments: <String>[_prereleaseKey, '-all']);
return _cachedAnyVisualStudioDetails;
}
......
......@@ -30,7 +30,21 @@ class VisualStudioValidator extends DoctorValidator {
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;
final int majorVersion = int.tryParse(visualStudio.fullVersion.split('.')[0]);
messages.add(ValidationMessage.error(
......
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// 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/windows/visual_studio.dart';
import 'package:flutter_tools/src/windows/visual_studio_validator.dart';
......@@ -18,6 +19,24 @@ void main() {
setUp(() {
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 {
......@@ -29,8 +48,44 @@ void main() {
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 {
when(visualStudio.isInstalled).thenReturn(true);
when(visualStudio.hasNecessaryComponents).thenReturn(false);
when(visualStudio.workloadDescription).thenReturn('Desktop development');
when(visualStudio.necessaryComponentDescriptions(any)).thenReturn(<String>['A', 'B']);
......@@ -43,13 +98,22 @@ void main() {
});
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();
final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.installed);
}, overrides: <Type, Generator>{
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