Don't require a specific Windows 10 SDK (#58713)

Current versions of the Windows desktop build files don't require a specific Windows 10 SDK version, but doctor still checks for one since vswhere doesn't allow for flexible queries. This has been a common source of issues for people setting up on Windows for the first time, because the current VS installer by default only includes a newer version of the SDK than what doctor is looking for.

This removes the vswhere SDK check, and instead uses a manual check for SDKs. Since this uses undocumented (although fairly widely used, so relatively unlikely to change) registry information, the check is non-fatal, so that builds can progress even if the SDK isn't found by doctor; in practice, it's very unlikely that someone would install the C++ Windows development workload but remove the selected-by-default SDK from the install.

Now that all requirements are default, the instructions when missing VS have been simplified so that they no longer list individual components, and instead just say to include default items.

Fixes #50487
This commit is contained in:
stuartmorgan 2020-06-04 18:53:00 -07:00 committed by GitHub
parent 3ec6978c9c
commit 94b7ff241e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 236 additions and 31 deletions

View file

@ -194,18 +194,22 @@ class UserMessages {
// Messages used in VisualStudioValidator
String visualStudioVersion(String name, String version) => '$name version $version';
String visualStudioLocation(String location) => 'Visual Studio at $location';
String windows10SdkVersion(String version) => 'Windows 10 SDK version $version';
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 visualStudioMissing(String workload, List<String> components) =>
' ${components.join('\n ')}\n'
' Windows 10 SDK';
String get windows10SdkNotFound =>
'Unable to locate a Windows 10 SDK. If building fails, install the Windows 10 SDK in Visual Studio.';
String visualStudioMissing(String workload) =>
'Visual Studio not installed; this is necessary for Windows development.\n'
'Download at https://visualstudio.microsoft.com/downloads/.\n'
'Please install the "$workload" workload, including the following components:\n ${components.join('\n ')}';
String visualStudioTooOld(String minimumVersion, String workload, List<String> components) =>
'Please install the "$workload" workload, including all of its default components';
String visualStudioTooOld(String minimumVersion, String workload) =>
'Visual Studio $minimumVersion or later is required.\n'
'Download at https://visualstudio.microsoft.com/downloads/.\n'
'Please install the "$workload" workload, including the following components:\n ${components.join('\n ')}';
'Please install the "$workload" workload, including all of its default components';
String get visualStudioIsPrerelease => 'The current Visual Studio installation is a pre-release version. It may not be '
'supported by Flutter yet.';
String get visualStudioNotLaunchable =>

View file

@ -10,6 +10,7 @@ import '../base/io.dart';
import '../base/logger.dart';
import '../base/platform.dart';
import '../base/process.dart';
import '../base/version.dart';
import '../convert.dart';
/// Encapsulates information about the installed copy of Visual Studio, if any.
@ -99,6 +100,37 @@ class VisualStudio {
/// The name of the recommended Visual Studio installer workload.
String get workloadDescription => 'Desktop development with C++';
/// Returns the highest installed Windows 10 SDK version, or null if none is
/// found.
///
/// For instance: 10.0.18362.0
String getWindows10SDKVersion() {
final String sdkLocation = _getWindows10SdkLocation();
if (sdkLocation == null) {
return null;
}
final Directory sdkIncludeDirectory = _fileSystem.directory(sdkLocation).childDirectory('Include');
if (!sdkIncludeDirectory.existsSync()) {
return null;
}
// The directories in this folder are named by the SDK version.
Version highestVersion;
for (final FileSystemEntity versionEntry in sdkIncludeDirectory.listSync()) {
if (versionEntry.basename.startsWith('10.')) {
// Version only handles 3 components; strip off the '10.' to leave three
// components, since they all start with that.
final Version version = Version.parse(versionEntry.basename.substring(3));
if (highestVersion == null || version > highestVersion) {
highestVersion = version;
}
}
}
if (highestVersion == null) {
return null;
}
return '10.$highestVersion';
}
/// The names of the components within the workload that must be installed.
///
/// The descriptions of some components differ from version to version. When
@ -147,6 +179,11 @@ class VisualStudio {
'vswhere.exe',
);
/// Workload ID for use with vswhere requirements.
///
/// See https://docs.microsoft.com/en-us/visualstudio/install/workload-and-component-ids
static const String _requiredWorkload = 'Microsoft.VisualStudio.Workload.NativeDesktop';
/// Components for use with vswhere requirements.
///
/// Maps from component IDs to description in the installer UI.
@ -170,14 +207,11 @@ class VisualStudio {
// wrong after each VC++ toolchain update, so just instruct people to install the
// latest version.
cppToolchainDescription += '\n - If there are multiple build tool versions available, install the latest';
// Things which are required by the workload (e.g., MSBuild) don't need to
// be included here.
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)',
};
}
@ -217,13 +251,27 @@ class VisualStudio {
/// 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, List<String> additionalArguments}) {
final List<String> requirementArguments = requiredComponents == null
? <String>[]
: <String>['-requires', ...requiredComponents];
/// The registry path for Windows 10 SDK installation details.
static const String _windows10SdkRegistryPath = r'HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0';
/// The registry key in _windows10SdkRegistryPath for the folder where the
/// SDKs are installed.
static const String _windows10SdkRegistryKey = 'InstallationFolder';
/// Returns the details dictionary for the newest version of Visual Studio.
/// If [validateRequirements] is set, the search will be limited to versions
/// that have all of the required workloads and components.
Map<String, dynamic> _visualStudioDetails({
bool validateRequirements = false,
List<String> additionalArguments,
}) {
final List<String> requirementArguments = validateRequirements
? <String>[
'-requires',
_requiredWorkload,
..._requiredComponents(_minimumSupportedVersion).keys
]
: <String>[];
try {
final List<String> defaultArguments = <String>[
'-format', 'json',
@ -290,11 +338,11 @@ class VisualStudio {
_minimumSupportedVersion.toString(),
];
Map<String, dynamic> visualStudioDetails = _visualStudioDetails(
requiredComponents: _requiredComponents(_minimumSupportedVersion).keys,
validateRequirements: true,
additionalArguments: minimumVersionArguments);
// If a stable version is not found, try searching for a pre-release version.
visualStudioDetails ??= _visualStudioDetails(
requiredComponents: _requiredComponents(_minimumSupportedVersion).keys,
validateRequirements: true,
additionalArguments: <String>[...minimumVersionArguments, _vswherePrereleaseArgument]);
if (visualStudioDetails != null) {
@ -336,4 +384,56 @@ class VisualStudio {
}
return _anyVisualStudioDetails;
}
/// Returns the installation location of the Windows 10 SDKs, or null if the
/// registry doesn't contain that information.
String _getWindows10SdkLocation() {
try {
final RunResult result = _processUtils.runSync(<String>[
'reg',
'query',
_windows10SdkRegistryPath,
'/v',
_windows10SdkRegistryKey,
]);
if (result.exitCode == 0) {
final RegExp pattern = RegExp(r'InstallationFolder\s+REG_SZ\s+(.+)');
final RegExpMatch match = pattern.firstMatch(result.stdout);
if (match != null) {
return match.group(1).trim();
}
}
} on ArgumentError {
// Thrown if reg somehow doesn't exist; ignore and return null below.
} on ProcessException {
// Ignored, return null below.
}
return null;
}
/// Returns the highest-numbered SDK version in [dir], which should be the
/// Windows 10 SDK installation directory.
///
/// Returns null if no Windows 10 SDKs are found.
String findHighestVersionInSdkDirectory(Directory dir) {
// This contains subfolders that are named by the SDK version.
final Directory includeDir = dir.childDirectory('Includes');
if (!includeDir.existsSync()) {
return null;
}
Version highestVersion;
for (final FileSystemEntity versionEntry in includeDir.listSync()) {
if (!versionEntry.basename.startsWith('10.')) {
continue;
}
// Version only handles 3 components; strip off the '10.' to leave three
// components, since they all start with that.
final Version version = Version.parse(versionEntry.basename.substring(3));
if (highestVersion == null || version > highestVersion) {
highestVersion = version;
}
}
// Re-add the leading '10.' that was removed for comparison.
return highestVersion == null ? null : '10.$highestVersion';
}
}

View file

@ -44,6 +44,11 @@ class VisualStudioValidator extends DoctorValidator {
messages.add(ValidationMessage(_userMessages.visualStudioIsPrerelease));
}
final String windows10SdkVersion = _visualStudio.getWindows10SDKVersion();
if (windows10SdkVersion != null) {
messages.add(ValidationMessage(_userMessages.windows10SdkVersion(windows10SdkVersion)));
}
// Messages for faulty installations.
if (!_visualStudio.isAtLeastMinimumVersion) {
status = ValidationType.partial;
@ -51,7 +56,6 @@ class VisualStudioValidator extends DoctorValidator {
_userMessages.visualStudioTooOld(
_visualStudio.minimumVersionDescription,
_visualStudio.workloadDescription,
_visualStudio.necessaryComponentDescriptions(),
),
));
} else if (_visualStudio.isRebootRequired) {
@ -71,6 +75,9 @@ class VisualStudioValidator extends DoctorValidator {
_visualStudio.necessaryComponentDescriptions(),
),
));
} else if (windows10SdkVersion == null) {
status = ValidationType.partial;
messages.add(ValidationMessage.hint(_userMessages.windows10SdkNotFound));
}
versionInfo = '${_visualStudio.displayName} ${_visualStudio.displayVersion}';
} else {
@ -78,7 +85,6 @@ class VisualStudioValidator extends DoctorValidator {
messages.add(ValidationMessage.error(
_userMessages.visualStudioMissing(
_visualStudio.workloadDescription,
_visualStudio.necessaryComponentDescriptions(),
),
));
}

View file

@ -66,11 +66,11 @@ const Map<String, dynamic> _missingStatusResponse = <String, dynamic>{
},
};
// Arguments for a vswhere query to search for an installation with the required components.
const List<String> _requiredComponents = <String>[
'Microsoft.Component.MSBuild',
// Arguments for a vswhere query to search for an installation with the
// requirements.
const List<String> _requirements = <String>[
'Microsoft.VisualStudio.Workload.NativeDesktop',
'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
'Microsoft.VisualStudio.Component.Windows10SDK.17763',
];
// Sets up the mock environment so that searching for Visual Studio with
@ -116,7 +116,7 @@ void setMockCompatibleVisualStudioInstallation(
setMockVswhereResponse(
fileSystem,
processManager,
_requiredComponents,
_requirements,
<String>['-version', '16'],
response,
);
@ -132,7 +132,7 @@ void setMockPrereleaseVisualStudioInstallation(
setMockVswhereResponse(
fileSystem,
processManager,
_requiredComponents,
_requirements,
<String>['-version', '16', '-prerelease'],
response,
);
@ -170,6 +170,50 @@ void setMockEncodedAnyVisualStudioInstallation(
);
}
// Sets up the mock environment for a Windows 10 SDK query.
//
// registryPresent controls whether or not the registry key is found.
// filesPresent controles where or not there are any SDK folders at the location
// returned by the registry query.
void setMockSdkRegResponse(
FileSystem fileSystem,
FakeProcessManager processManager, {
bool registryPresent = true,
bool filesPresent = true,
}) {
const String registryPath = r'HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0';
const String registryKey = r'InstallationFolder';
const String installationPath = r'C:\Program Files (x86)\Windows Kits\10\';
final String stdout = registryPresent
? '''
$registryPath
$registryKey REG_SZ $installationPath
'''
: '''
ERROR: The system was unable to find the specified registry key or value.
''';
if (filesPresent) {
final Directory includeDirectory = fileSystem.directory(installationPath).childDirectory('Include');
includeDirectory.childDirectory('10.0.17763.0').createSync(recursive: true);
includeDirectory.childDirectory('10.0.18362.0').createSync(recursive: true);
// Not an actual version; added to ensure that version comparison is number, not string-based.
includeDirectory.childDirectory('10.0.184.0').createSync(recursive: true);
}
processManager.addCommand(FakeCommand(
command: const <String>[
'reg',
'query',
registryPath,
'/v',
registryKey,
],
stdout: stdout,
));
}
// Create a visual studio instance with a FakeProcessManager.
VisualStudioFixture setUpVisualStudio() {
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[]);
@ -283,7 +327,7 @@ void main() {
fixture.processManager,
);
final String toolsString = visualStudio.necessaryComponentDescriptions()[1];
final String toolsString = visualStudio.necessaryComponentDescriptions()[0];
expect(toolsString.contains('v142'), true);
});
@ -308,7 +352,7 @@ void main() {
fixture.processManager,
);
final String toolsString = visualStudio.necessaryComponentDescriptions()[1];
final String toolsString = visualStudio.necessaryComponentDescriptions()[0];
expect(toolsString.contains('v142'), true);
});
@ -717,6 +761,44 @@ void main() {
expect(visualStudio.displayName, equals('Visual Studio Community 2017'));
expect(visualStudio.displayVersion, equals('15.9.12'));
});
testWithoutContext('SDK version returns the latest version when present', () {
final VisualStudioFixture fixture = setUpVisualStudio();
final VisualStudio visualStudio = fixture.visualStudio;
setMockSdkRegResponse(
fixture.fileSystem,
fixture.processManager,
);
expect(visualStudio.getWindows10SDKVersion(), '10.0.18362.0');
});
testWithoutContext('SDK version returns null when the registry key is not present', () {
final VisualStudioFixture fixture = setUpVisualStudio();
final VisualStudio visualStudio = fixture.visualStudio;
setMockSdkRegResponse(
fixture.fileSystem,
fixture.processManager,
registryPresent: false,
);
expect(visualStudio.getWindows10SDKVersion(), null);
});
testWithoutContext('SDK version returns null when there are no SDK files present', () {
final VisualStudioFixture fixture = setUpVisualStudio();
final VisualStudio visualStudio = fixture.visualStudio;
setMockSdkRegResponse(
fixture.fileSystem,
fixture.processManager,
filesPresent: false,
);
expect(visualStudio.getWindows10SDKVersion(), null);
});
});
}

