Unverified Commit aecf053e authored by stuartmorgan's avatar stuartmorgan Committed by GitHub

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
parent e1a784ae
......@@ -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(
......
......@@ -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
......
......@@ -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<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);
......@@ -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>()));
......@@ -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 <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());
......@@ -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>{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment