Teach flutter msbuild for Windows (#32335)
Eliminates the need for a build.bat in the Windows build workflow, adding
preliminary support for building using msbuild. The handling of
vcvars64.bat may be refined in the future, but this serves as a starting point.
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 2decfcf..8ac323e 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -592,13 +592,19 @@
final FlutterProject project;
- bool existsSync() => project.directory.childDirectory('windows').existsSync();
+ bool existsSync() => _editableDirectory.existsSync();
- // Note: The build script file exists as a temporary shim.
- File get buildScript => project.directory.childDirectory('windows').childFile('build.bat');
+ Directory get _editableDirectory => project.directory.childDirectory('windows');
+
+ /// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for
+ /// the build.
+ File get generatedPropertySheetFile => _editableDirectory.childDirectory('flutter').childFile('Generated.props');
+
+ // The MSBuild project file.
+ File get vcprojFile => _editableDirectory.childFile('Runner.vcxproj');
// Note: The name script file exists as a temporary shim.
- File get nameScript => project.directory.childDirectory('windows').childFile('name_output.bat');
+ File get nameScript => _editableDirectory.childFile('name_output.bat');
}
/// The Linux sub project.
diff --git a/packages/flutter_tools/lib/src/windows/build_windows.dart b/packages/flutter_tools/lib/src/windows/build_windows.dart
index d2aabfe..985f9b7 100644
--- a/packages/flutter_tools/lib/src/windows/build_windows.dart
+++ b/packages/flutter_tools/lib/src/windows/build_windows.dart
@@ -11,14 +11,26 @@
import '../convert.dart';
import '../globals.dart';
import '../project.dart';
+import 'msbuild_utils.dart';
-/// Builds the Windows project through the project bat script.
+/// Builds the Windows project using msbuild.
Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo) async {
+ final Map<String, String> environment = <String, String>{
+ 'FLUTTER_ROOT': Cache.flutterRoot,
+ 'EXTRA_BUNDLE_FLAGS': buildInfo?.trackWidgetCreation == true ? '--track-widget-creation' : '',
+ };
+ writePropertySheet(windowsProject.generatedPropertySheetFile, environment);
+
+ final String vcvarsScript = await findVcvars();
+ if (vcvarsScript == null) {
+ throwToolExit('Unable to build: could not find vcvars64.bat');
+ }
+
+ final String configuration = buildInfo.isDebug ? 'Debug' : 'Release';
final Process process = await processManager.start(<String>[
- windowsProject.buildScript.path,
- Cache.flutterRoot,
- buildInfo.isDebug ? 'debug' : 'release',
- buildInfo?.trackWidgetCreation == true ? 'track-widget-creation' : 'no-track-widget-creation',
+ vcvarsScript, '&&', 'msbuild',
+ windowsProject.vcprojFile.path,
+ '/p:Configuration=$configuration',
], runInShell: true);
final Status status = logger.startProgress(
'Building Windows application...',
diff --git a/packages/flutter_tools/lib/src/windows/msbuild_utils.dart b/packages/flutter_tools/lib/src/windows/msbuild_utils.dart
new file mode 100644
index 0000000..f816d2c
--- /dev/null
+++ b/packages/flutter_tools/lib/src/windows/msbuild_utils.dart
@@ -0,0 +1,103 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file
+
+import 'package:xml/xml.dart' as xml;
+
+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'
+];
+
+/// 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;
+ }
+ }
+ }
+
+ // If it can't be found manually, check the path.
+ final ProcessResult whereResult = await processManager.run(<String>[
+ 'where.exe',
+ vcvarsScriptName,
+ ]);
+ if (whereResult.exitCode == 0) {
+ return whereResult.stdout.trim();
+ }
+
+ return null;
+}
+
+/// Writes a property sheet (.props) file to expose all of the key/value
+/// pairs in [variables] as enivornment variables.
+void writePropertySheet(File propertySheetFile, Map<String, String> variables) {
+ final xml.XmlBuilder builder = xml.XmlBuilder();
+ builder.processing('xml', 'version="1.0" encoding="utf-8"');
+ builder.element('Project', nest: () {
+ builder.attribute('ToolsVersion', '4.0');
+ builder.attribute(
+ 'xmlns', 'http://schemas.microsoft.com/developer/msbuild/2003');
+ builder.element('ImportGroup', nest: () {
+ builder.attribute('Label', 'PropertySheets');
+ });
+ _addUserMacros(builder, variables);
+ builder.element('PropertyGroup');
+ builder.element('ItemDefinitionGroup');
+ _addItemGroup(builder, variables);
+ });
+
+ propertySheetFile.createSync(recursive: true);
+ propertySheetFile.writeAsStringSync(
+ builder.build().toXmlString(pretty: true, indent: ' '));
+}
+
+/// Adds the UserMacros PropertyGroup that defines [variables] to [builder].
+void _addUserMacros(xml.XmlBuilder builder, Map<String, String> variables) {
+ builder.element('PropertyGroup', nest: () {
+ builder.attribute('Label', 'UserMacros');
+ for (final MapEntry<String, String> variable in variables.entries) {
+ builder.element(variable.key, nest: () {
+ builder.text(variable.value);
+ });
+ }
+ });
+}
+
+/// Adds the ItemGroup to expose the given [variables] as environment variables
+/// to [builder].
+void _addItemGroup(xml.XmlBuilder builder, Map<String, String> variables) {
+ builder.element('ItemGroup', nest: () {
+ for (final String name in variables.keys) {
+ builder.element('BuildMacro', nest: () {
+ builder.attribute('Include', name);
+ builder.element('Value', nest: () {
+ builder.text('\$($name)');
+ });
+ builder.element('EnvironmentVariable', nest: () {
+ builder.text('true');
+ });
+ });
+ }
+ });
+}