View file

@ -35,6 +35,7 @@ void main() {
when(mockVisualStudio.hasNecessaryComponents).thenReturn(true);
when(mockVisualStudio.fullVersion).thenReturn('16.2');
when(mockVisualStudio.displayName).thenReturn('Visual Studio Community 2019');
when(mockVisualStudio.getWindows10SDKVersion()).thenReturn('10.0.18362.0');
}
// Assigns default values for a complete VS installation that is too old.
@ -48,6 +49,7 @@ void main() {
when(mockVisualStudio.hasNecessaryComponents).thenReturn(true);
when(mockVisualStudio.fullVersion).thenReturn('15.1');
when(mockVisualStudio.displayName).thenReturn('Visual Studio Community 2017');
when(mockVisualStudio.getWindows10SDKVersion()).thenReturn('10.0.17763.0');
}
// Assigns default values for a missing VS installation.
@ -59,6 +61,7 @@ void main() {
when(mockVisualStudio.isLaunchable).thenReturn(false);
when(mockVisualStudio.isRebootRequired).thenReturn(false);
when(mockVisualStudio.hasNecessaryComponents).thenReturn(false);
when(mockVisualStudio.getWindows10SDKVersion()).thenReturn(null);
}
testWithoutContext('Emits a message when Visual Studio is a pre-release version', () async {
@ -132,7 +135,6 @@ void main() {
userMessages.visualStudioTooOld(
mockVisualStudio.minimumVersionDescription,
mockVisualStudio.workloadDescription,
mockVisualStudio.necessaryComponentDescriptions(),
),
);
@ -152,6 +154,18 @@ void main() {
expect(result.type, ValidationType.partial);
});
testWithoutContext('Emits partial status when Visual Studio is installed but the SDK cannot be found', () async {
final VisualStudioValidator validator = VisualStudioValidator(
userMessages: userMessages,
visualStudio: mockVisualStudio,
);
_configureMockVisualStudioAsInstalled();
when(mockVisualStudio.getWindows10SDKVersion()).thenReturn(null);
final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.partial);
});
testWithoutContext('Emits installed status when Visual Studio is installed with necessary components', () async {
final VisualStudioValidator validator = VisualStudioValidator(
userMessages: userMessages,
@ -178,7 +192,6 @@ void main() {
final ValidationMessage expectedMessage = ValidationMessage.error(
userMessages.visualStudioMissing(
mockVisualStudio.workloadDescription,
mockVisualStudio.necessaryComponentDescriptions(),
),
);