Skip to content

Commit 94b7ff2

Browse files
Don't require a specific Windows 10 SDK (flutter#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 flutter#50487
1 parent 3ec6978 commit 94b7ff2

File tree

5 files changed

+236
-31
lines changed

5 files changed

+236
-31
lines changed

packages/flutter_tools/lib/src/base/user_messages.dart

+9-5
Original file line numberDiff line numberDiff line change
@@ -194,18 +194,22 @@ class UserMessages {
194194
// Messages used in VisualStudioValidator
195195
String visualStudioVersion(String name, String version) => '$name version $version';
196196
String visualStudioLocation(String location) => 'Visual Studio at $location';
197+
String windows10SdkVersion(String version) => 'Windows 10 SDK version $version';
197198
String visualStudioMissingComponents(String workload, List<String> components) =>
198199
'Visual Studio is missing necessary components. Please re-run the '
199200
'Visual Studio installer for the "$workload" workload, and include these components:\n'
200-
' ${components.join('\n ')}';
201-
String visualStudioMissing(String workload, List<String> components) =>
201+
' ${components.join('\n ')}\n'
202+
' Windows 10 SDK';
203+
String get windows10SdkNotFound =>
204+
'Unable to locate a Windows 10 SDK. If building fails, install the Windows 10 SDK in Visual Studio.';
205+
String visualStudioMissing(String workload) =>
202206
'Visual Studio not installed; this is necessary for Windows development.\n'
203207
'Download at https://visualstudio.microsoft.com/downloads/.\n'
204-
'Please install the "$workload" workload, including the following components:\n ${components.join('\n ')}';
205-
String visualStudioTooOld(String minimumVersion, String workload, List<String> components) =>
208+
'Please install the "$workload" workload, including all of its default components';
209+
String visualStudioTooOld(String minimumVersion, String workload) =>
206210
'Visual Studio $minimumVersion or later is required.\n'
207211
'Download at https://visualstudio.microsoft.com/downloads/.\n'
208-
'Please install the "$workload" workload, including the following components:\n ${components.join('\n ')}';
212+
'Please install the "$workload" workload, including all of its default components';
209213
String get visualStudioIsPrerelease => 'The current Visual Studio installation is a pre-release version. It may not be '
210214
'supported by Flutter yet.';
211215
String get visualStudioNotLaunchable =>

packages/flutter_tools/lib/src/windows/visual_studio.dart

+114-14
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import '../base/io.dart';
1010
import '../base/logger.dart';
1111
import '../base/platform.dart';
1212
import '../base/process.dart';
13+
import '../base/version.dart';
1314
import '../convert.dart';
1415

1516
/// Encapsulates information about the installed copy of Visual Studio, if any.
@@ -99,6 +100,37 @@ class VisualStudio {
99100
/// The name of the recommended Visual Studio installer workload.
100101
String get workloadDescription => 'Desktop development with C++';
101102

103+
/// Returns the highest installed Windows 10 SDK version, or null if none is
104+
/// found.
105+
///
106+
/// For instance: 10.0.18362.0
107+
String getWindows10SDKVersion() {
108+
final String sdkLocation = _getWindows10SdkLocation();
109+
if (sdkLocation == null) {
110+
return null;
111+
}
112+
final Directory sdkIncludeDirectory = _fileSystem.directory(sdkLocation).childDirectory('Include');
113+
if (!sdkIncludeDirectory.existsSync()) {
114+
return null;
115+
}
116+
// The directories in this folder are named by the SDK version.
117+
Version highestVersion;
118+
for (final FileSystemEntity versionEntry in sdkIncludeDirectory.listSync()) {
119+
if (versionEntry.basename.startsWith('10.')) {
120+
// Version only handles 3 components; strip off the '10.' to leave three
121+
// components, since they all start with that.
122+
final Version version = Version.parse(versionEntry.basename.substring(3));
123+
if (highestVersion == null || version > highestVersion) {
124+
highestVersion = version;
125+
}
126+
}
127+
}
128+
if (highestVersion == null) {
129+
return null;
130+
}
131+
return '10.$highestVersion';
132+
}
133+
102134
/// The names of the components within the workload that must be installed.
103135
///
104136
/// The descriptions of some components differ from version to version. When
@@ -147,6 +179,11 @@ class VisualStudio {
147179
'vswhere.exe',
148180
);
149181

182+
/// Workload ID for use with vswhere requirements.
183+
///
184+
/// See https://docs.microsoft.com/en-us/visualstudio/install/workload-and-component-ids
185+
static const String _requiredWorkload = 'Microsoft.VisualStudio.Workload.NativeDesktop';
186+
150187
/// Components for use with vswhere requirements.
151188
///
152189
/// Maps from component IDs to description in the installer UI.
@@ -170,14 +207,11 @@ class VisualStudio {
170207
// wrong after each VC++ toolchain update, so just instruct people to install the
171208
// latest version.
172209
cppToolchainDescription += '\n - If there are multiple build tool versions available, install the latest';
210+
// Things which are required by the workload (e.g., MSBuild) don't need to
211+
// be included here.
173212
return <String, String>{
174-
// The MSBuild tool and related command-line toolchain.
175-
'Microsoft.Component.MSBuild': 'MSBuild',
176213
// The C++ toolchain required by the template.
177214
'Microsoft.VisualStudio.Component.VC.Tools.x86.x64': cppToolchainDescription,
178-
// The Windows SDK version used by the template.
179-
'Microsoft.VisualStudio.Component.Windows10SDK.17763':
180-
'Windows 10 SDK (10.0.17763.0)',
181215
};
182216
}
183217

@@ -217,13 +251,27 @@ class VisualStudio {
217251
/// This key is under the 'catalog' entry.
218252
static const String _catalogDisplayVersionKey = 'productDisplayVersion';
219253

220-
/// Returns the details dictionary for the newest version of Visual Studio
221-
/// that includes all of [requiredComponents], if there is one.
222-
Map<String, dynamic> _visualStudioDetails(
223-
{Iterable<String> requiredComponents, List<String> additionalArguments}) {
224-
final List<String> requirementArguments = requiredComponents == null
225-
? <String>[]
226-
: <String>['-requires', ...requiredComponents];
254+
/// The registry path for Windows 10 SDK installation details.
255+
static const String _windows10SdkRegistryPath = r'HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0';
256+
257+
/// The registry key in _windows10SdkRegistryPath for the folder where the
258+
/// SDKs are installed.
259+
static const String _windows10SdkRegistryKey = 'InstallationFolder';
260+
261+
/// Returns the details dictionary for the newest version of Visual Studio.
262+
/// If [validateRequirements] is set, the search will be limited to versions
263+
/// that have all of the required workloads and components.
264+
Map<String, dynamic> _visualStudioDetails({
265+
bool validateRequirements = false,
266+
List<String> additionalArguments,
267+
}) {
268+
final List<String> requirementArguments = validateRequirements
269+
? <String>[
270+
'-requires',
271+
_requiredWorkload,
272+
..._requiredComponents(_minimumSupportedVersion).keys
273+
]
274+
: <String>[];
227275
try {
228276
final List<String> defaultArguments = <String>[
229277
'-format', 'json',
@@ -290,11 +338,11 @@ class VisualStudio {
290338
_minimumSupportedVersion.toString(),
291339
];
292340
Map<String, dynamic> visualStudioDetails = _visualStudioDetails(
293-
requiredComponents: _requiredComponents(_minimumSupportedVersion).keys,
341+
validateRequirements: true,
294342
additionalArguments: minimumVersionArguments);
295343
// If a stable version is not found, try searching for a pre-release version.
296344
visualStudioDetails ??= _visualStudioDetails(
297-
requiredComponents: _requiredComponents(_minimumSupportedVersion).keys,
345+
validateRequirements: true,
298346
additionalArguments: <String>[...minimumVersionArguments, _vswherePrereleaseArgument]);
299347

300348
if (visualStudioDetails != null) {
@@ -336,4 +384,56 @@ class VisualStudio {
336384
}
337385
return _anyVisualStudioDetails;
338386
}
387+
388+
/// Returns the installation location of the Windows 10 SDKs, or null if the
389+
/// registry doesn't contain that information.
390+
String _getWindows10SdkLocation() {
391+
try {
392+
final RunResult result = _processUtils.runSync(<String>[
393+
'reg',
394+
'query',
395+
_windows10SdkRegistryPath,
396+
'/v',
397+
_windows10SdkRegistryKey,
398+
]);
399+
if (result.exitCode == 0) {
400+
final RegExp pattern = RegExp(r'InstallationFolder\s+REG_SZ\s+(.+)');
401+
final RegExpMatch match = pattern.firstMatch(result.stdout);
402+
if (match != null) {
403+
return match.group(1).trim();
404+
}
405+
}
406+
} on ArgumentError {
407+
// Thrown if reg somehow doesn't exist; ignore and return null below.
408+
} on ProcessException {
409+
// Ignored, return null below.
410+
}
411+
return null;
412+
}
413+
414+
/// Returns the highest-numbered SDK version in [dir], which should be the
415+
/// Windows 10 SDK installation directory.
416+
///
417+
/// Returns null if no Windows 10 SDKs are found.
418+
String findHighestVersionInSdkDirectory(Directory dir) {
419+
// This contains subfolders that are named by the SDK version.
420+
final Directory includeDir = dir.childDirectory('Includes');
421+
if (!includeDir.existsSync()) {
422+
return null;
423+
}
424+
Version highestVersion;
425+
for (final FileSystemEntity versionEntry in includeDir.listSync()) {
426+
if (!versionEntry.basename.startsWith('10.')) {
427+
continue;
428+
}
429+
// Version only handles 3 components; strip off the '10.' to leave three
430+
// components, since they all start with that.
431+
final Version version = Version.parse(versionEntry.basename.substring(3));
432+
if (highestVersion == null || version > highestVersion) {
433+
highestVersion = version;
434+
}
435+
}
436+
// Re-add the leading '10.' that was removed for comparison.
437+
return highestVersion == null ? null : '10.$highestVersion';
438+
}
339439
}

packages/flutter_tools/lib/src/windows/visual_studio_validator.dart

+8-2
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,18 @@ class VisualStudioValidator extends DoctorValidator {
4444
messages.add(ValidationMessage(_userMessages.visualStudioIsPrerelease));
4545
}
4646

47+
final String windows10SdkVersion = _visualStudio.getWindows10SDKVersion();
48+
if (windows10SdkVersion != null) {
49+
messages.add(ValidationMessage(_userMessages.windows10SdkVersion(windows10SdkVersion)));
50+
}
51+
4752
// Messages for faulty installations.
4853
if (!_visualStudio.isAtLeastMinimumVersion) {
4954
status = ValidationType.partial;
5055
messages.add(ValidationMessage.error(
5156
_userMessages.visualStudioTooOld(
5257
_visualStudio.minimumVersionDescription,
5358
_visualStudio.workloadDescription,
54-
_visualStudio.necessaryComponentDescriptions(),
5559
),
5660
));
5761
} else if (_visualStudio.isRebootRequired) {
@@ -71,14 +75,16 @@ class VisualStudioValidator extends DoctorValidator {
7175
_visualStudio.necessaryComponentDescriptions(),
7276
),
7377
));
78+
} else if (windows10SdkVersion == null) {
79+
status = ValidationType.partial;
80+
messages.add(ValidationMessage.hint(_userMessages.windows10SdkNotFound));
7481
}
7582
versionInfo = '${_visualStudio.displayName} ${_visualStudio.displayVersion}';
7683
} else {
7784
status = ValidationType.missing;
7885
messages.add(ValidationMessage.error(
7986
_userMessages.visualStudioMissing(
8087
_visualStudio.workloadDescription,
81-
_visualStudio.necessaryComponentDescriptions(),
8288
),
8389
));
8490
}

packages/flutter_tools/test/general.shard/windows/visual_studio_test.dart

+90-8
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ const Map<String, dynamic> _missingStatusResponse = <String, dynamic>{
6666
},
6767
};
6868

69-
// Arguments for a vswhere query to search for an installation with the required components.
70-
const List<String> _requiredComponents = <String>[
71-
'Microsoft.Component.MSBuild',
69+
// Arguments for a vswhere query to search for an installation with the
70+
// requirements.
71+
const List<String> _requirements = <String>[
72+
'Microsoft.VisualStudio.Workload.NativeDesktop',
7273
'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
73-
'Microsoft.VisualStudio.Component.Windows10SDK.17763',
7474
];
7575

7676
// Sets up the mock environment so that searching for Visual Studio with
@@ -116,7 +116,7 @@ void setMockCompatibleVisualStudioInstallation(
116116
setMockVswhereResponse(
117117
fileSystem,
118118
processManager,
119-
_requiredComponents,
119+
_requirements,
120120
<String>['-version', '16'],
121121
response,
122122
);
@@ -132,7 +132,7 @@ void setMockPrereleaseVisualStudioInstallation(
132132
setMockVswhereResponse(
133133
fileSystem,
134134
processManager,
135-
_requiredComponents,
135+
_requirements,
136136
<String>['-version', '16', '-prerelease'],
137137
response,
138138
);
@@ -170,6 +170,50 @@ void setMockEncodedAnyVisualStudioInstallation(
170170
);
171171
}
172172

173+
// Sets up the mock environment for a Windows 10 SDK query.
174+
//
175+
// registryPresent controls whether or not the registry key is found.
176+
// filesPresent controles where or not there are any SDK folders at the location
177+
// returned by the registry query.
178+
void setMockSdkRegResponse(
179+
FileSystem fileSystem,
180+
FakeProcessManager processManager, {
181+
bool registryPresent = true,
182+
bool filesPresent = true,
183+
}) {
184+
const String registryPath = r'HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0';
185+
const String registryKey = r'InstallationFolder';
186+
const String installationPath = r'C:\Program Files (x86)\Windows Kits\10\';
187+
final String stdout = registryPresent
188+
? '''
189+
$registryPath
190+
$registryKey REG_SZ $installationPath
191+
'''
192+
: '''
193+
194+
ERROR: The system was unable to find the specified registry key or value.
195+
''';
196+
197+
if (filesPresent) {
198+
final Directory includeDirectory = fileSystem.directory(installationPath).childDirectory('Include');
199+
includeDirectory.childDirectory('10.0.17763.0').createSync(recursive: true);
200+
includeDirectory.childDirectory('10.0.18362.0').createSync(recursive: true);
201+
// Not an actual version; added to ensure that version comparison is number, not string-based.
202+
includeDirectory.childDirectory('10.0.184.0').createSync(recursive: true);
203+
}
204+
205+
processManager.addCommand(FakeCommand(
206+
command: const <String>[
207+
'reg',
208+
'query',
209+
registryPath,
210+
'/v',
211+
registryKey,
212+
],
213+
stdout: stdout,
214+
));
215+
}
216+
173217
// Create a visual studio instance with a FakeProcessManager.
174218
VisualStudioFixture setUpVisualStudio() {
175219
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[]);
@@ -283,7 +327,7 @@ void main() {
283327
fixture.processManager,
284328
);
285329

286-
final String toolsString = visualStudio.necessaryComponentDescriptions()[1];
330+
final String toolsString = visualStudio.necessaryComponentDescriptions()[0];
287331

288332
expect(toolsString.contains('v142'), true);
289333
});
@@ -308,7 +352,7 @@ void main() {
308352
fixture.processManager,
309353
);
310354

311-
final String toolsString = visualStudio.necessaryComponentDescriptions()[1];
355+
final String toolsString = visualStudio.necessaryComponentDescriptions()[0];
312356

313357
expect(toolsString.contains('v142'), true);
314358
});
@@ -717,6 +761,44 @@ void main() {
717761
expect(visualStudio.displayName, equals('Visual Studio Community 2017'));
718762
expect(visualStudio.displayVersion, equals('15.9.12'));
719763
});
764+
765+
testWithoutContext('SDK version returns the latest version when present', () {
766+
final VisualStudioFixture fixture = setUpVisualStudio();
767+
final VisualStudio visualStudio = fixture.visualStudio;
768+
769+
setMockSdkRegResponse(
770+
fixture.fileSystem,
771+
fixture.processManager,
772+
);
773+
774+
expect(visualStudio.getWindows10SDKVersion(), '10.0.18362.0');
775+
});
776+
777+
testWithoutContext('SDK version returns null when the registry key is not present', () {
778+
final VisualStudioFixture fixture = setUpVisualStudio();
779+
final VisualStudio visualStudio = fixture.visualStudio;
780+
781+
setMockSdkRegResponse(
782+
fixture.fileSystem,
783+
fixture.processManager,
784+
registryPresent: false,
785+
);
786+
787+
expect(visualStudio.getWindows10SDKVersion(), null);
788+
});
789+
790+
testWithoutContext('SDK version returns null when there are no SDK files present', () {
791+
final VisualStudioFixture fixture = setUpVisualStudio();
792+
final VisualStudio visualStudio = fixture.visualStudio;
793+
794+
setMockSdkRegResponse(
795+
fixture.fileSystem,
796+
fixture.processManager,
797+
filesPresent: false,
798+
);
799+
800+
expect(visualStudio.getWindows10SDKVersion(), null);
801+
});
720802
});
721803
}
722804

0 commit comments

Comments
 (0)