mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Add 'doctor' support for Windows (#33872)
Moves the logic for finding vcvars64.bat to a new VisualStudio class that encapsulates finding, and providing information about, VisualStudio installations. Adds a validator for it, and runs it for Windows workflows in doctor.
This commit is contained in:
parent
9751f3e537
commit
fb8df82c06
@ -215,6 +215,17 @@ class UserMessages {
|
|||||||
String vsCodeLocation(String location) => 'VS Code at $location';
|
String vsCodeLocation(String location) => 'VS Code at $location';
|
||||||
String vsCodeFlutterExtensionMissing(String url) => 'Flutter extension not installed; install from\n$url';
|
String vsCodeFlutterExtensionMissing(String url) => 'Flutter extension not installed; install from\n$url';
|
||||||
|
|
||||||
|
// Messages used in VisualStudioValidator
|
||||||
|
String visualStudioVersion(String name, String version) => '$name version $version';
|
||||||
|
String visualStudioLocation(String location) => 'Visual Studio at $location';
|
||||||
|
String visualStudioMissingComponents(String workload, List<String> components) =>
|
||||||
|
'Visual Studio is missing necessary components. Please re-run the '
|
||||||
|
'Visual Studio installer for the "$workload" workload, and include these components:\n'
|
||||||
|
' ${components.join('\n ')}';
|
||||||
|
String get visualStudioMissing =>
|
||||||
|
'Visual Studio not installed; this is necessary for Windows development.\n'
|
||||||
|
'Download at https://visualstudio.microsoft.com/downloads/.';
|
||||||
|
|
||||||
// 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.';
|
||||||
String get flutterNoDevelopmentDevice =>
|
String get flutterNoDevelopmentDevice =>
|
||||||
|
@ -46,6 +46,8 @@ import 'version.dart';
|
|||||||
import 'web/chrome.dart';
|
import 'web/chrome.dart';
|
||||||
import 'web/compile.dart';
|
import 'web/compile.dart';
|
||||||
import 'web/workflow.dart';
|
import 'web/workflow.dart';
|
||||||
|
import 'windows/visual_studio.dart';
|
||||||
|
import 'windows/visual_studio_validator.dart';
|
||||||
import 'windows/windows_workflow.dart';
|
import 'windows/windows_workflow.dart';
|
||||||
|
|
||||||
Future<T> runInContext<T>(
|
Future<T> runInContext<T>(
|
||||||
@ -100,6 +102,8 @@ Future<T> runInContext<T>(
|
|||||||
TimeoutConfiguration: () => const TimeoutConfiguration(),
|
TimeoutConfiguration: () => const TimeoutConfiguration(),
|
||||||
Usage: () => Usage(),
|
Usage: () => Usage(),
|
||||||
UserMessages: () => UserMessages(),
|
UserMessages: () => UserMessages(),
|
||||||
|
VisualStudio: () => VisualStudio(),
|
||||||
|
VisualStudioValidator: () => const VisualStudioValidator(),
|
||||||
WebCompiler: () => const WebCompiler(),
|
WebCompiler: () => const WebCompiler(),
|
||||||
WebWorkflow: () => const WebWorkflow(),
|
WebWorkflow: () => const WebWorkflow(),
|
||||||
WindowsWorkflow: () => const WindowsWorkflow(),
|
WindowsWorkflow: () => const WindowsWorkflow(),
|
||||||
|
@ -35,6 +35,7 @@ import 'version.dart';
|
|||||||
import 'vscode/vscode_validator.dart';
|
import 'vscode/vscode_validator.dart';
|
||||||
import 'web/web_validator.dart';
|
import 'web/web_validator.dart';
|
||||||
import 'web/workflow.dart';
|
import 'web/workflow.dart';
|
||||||
|
import 'windows/visual_studio_validator.dart';
|
||||||
import 'windows/windows_workflow.dart';
|
import 'windows/windows_workflow.dart';
|
||||||
|
|
||||||
Doctor get doctor => context.get<Doctor>();
|
Doctor get doctor => context.get<Doctor>();
|
||||||
@ -68,6 +69,9 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider {
|
|||||||
if (iosWorkflow.appliesToHostPlatform)
|
if (iosWorkflow.appliesToHostPlatform)
|
||||||
_validators.add(iosValidator);
|
_validators.add(iosValidator);
|
||||||
|
|
||||||
|
if (windowsWorkflow.appliesToHostPlatform)
|
||||||
|
_validators.add(visualStudioValidator);
|
||||||
|
|
||||||
if (webWorkflow.appliesToHostPlatform)
|
if (webWorkflow.appliesToHostPlatform)
|
||||||
_validators.add(const WebValidator());
|
_validators.add(const WebValidator());
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import '../convert.dart';
|
|||||||
import '../globals.dart';
|
import '../globals.dart';
|
||||||
import '../project.dart';
|
import '../project.dart';
|
||||||
import 'msbuild_utils.dart';
|
import 'msbuild_utils.dart';
|
||||||
|
import 'visual_studio.dart';
|
||||||
|
|
||||||
/// Builds the Windows project using msbuild.
|
/// Builds the Windows project using msbuild.
|
||||||
Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {String target = 'lib/main.dart'}) async {
|
Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {String target = 'lib/main.dart'}) async {
|
||||||
@ -31,9 +32,10 @@ Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {S
|
|||||||
}
|
}
|
||||||
writePropertySheet(windowsProject.generatedPropertySheetFile, environment);
|
writePropertySheet(windowsProject.generatedPropertySheetFile, environment);
|
||||||
|
|
||||||
final String vcvarsScript = await findVcvars();
|
final String vcvarsScript = visualStudio.vcvarsPath;
|
||||||
if (vcvarsScript == null) {
|
if (vcvarsScript == null) {
|
||||||
throwToolExit('Unable to build: could not find suitable toolchain.');
|
throwToolExit('Unable to find suitable Visual Studio toolchain. '
|
||||||
|
'Please run `flutter doctor` for more details.');
|
||||||
}
|
}
|
||||||
|
|
||||||
final String buildScript = fs.path.join(
|
final String buildScript = fs.path.join(
|
||||||
|
@ -5,74 +5,6 @@
|
|||||||
import 'package:xml/xml.dart' as xml;
|
import 'package:xml/xml.dart' as xml;
|
||||||
|
|
||||||
import '../base/file_system.dart';
|
import '../base/file_system.dart';
|
||||||
import '../base/io.dart';
|
|
||||||
import '../base/platform.dart';
|
|
||||||
import '../base/process_manager.dart';
|
|
||||||
import '../globals.dart';
|
|
||||||
|
|
||||||
/// Returns the path to an installed vcvars64.bat script if found, or null.
|
|
||||||
Future<String> findVcvars() async {
|
|
||||||
final String vswherePath = fs.path.join(
|
|
||||||
platform.environment['PROGRAMFILES(X86)'],
|
|
||||||
'Microsoft Visual Studio',
|
|
||||||
'Installer',
|
|
||||||
'vswhere.exe',
|
|
||||||
);
|
|
||||||
// The "Desktop development with C++" workload. This is a coarse check, since
|
|
||||||
// it doesn't validate that the specific pieces are available, but should be
|
|
||||||
// a reasonable first-pass approximation.
|
|
||||||
// In the future, a set of more targetted checks will be used to provide
|
|
||||||
// clear validation feedback (e.g., VS is installed, but missing component X).
|
|
||||||
const String requiredComponent = 'Microsoft.VisualStudio.Workload.NativeDesktop';
|
|
||||||
|
|
||||||
const String visualStudioInstallMessage =
|
|
||||||
'Ensure that you have Visual Studio 2017 or later installed, including '
|
|
||||||
'the "Desktop development with C++" workload.';
|
|
||||||
|
|
||||||
if (!fs.file(vswherePath).existsSync()) {
|
|
||||||
printError(
|
|
||||||
'Unable to locate Visual Studio: vswhere.exe not found\n'
|
|
||||||
'$visualStudioInstallMessage',
|
|
||||||
emphasis: true,
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final ProcessResult whereResult = await processManager.run(<String>[
|
|
||||||
vswherePath,
|
|
||||||
'-latest',
|
|
||||||
'-requires', requiredComponent,
|
|
||||||
'-property', 'installationPath',
|
|
||||||
]);
|
|
||||||
if (whereResult.exitCode != 0) {
|
|
||||||
printError(
|
|
||||||
'Unable to locate Visual Studio:\n'
|
|
||||||
'${whereResult.stdout}\n'
|
|
||||||
'$visualStudioInstallMessage',
|
|
||||||
emphasis: true,
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final String visualStudioPath = whereResult.stdout.trim();
|
|
||||||
if (visualStudioPath.isEmpty) {
|
|
||||||
printError(
|
|
||||||
'No suitable Visual Studio found. $visualStudioInstallMessage\n',
|
|
||||||
emphasis: true,
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final String vcvarsPath =
|
|
||||||
fs.path.join(visualStudioPath, 'VC', 'Auxiliary', 'Build', 'vcvars64.bat');
|
|
||||||
if (!fs.file(vcvarsPath).existsSync()) {
|
|
||||||
printError(
|
|
||||||
'vcvars64.bat does not exist at $vcvarsPath.\n',
|
|
||||||
emphasis: true,
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return vcvarsPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes a property sheet (.props) file to expose all of the key/value
|
/// Writes a property sheet (.props) file to expose all of the key/value
|
||||||
/// pairs in [variables] as enivornment variables.
|
/// pairs in [variables] as enivornment variables.
|
||||||
|
186
packages/flutter_tools/lib/src/windows/visual_studio.dart
Normal file
186
packages/flutter_tools/lib/src/windows/visual_studio.dart
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import '../base/context.dart';
|
||||||
|
import '../base/file_system.dart';
|
||||||
|
import '../base/io.dart';
|
||||||
|
import '../base/platform.dart';
|
||||||
|
import '../base/process_manager.dart';
|
||||||
|
import '../convert.dart';
|
||||||
|
|
||||||
|
VisualStudio get visualStudio => context.get<VisualStudio>();
|
||||||
|
|
||||||
|
/// Encapsulates information about the installed copy of Visual Studio, if any.
|
||||||
|
class VisualStudio {
|
||||||
|
/// True if a sufficiently recent version of Visual Studio is installed.
|
||||||
|
///
|
||||||
|
/// Versions older than 2017 Update 2 won't be detected, so error messages to
|
||||||
|
/// users should take into account that [false] may mean that the user may
|
||||||
|
/// have an old version rather than no installation at all.
|
||||||
|
bool get isInstalled => _bestVisualStudioDetails != null;
|
||||||
|
|
||||||
|
/// True if there is a version of Visual Studio with all the components
|
||||||
|
/// necessary to build the project.
|
||||||
|
bool get hasNecessaryComponents => _usableVisualStudioDetails != null;
|
||||||
|
|
||||||
|
/// The name of the Visual Studio install.
|
||||||
|
///
|
||||||
|
/// For instance: "Visual Studio Community 2017".
|
||||||
|
String get displayName => _bestVisualStudioDetails[_displayNameKey];
|
||||||
|
|
||||||
|
/// The user-friendly version number of the Visual Studio install.
|
||||||
|
///
|
||||||
|
/// For instance: "15.4.0".
|
||||||
|
String get displayVersion =>
|
||||||
|
_bestVisualStudioDetails[_catalogKey][_catalogDisplayVersionKey];
|
||||||
|
|
||||||
|
/// The directory where Visual Studio is installed.
|
||||||
|
String get installLocation => _bestVisualStudioDetails[_installationPathKey];
|
||||||
|
|
||||||
|
/// The full version of the Visual Studio install.
|
||||||
|
///
|
||||||
|
/// For instance: "15.4.27004.2002".
|
||||||
|
String get fullVersion => _bestVisualStudioDetails[_fullVersionKey];
|
||||||
|
|
||||||
|
/// The name of the recommended Visual Studio installer workload.
|
||||||
|
String get workloadDescription => 'Desktop development with C++';
|
||||||
|
|
||||||
|
/// The names of the components within the workload that must be installed.
|
||||||
|
///
|
||||||
|
/// If there is an existing Visual Studio installation, the major version
|
||||||
|
/// should be provided here, as the descriptions of some componets differ
|
||||||
|
/// from version to version.
|
||||||
|
List<String> necessaryComponentDescriptions([int visualStudioMajorVersion]) {
|
||||||
|
return _requiredComponents(visualStudioMajorVersion).values.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The path to vcvars64.bat, or null if no Visual Studio installation has
|
||||||
|
/// the components necessary to build.
|
||||||
|
String get vcvarsPath {
|
||||||
|
final Map<String, dynamic> details = _usableVisualStudioDetails;
|
||||||
|
if (details == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return fs.path.join(
|
||||||
|
_usableVisualStudioDetails[_installationPathKey],
|
||||||
|
'VC',
|
||||||
|
'Auxiliary',
|
||||||
|
'Build',
|
||||||
|
'vcvars64.bat',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The path to vswhere.exe.
|
||||||
|
///
|
||||||
|
/// vswhere should be installed for VS 2017 Update 2 and later; if it's not
|
||||||
|
/// present then there isn't a new enough installation of VS. This path is
|
||||||
|
/// not user-controllable, unlike the install location of Visual Studio
|
||||||
|
/// itself.
|
||||||
|
final String _vswherePath = fs.path.join(
|
||||||
|
platform.environment['PROGRAMFILES(X86)'],
|
||||||
|
'Microsoft Visual Studio',
|
||||||
|
'Installer',
|
||||||
|
'vswhere.exe',
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Components for use with vswhere requriements.
|
||||||
|
///
|
||||||
|
/// Maps from component IDs to description in the installer UI.
|
||||||
|
/// See https://docs.microsoft.com/en-us/visualstudio/install/workload-and-component-ids
|
||||||
|
Map<String, String> _requiredComponents([int visualStudioMajorVersion]) {
|
||||||
|
// The description of the C++ toolchain required by the template. The
|
||||||
|
// component name is significantly different in different versions.
|
||||||
|
// Default to the latest known description, but use a specific string
|
||||||
|
// if a known older version is requested.
|
||||||
|
String cppToolchainDescription = 'MSVC v142 - VS 2019 C++ x64/x86 build tools (v14.21)';
|
||||||
|
if (visualStudioMajorVersion == 15) {
|
||||||
|
cppToolchainDescription = 'VC++ 2017 version 15.9 v14.16 latest v141 tools';
|
||||||
|
}
|
||||||
|
|
||||||
|
return <String, String>{
|
||||||
|
// The MSBuild tool and related command-line toolchain.
|
||||||
|
'Microsoft.Component.MSBuild': 'MSBuild',
|
||||||
|
// The C++ toolchain required by the template.
|
||||||
|
'Microsoft.VisualStudio.Component.VC.Tools.x86.x64': cppToolchainDescription,
|
||||||
|
// The Windows SDK version used by the template.
|
||||||
|
'Microsoft.VisualStudio.Component.Windows10SDK.17763':
|
||||||
|
'Windows 10 SDK (10.0.17763.0)',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys in a VS details dictionary returned from vswhere.
|
||||||
|
|
||||||
|
/// The root directory of the Visual Studio installation.
|
||||||
|
static const String _installationPathKey = 'installationPath';
|
||||||
|
|
||||||
|
/// The user-friendly name of the installation.
|
||||||
|
static const String _displayNameKey = 'displayName';
|
||||||
|
|
||||||
|
/// The complete version.
|
||||||
|
static const String _fullVersionKey = 'installationVersion';
|
||||||
|
|
||||||
|
/// The 'catalog' entry containing more details.
|
||||||
|
static const String _catalogKey = 'catalog';
|
||||||
|
|
||||||
|
/// The user-friendly version.
|
||||||
|
///
|
||||||
|
/// This key is under the 'catalog' entry.
|
||||||
|
static const String _catalogDisplayVersionKey = 'productDisplayVersion';
|
||||||
|
|
||||||
|
/// 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}) {
|
||||||
|
final List<String> requirementArguments = requiredComponents == null
|
||||||
|
? <String>[]
|
||||||
|
: <String>['-requires', ...requiredComponents];
|
||||||
|
try {
|
||||||
|
final ProcessResult whereResult = processManager.runSync(<String>[
|
||||||
|
_vswherePath,
|
||||||
|
'-format', 'json',
|
||||||
|
'-utf8',
|
||||||
|
'-latest',
|
||||||
|
...?requirementArguments,
|
||||||
|
]);
|
||||||
|
if (whereResult.exitCode == 0) {
|
||||||
|
final List<Map<String, dynamic>> installations = json.decode(whereResult.stdout)
|
||||||
|
.cast<Map<String, dynamic>>();
|
||||||
|
if (installations.isNotEmpty) {
|
||||||
|
return installations[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} on ArgumentError {
|
||||||
|
// Thrown if vswhere doesnt' exist; ignore and return null below.
|
||||||
|
} on ProcessException {
|
||||||
|
// Ignored, return null below.
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
return _cachedUsableVisualStudioDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the details dictionary of the latest version of Visual Studio,
|
||||||
|
/// regardless of components.
|
||||||
|
Map<String, dynamic> _cachedAnyVisualStudioDetails;
|
||||||
|
Map<String, dynamic> get _anyVisualStudioDetails {
|
||||||
|
_cachedAnyVisualStudioDetails ??= _visualStudioDetails();
|
||||||
|
return _cachedAnyVisualStudioDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the details dictionary of the best available version of Visual
|
||||||
|
/// Studio. If there's a version that has all the required components, that
|
||||||
|
/// will be returned, otherwise returs the lastest installed version (if any).
|
||||||
|
Map<String, dynamic> get _bestVisualStudioDetails {
|
||||||
|
if (_usableVisualStudioDetails != null) {
|
||||||
|
return _usableVisualStudioDetails;
|
||||||
|
}
|
||||||
|
return _anyVisualStudioDetails;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import '../base/context.dart';
|
||||||
|
import '../base/user_messages.dart';
|
||||||
|
import '../doctor.dart';
|
||||||
|
import 'visual_studio.dart';
|
||||||
|
|
||||||
|
VisualStudioValidator get visualStudioValidator => context.get<VisualStudioValidator>();
|
||||||
|
|
||||||
|
class VisualStudioValidator extends DoctorValidator {
|
||||||
|
const VisualStudioValidator() : super('Visual Studio - develop for Windows');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<ValidationResult> validate() async {
|
||||||
|
final List<ValidationMessage> messages = <ValidationMessage>[];
|
||||||
|
ValidationType status = ValidationType.missing;
|
||||||
|
String versionInfo;
|
||||||
|
|
||||||
|
if (visualStudio.isInstalled) {
|
||||||
|
status = ValidationType.installed;
|
||||||
|
|
||||||
|
messages.add(ValidationMessage(
|
||||||
|
userMessages.visualStudioLocation(visualStudio.installLocation)
|
||||||
|
));
|
||||||
|
|
||||||
|
messages.add(ValidationMessage(userMessages.visualStudioVersion(
|
||||||
|
visualStudio.displayName,
|
||||||
|
visualStudio.fullVersion,
|
||||||
|
)));
|
||||||
|
|
||||||
|
if (!visualStudio.hasNecessaryComponents) {
|
||||||
|
status = ValidationType.partial;
|
||||||
|
final int majorVersion = int.tryParse(visualStudio.fullVersion.split('.')[0]);
|
||||||
|
messages.add(ValidationMessage.error(
|
||||||
|
userMessages.visualStudioMissingComponents(
|
||||||
|
visualStudio.workloadDescription,
|
||||||
|
visualStudio.necessaryComponentDescriptions(majorVersion)
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
versionInfo = '${visualStudio.displayName} ${visualStudio.displayVersion}';
|
||||||
|
} else {
|
||||||
|
status = ValidationType.missing;
|
||||||
|
messages.add(ValidationMessage.error(userMessages.visualStudioMissing));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValidationResult(status, messages, statusInfo: versionInfo);
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ import 'package:flutter_tools/src/base/io.dart';
|
|||||||
import 'package:flutter_tools/src/base/platform.dart';
|
import 'package:flutter_tools/src/base/platform.dart';
|
||||||
import 'package:flutter_tools/src/cache.dart';
|
import 'package:flutter_tools/src/cache.dart';
|
||||||
import 'package:flutter_tools/src/commands/build.dart';
|
import 'package:flutter_tools/src/commands/build.dart';
|
||||||
|
import 'package:flutter_tools/src/windows/visual_studio.dart';
|
||||||
import 'package:mockito/mockito.dart';
|
import 'package:mockito/mockito.dart';
|
||||||
import 'package:process/process.dart';
|
import 'package:process/process.dart';
|
||||||
import 'package:xml/xml.dart' as xml;
|
import 'package:xml/xml.dart' as xml;
|
||||||
@ -25,6 +26,7 @@ void main() {
|
|||||||
final MockPlatform windowsPlatform = MockPlatform()
|
final MockPlatform windowsPlatform = MockPlatform()
|
||||||
..environment['PROGRAMFILES(X86)'] = r'C:\Program Files (x86)\';
|
..environment['PROGRAMFILES(X86)'] = r'C:\Program Files (x86)\';
|
||||||
final MockPlatform notWindowsPlatform = MockPlatform();
|
final MockPlatform notWindowsPlatform = MockPlatform();
|
||||||
|
final MockVisualStudio mockVisualStudio = MockVisualStudio();
|
||||||
const String solutionPath = r'C:\windows\Runner.sln';
|
const String solutionPath = r'C:\windows\Runner.sln';
|
||||||
const String visualStudioPath = r'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community';
|
const String visualStudioPath = r'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community';
|
||||||
const String vcvarsPath = visualStudioPath + r'\VC\Auxiliary\Build\vcvars64.bat';
|
const String vcvarsPath = visualStudioPath + r'\VC\Auxiliary\Build\vcvars64.bat';
|
||||||
@ -41,25 +43,6 @@ void main() {
|
|||||||
when(windowsPlatform.isWindows).thenReturn(true);
|
when(windowsPlatform.isWindows).thenReturn(true);
|
||||||
when(notWindowsPlatform.isWindows).thenReturn(false);
|
when(notWindowsPlatform.isWindows).thenReturn(false);
|
||||||
|
|
||||||
// Sets up the mock environment so that lookup of vcvars64.bat will succeed.
|
|
||||||
void enableVcvarsMocking() {
|
|
||||||
const String vswherePath = r'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe';
|
|
||||||
fs.file(vswherePath).createSync(recursive: true);
|
|
||||||
fs.file(vcvarsPath).createSync(recursive: true);
|
|
||||||
|
|
||||||
final MockProcessResult result = MockProcessResult();
|
|
||||||
when(result.exitCode).thenReturn(0);
|
|
||||||
when<String>(result.stdout).thenReturn(visualStudioPath);
|
|
||||||
when(mockProcessManager.run(<String>[
|
|
||||||
vswherePath,
|
|
||||||
'-latest',
|
|
||||||
'-requires', 'Microsoft.VisualStudio.Workload.NativeDesktop',
|
|
||||||
'-property', 'installationPath',
|
|
||||||
])).thenAnswer((Invocation invocation) async {
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
testUsingContext('Windows build fails when there is no vcvars64.bat', () async {
|
testUsingContext('Windows build fails when there is no vcvars64.bat', () async {
|
||||||
final BuildCommand command = BuildCommand();
|
final BuildCommand command = BuildCommand();
|
||||||
applyMocksToCommand(command);
|
applyMocksToCommand(command);
|
||||||
@ -70,25 +53,27 @@ void main() {
|
|||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
Platform: () => windowsPlatform,
|
Platform: () => windowsPlatform,
|
||||||
FileSystem: () => memoryFilesystem,
|
FileSystem: () => memoryFilesystem,
|
||||||
|
VisualStudio: () => mockVisualStudio,
|
||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('Windows build fails when there is no windows project', () async {
|
testUsingContext('Windows build fails when there is no windows project', () async {
|
||||||
final BuildCommand command = BuildCommand();
|
final BuildCommand command = BuildCommand();
|
||||||
applyMocksToCommand(command);
|
applyMocksToCommand(command);
|
||||||
enableVcvarsMocking();
|
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
|
||||||
expect(createTestCommandRunner(command).run(
|
expect(createTestCommandRunner(command).run(
|
||||||
const <String>['build', 'windows']
|
const <String>['build', 'windows']
|
||||||
), throwsA(isInstanceOf<ToolExit>()));
|
), throwsA(isInstanceOf<ToolExit>()));
|
||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
Platform: () => windowsPlatform,
|
Platform: () => windowsPlatform,
|
||||||
FileSystem: () => memoryFilesystem,
|
FileSystem: () => memoryFilesystem,
|
||||||
|
VisualStudio: () => mockVisualStudio,
|
||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('Windows build fails on non windows platform', () async {
|
testUsingContext('Windows build fails on non windows platform', () async {
|
||||||
final BuildCommand command = BuildCommand();
|
final BuildCommand command = BuildCommand();
|
||||||
applyMocksToCommand(command);
|
applyMocksToCommand(command);
|
||||||
fs.file(solutionPath).createSync(recursive: true);
|
fs.file(solutionPath).createSync(recursive: true);
|
||||||
enableVcvarsMocking();
|
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
|
||||||
fs.file('pubspec.yaml').createSync();
|
fs.file('pubspec.yaml').createSync();
|
||||||
fs.file('.packages').createSync();
|
fs.file('.packages').createSync();
|
||||||
|
|
||||||
@ -98,13 +83,14 @@ void main() {
|
|||||||
}, overrides: <Type, Generator>{
|
}, overrides: <Type, Generator>{
|
||||||
Platform: () => notWindowsPlatform,
|
Platform: () => notWindowsPlatform,
|
||||||
FileSystem: () => memoryFilesystem,
|
FileSystem: () => memoryFilesystem,
|
||||||
|
VisualStudio: () => mockVisualStudio,
|
||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('Windows build invokes msbuild and writes generated files', () async {
|
testUsingContext('Windows build invokes msbuild and writes generated files', () async {
|
||||||
final BuildCommand command = BuildCommand();
|
final BuildCommand command = BuildCommand();
|
||||||
applyMocksToCommand(command);
|
applyMocksToCommand(command);
|
||||||
fs.file(solutionPath).createSync(recursive: true);
|
fs.file(solutionPath).createSync(recursive: true);
|
||||||
enableVcvarsMocking();
|
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
|
||||||
fs.file('pubspec.yaml').createSync();
|
fs.file('pubspec.yaml').createSync();
|
||||||
fs.file('.packages').createSync();
|
fs.file('.packages').createSync();
|
||||||
|
|
||||||
@ -132,15 +118,16 @@ void main() {
|
|||||||
FileSystem: () => memoryFilesystem,
|
FileSystem: () => memoryFilesystem,
|
||||||
ProcessManager: () => mockProcessManager,
|
ProcessManager: () => mockProcessManager,
|
||||||
Platform: () => windowsPlatform,
|
Platform: () => windowsPlatform,
|
||||||
|
VisualStudio: () => mockVisualStudio,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockProcessManager extends Mock implements ProcessManager {}
|
class MockProcessManager extends Mock implements ProcessManager {}
|
||||||
class MockProcess extends Mock implements Process {}
|
class MockProcess extends Mock implements Process {}
|
||||||
class MockProcessResult extends Mock implements ProcessResult {}
|
|
||||||
class MockPlatform extends Mock implements Platform {
|
class MockPlatform extends Mock implements Platform {
|
||||||
@override
|
@override
|
||||||
Map<String, String> environment = <String, String>{
|
Map<String, String> environment = <String, String>{
|
||||||
'FLUTTER_ROOT': r'C:\',
|
'FLUTTER_ROOT': r'C:\',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
class MockVisualStudio extends Mock implements VisualStudio {}
|
||||||
|
236
packages/flutter_tools/test/windows/visual_studio_test.dart
Normal file
236
packages/flutter_tools/test/windows/visual_studio_test.dart
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:file/memory.dart';
|
||||||
|
import 'package:flutter_tools/src/base/file_system.dart';
|
||||||
|
import 'package:flutter_tools/src/base/io.dart' show ProcessException, ProcessResult;
|
||||||
|
import 'package:flutter_tools/src/base/platform.dart';
|
||||||
|
import 'package:flutter_tools/src/convert.dart';
|
||||||
|
import 'package:flutter_tools/src/windows/visual_studio.dart';
|
||||||
|
import 'package:mockito/mockito.dart';
|
||||||
|
import 'package:process/process.dart';
|
||||||
|
|
||||||
|
import '../src/common.dart';
|
||||||
|
import '../src/context.dart';
|
||||||
|
|
||||||
|
class MockPlatform extends Mock implements Platform {
|
||||||
|
@override
|
||||||
|
Map<String, String> environment = <String, String>{};
|
||||||
|
}
|
||||||
|
class MockProcessManager extends Mock implements ProcessManager {}
|
||||||
|
class MockProcessResult extends Mock implements ProcessResult {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
const String programFilesPath = r'C:\Program Files (x86)';
|
||||||
|
const String visualStudioPath = programFilesPath + r'\Microsoft Visual Studio\2017\Community';
|
||||||
|
const String vcvarsPath = visualStudioPath + r'\VC\Auxiliary\Build\vcvars64.bat';
|
||||||
|
const String vswherePath = programFilesPath + r'\Microsoft Visual Studio\Installer\vswhere.exe';
|
||||||
|
|
||||||
|
final MockPlatform windowsPlatform = MockPlatform()
|
||||||
|
..environment['PROGRAMFILES(X86)'] = r'C:\Program Files (x86)\';
|
||||||
|
MockProcessManager mockProcessManager;
|
||||||
|
final MemoryFileSystem memoryFilesystem = MemoryFileSystem(style: FileSystemStyle.windows);
|
||||||
|
|
||||||
|
// Sets up the mock environment so that searching for Visual Studio with
|
||||||
|
// exactly the given required components will provide a result. By default it
|
||||||
|
// return a preset installation, but the response can be overridden.
|
||||||
|
void setMockVswhereResponse([List<String> requiredComponents, String response]) {
|
||||||
|
fs.file(vswherePath).createSync(recursive: true);
|
||||||
|
fs.file(vcvarsPath).createSync(recursive: true);
|
||||||
|
|
||||||
|
final MockProcessResult result = MockProcessResult();
|
||||||
|
when(result.exitCode).thenReturn(0);
|
||||||
|
when<String>(result.stdout).thenReturn(response == null
|
||||||
|
? json.encode(<Map<String, dynamic>>[
|
||||||
|
<String, dynamic>{
|
||||||
|
'installationPath': visualStudioPath,
|
||||||
|
'displayName': 'Visual Studio Community 2017',
|
||||||
|
'installationVersion': '15.9.28307.665',
|
||||||
|
'catalog': <String, String>{
|
||||||
|
'productDisplayVersion': '15.9.12',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
])
|
||||||
|
: response);
|
||||||
|
|
||||||
|
final List<String> requirementArguments = requiredComponents == null
|
||||||
|
? <String>[]
|
||||||
|
: <String>['-requires', ...requiredComponents];
|
||||||
|
when(mockProcessManager.runSync(<String>[
|
||||||
|
vswherePath,
|
||||||
|
'-format', 'json',
|
||||||
|
'-utf8',
|
||||||
|
'-latest',
|
||||||
|
...?requirementArguments,
|
||||||
|
])).thenAnswer((Invocation invocation) {
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets whether or not a vswhere query without components will return an
|
||||||
|
// installation.
|
||||||
|
void setMockIncompleteVisualStudioExists(bool exists) {
|
||||||
|
setMockVswhereResponse(null, exists ? null : '[]');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets whether or not a vswhere query with the required components will
|
||||||
|
// return an installation.
|
||||||
|
void setMockCompatibleVisualStudioExists(bool exists) {
|
||||||
|
setMockVswhereResponse(<String>[
|
||||||
|
'Microsoft.Component.MSBuild',
|
||||||
|
'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
|
||||||
|
'Microsoft.VisualStudio.Component.Windows10SDK.17763',
|
||||||
|
], exists ? null : '[]');
|
||||||
|
}
|
||||||
|
|
||||||
|
group('Visual Studio', () {
|
||||||
|
VisualStudio visualStudio;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
mockProcessManager = MockProcessManager();
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('isInstalled returns false when vswhere is missing', () {
|
||||||
|
when(mockProcessManager.runSync(any))
|
||||||
|
.thenThrow(const ProcessException('vswhere', <String>[]));
|
||||||
|
|
||||||
|
visualStudio = VisualStudio();
|
||||||
|
expect(visualStudio.isInstalled, false);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => memoryFilesystem,
|
||||||
|
Platform: () => windowsPlatform,
|
||||||
|
ProcessManager: () => mockProcessManager,
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('vcvarsPath returns null when vswhere is missing', () {
|
||||||
|
when(mockProcessManager.runSync(any))
|
||||||
|
.thenThrow(const ProcessException('vswhere', <String>[]));
|
||||||
|
|
||||||
|
visualStudio = VisualStudio();
|
||||||
|
expect(visualStudio.vcvarsPath, isNull);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => memoryFilesystem,
|
||||||
|
Platform: () => windowsPlatform,
|
||||||
|
ProcessManager: () => mockProcessManager,
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('isInstalled returns false when vswhere returns non-zero', () {
|
||||||
|
when(mockProcessManager.runSync(any))
|
||||||
|
.thenThrow(const ProcessException('vswhere', <String>[]));
|
||||||
|
final MockProcessResult result = MockProcessResult();
|
||||||
|
when(result.exitCode).thenReturn(1);
|
||||||
|
when(mockProcessManager.runSync(any)).thenAnswer((Invocation invocation) {
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
visualStudio = VisualStudio();
|
||||||
|
expect(visualStudio.isInstalled, false);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => memoryFilesystem,
|
||||||
|
Platform: () => windowsPlatform,
|
||||||
|
ProcessManager: () => mockProcessManager,
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('isInstalled returns true when VS is present but missing components', () {
|
||||||
|
setMockIncompleteVisualStudioExists(true);
|
||||||
|
setMockCompatibleVisualStudioExists(false);
|
||||||
|
|
||||||
|
visualStudio = VisualStudio();
|
||||||
|
expect(visualStudio.isInstalled, true);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => memoryFilesystem,
|
||||||
|
Platform: () => windowsPlatform,
|
||||||
|
ProcessManager: () => mockProcessManager,
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('hasNecessaryComponents returns false when VS is present but missing components', () {
|
||||||
|
setMockIncompleteVisualStudioExists(true);
|
||||||
|
setMockCompatibleVisualStudioExists(false);
|
||||||
|
|
||||||
|
visualStudio = VisualStudio();
|
||||||
|
expect(visualStudio.hasNecessaryComponents, false);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => memoryFilesystem,
|
||||||
|
Platform: () => windowsPlatform,
|
||||||
|
ProcessManager: () => mockProcessManager,
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('vcvarsPath returns null when VS is present but missing components', () {
|
||||||
|
setMockIncompleteVisualStudioExists(true);
|
||||||
|
setMockCompatibleVisualStudioExists(false);
|
||||||
|
|
||||||
|
visualStudio = VisualStudio();
|
||||||
|
expect(visualStudio.vcvarsPath, isNull);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => memoryFilesystem,
|
||||||
|
Platform: () => windowsPlatform,
|
||||||
|
ProcessManager: () => mockProcessManager,
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('VS metadata is available when VS is present, even if missing components', () {
|
||||||
|
setMockIncompleteVisualStudioExists(true);
|
||||||
|
setMockCompatibleVisualStudioExists(false);
|
||||||
|
|
||||||
|
visualStudio = VisualStudio();
|
||||||
|
expect(visualStudio.displayName, equals('Visual Studio Community 2017'));
|
||||||
|
expect(visualStudio.displayVersion, equals('15.9.12'));
|
||||||
|
expect(visualStudio.installLocation, equals(visualStudioPath));
|
||||||
|
expect(visualStudio.fullVersion, equals('15.9.28307.665'));
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => memoryFilesystem,
|
||||||
|
Platform: () => windowsPlatform,
|
||||||
|
ProcessManager: () => mockProcessManager,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
testUsingContext('isInstalled returns true when VS is present but missing components', () {
|
||||||
|
setMockIncompleteVisualStudioExists(true);
|
||||||
|
setMockCompatibleVisualStudioExists(false);
|
||||||
|
|
||||||
|
visualStudio = VisualStudio();
|
||||||
|
expect(visualStudio.isInstalled, true);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => memoryFilesystem,
|
||||||
|
Platform: () => windowsPlatform,
|
||||||
|
ProcessManager: () => mockProcessManager,
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('Everything returns good values when VS is present with all components', () {
|
||||||
|
setMockCompatibleVisualStudioExists(true);
|
||||||
|
|
||||||
|
visualStudio = VisualStudio();
|
||||||
|
expect(visualStudio.isInstalled, true);
|
||||||
|
expect(visualStudio.hasNecessaryComponents, true);
|
||||||
|
expect(visualStudio.vcvarsPath, equals(vcvarsPath));
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => memoryFilesystem,
|
||||||
|
Platform: () => windowsPlatform,
|
||||||
|
ProcessManager: () => mockProcessManager,
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('Metadata is for compatible version when latest is missing components', () {
|
||||||
|
setMockCompatibleVisualStudioExists(true);
|
||||||
|
// Return a different version for queries without the required packages.
|
||||||
|
final String incompleteVersionResponse = json.encode(<Map<String, dynamic>>[
|
||||||
|
<String, dynamic>{
|
||||||
|
'installationPath': visualStudioPath,
|
||||||
|
'displayName': 'Visual Studio Community 2019',
|
||||||
|
'installationVersion': '16.1.1.1',
|
||||||
|
'catalog': <String, String>{
|
||||||
|
'productDisplayVersion': '16.1',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
setMockVswhereResponse(null, incompleteVersionResponse);
|
||||||
|
|
||||||
|
visualStudio = VisualStudio();
|
||||||
|
expect(visualStudio.displayName, equals('Visual Studio Community 2017'));
|
||||||
|
expect(visualStudio.displayVersion, equals('15.9.12'));
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => memoryFilesystem,
|
||||||
|
Platform: () => windowsPlatform,
|
||||||
|
ProcessManager: () => mockProcessManager,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||||
|
// 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/doctor.dart';
|
||||||
|
import 'package:flutter_tools/src/windows/visual_studio.dart';
|
||||||
|
import 'package:flutter_tools/src/windows/visual_studio_validator.dart';
|
||||||
|
import 'package:mockito/mockito.dart';
|
||||||
|
|
||||||
|
import '../src/common.dart';
|
||||||
|
import '../src/context.dart';
|
||||||
|
|
||||||
|
class MockVisualStudio extends Mock implements VisualStudio {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('Visual Studio validation', () {
|
||||||
|
MockVisualStudio mockVisualStudio;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
mockVisualStudio = 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();
|
||||||
|
expect(result.type, ValidationType.missing);
|
||||||
|
}, 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']);
|
||||||
|
when(visualStudio.fullVersion).thenReturn('15.1');
|
||||||
|
const VisualStudioValidator validator = VisualStudioValidator();
|
||||||
|
final ValidationResult result = await validator.validate();
|
||||||
|
expect(result.type, ValidationType.partial);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
VisualStudio: () => mockVisualStudio,
|
||||||
|
});
|
||||||
|
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user