From aecf053ee02cdf45f4ed1c957f9eee312db84c67 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Wed, 29 May 2019 09:17:17 -0700 Subject: [PATCH] Use vswhere to find Visual Studio (#33448) Rather than hard-coding a set of locations to check, use vswhere (which is installed by VS 2017 and later), and construct the vcvars64.bat path relative to that. This will allow Windows builds to work without special configuration for people who have VS installed at a custom path. Also adds error logging with different messages for each failure point, so that rather than the not-very-informative 'failed to find vcvars64.bat' message, the failure will provide feedback about what to do. This is an interim solution; later this will be replaced by a VisualStudio class with associated validator to match the structure of the other toolchains. Fixes #33249 --- .../lib/src/windows/build_windows.dart | 2 +- .../lib/src/windows/msbuild_utils.dart | 85 ++++++++++++------- .../test/commands/build_windows_test.dart | 32 +++++-- 3 files changed, 82 insertions(+), 37 deletions(-) diff --git a/packages/flutter_tools/lib/src/windows/build_windows.dart b/packages/flutter_tools/lib/src/windows/build_windows.dart index 986b5950fc1..c31708b15d7 100644 --- a/packages/flutter_tools/lib/src/windows/build_windows.dart +++ b/packages/flutter_tools/lib/src/windows/build_windows.dart @@ -26,7 +26,7 @@ Future buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {S final String vcvarsScript = await findVcvars(); if (vcvarsScript == null) { - throwToolExit('Unable to build: could not find vcvars64.bat'); + throwToolExit('Unable to build: could not find suitable toolchain.'); } final String buildScript = fs.path.join( diff --git a/packages/flutter_tools/lib/src/windows/msbuild_utils.dart b/packages/flutter_tools/lib/src/windows/msbuild_utils.dart index f816d2cbd70..5a5d173521e 100644 --- a/packages/flutter_tools/lib/src/windows/msbuild_utils.dart +++ b/packages/flutter_tools/lib/src/windows/msbuild_utils.dart @@ -8,45 +8,70 @@ import '../base/file_system.dart'; import '../base/io.dart'; import '../base/platform.dart'; import '../base/process_manager.dart'; - -/// The supported versions of Visual Studio. -const List _visualStudioVersions = ['2017', '2019']; - -/// The supported flavors of Visual Studio. -const List _visualStudioFlavors = [ - 'Community', - 'Professional', - 'Enterprise', - 'Preview' -]; +import '../globals.dart'; /// Returns the path to an installed vcvars64.bat script if found, or null. Future findVcvars() async { - final String programDir = platform.environment['PROGRAMFILES(X86)']; - final String pathPrefix = fs.path.join(programDir, 'Microsoft Visual Studio'); - const String vcvarsScriptName = 'vcvars64.bat'; - final String pathSuffix = - fs.path.join('VC', 'Auxiliary', 'Build', vcvarsScriptName); - for (final String version in _visualStudioVersions) { - for (final String flavor in _visualStudioFlavors) { - final String testPath = - fs.path.join(pathPrefix, version, flavor, pathSuffix); - if (fs.file(testPath).existsSync()) { - return testPath; - } - } + 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; } - // If it can't be found manually, check the path. final ProcessResult whereResult = await processManager.run([ - 'where.exe', - vcvarsScriptName, + vswherePath, + '-latest', + '-requires', requiredComponent, + '-property', 'installationPath', ]); - if (whereResult.exitCode == 0) { - return whereResult.stdout.trim(); + 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 null; + return vcvarsPath; } /// Writes a property sheet (.props) file to expose all of the key/value diff --git a/packages/flutter_tools/test/commands/build_windows_test.dart b/packages/flutter_tools/test/commands/build_windows_test.dart index 5733135e21c..1fde6dc4bd7 100644 --- a/packages/flutter_tools/test/commands/build_windows_test.dart +++ b/packages/flutter_tools/test/commands/build_windows_test.dart @@ -26,8 +26,8 @@ void main() { ..environment['PROGRAMFILES(X86)'] = r'C:\Program Files (x86)\'; final MockPlatform notWindowsPlatform = MockPlatform(); const String projectPath = r'C:\windows\Runner.vcxproj'; - // A vcvars64.bat location that will be found by the lookup method. - const String vcvarsPath = r'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat'; + const String visualStudioPath = r'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community'; + const String vcvarsPath = visualStudioPath + r'\VC\Auxiliary\Build\vcvars64.bat'; when(mockProcess.exitCode).thenAnswer((Invocation invocation) async { return 0; @@ -41,6 +41,25 @@ void main() { when(windowsPlatform.isWindows).thenReturn(true); 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(result.stdout).thenReturn(visualStudioPath); + when(mockProcessManager.run([ + 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 { final BuildCommand command = BuildCommand(); applyMocksToCommand(command); @@ -56,7 +75,7 @@ void main() { testUsingContext('Windows build fails when there is no windows project', () async { final BuildCommand command = BuildCommand(); applyMocksToCommand(command); - fs.file(vcvarsPath).createSync(recursive: true); + enableVcvarsMocking(); expect(createTestCommandRunner(command).run( const ['build', 'windows'] ), throwsA(isInstanceOf())); @@ -69,7 +88,7 @@ void main() { final BuildCommand command = BuildCommand(); applyMocksToCommand(command); fs.file(projectPath).createSync(recursive: true); - fs.file(vcvarsPath).createSync(recursive: true); + enableVcvarsMocking(); fs.file('pubspec.yaml').createSync(); fs.file('.packages').createSync(); @@ -85,7 +104,7 @@ void main() { final BuildCommand command = BuildCommand(); applyMocksToCommand(command); fs.file(projectPath).createSync(recursive: true); - fs.file(vcvarsPath).createSync(recursive: true); + enableVcvarsMocking(); fs.file('pubspec.yaml').createSync(); fs.file('.packages').createSync(); @@ -102,7 +121,7 @@ void main() { const ['build', 'windows'] ); - // Spot-check important elemenst from the properties file. + // Spot-check important elements from the properties file. final File propsFile = fs.file(r'C:\windows\flutter\Generated.props'); expect(propsFile.existsSync(), true); final xml.XmlDocument props = xml.parse(propsFile.readAsStringSync()); @@ -118,6 +137,7 @@ void main() { class MockProcessManager extends Mock implements ProcessManager {} class MockProcess extends Mock implements Process {} +class MockProcessResult extends Mock implements ProcessResult {} class MockPlatform extends Mock implements Platform { @override Map environment = {