Skip to content

Commit

Permalink
Use vswhere to find Visual Studio (flutter#33448)
Browse files Browse the repository at this point in the history
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 flutter#33249
  • Loading branch information
stuartmorgan authored May 29, 2019
1 parent e1a784a commit aecf053
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 37 deletions.
2 changes: 1 addition & 1 deletion packages/flutter_tools/lib/src/windows/build_windows.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Future<void> 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(
Expand Down
85 changes: 55 additions & 30 deletions packages/flutter_tools/lib/src/windows/msbuild_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> _visualStudioVersions = <String>['2017', '2019'];

/// The supported flavors of Visual Studio.
const List<String> _visualStudioFlavors = <String>[
'Community',
'Professional',
'Enterprise',
'Preview'
];
import '../globals.dart';

/// Returns the path to an installed vcvars64.bat script if found, or null.
Future<String> 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(<String>[
'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
Expand Down
32 changes: 26 additions & 6 deletions packages/flutter_tools/test/commands/build_windows_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<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 {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
Expand All @@ -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 <String>['build', 'windows']
), throwsA(isInstanceOf<ToolExit>()));
Expand All @@ -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();

Expand All @@ -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();

Expand All @@ -102,7 +121,7 @@ void main() {
const <String>['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());
Expand All @@ -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<String, String> environment = <String, String>{
Expand Down

0 comments on commit aecf053

Please sign in to comment.