Switch Windows to CMake (#60629)

* First pass at CMake files; untested

* First pass of adding CMake generation logic on Windows

* Misc fixes

* Get bundling working, start incoprorating CMake build into tool

* Fix debug, exe name.

* Add resources

* Move cmake.dart

* Rip out all the vcxproj/solution plumbing

* Fix plugin cmake generation

* Build with cmake rather than calling VS directly

* Adjust Windows plugin template to match standard header directory structure

* Pass config selection when building

* Partially fix multi-config handling

* Rev template version

* Share the CMake generation instead of splitting it out

* VS build/run cycle works, with slightly awkward requirement to always build all

* Update manifest

* Plugin template fixes

* Minor adjustments

* Build install as part of build command, instead of separately

* Test cleanup

* Update Linux test for adjusted generated CMake approach

* Plugin test typo fix

* Add missing stub file for project test

* Add a constant for VS generator
diff --git a/packages/flutter_tools/lib/src/linux/cmake.dart b/packages/flutter_tools/lib/src/cmake.dart
similarity index 63%
rename from packages/flutter_tools/lib/src/linux/cmake.dart
rename to packages/flutter_tools/lib/src/cmake.dart
index 8a50066..e57b974 100644
--- a/packages/flutter_tools/lib/src/linux/cmake.dart
+++ b/packages/flutter_tools/lib/src/cmake.dart
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import '../project.dart';
+import 'project.dart';
 
-/// Extracts the `BINARY_NAME` from a Linux project CMake file.
+/// Extracts the `BINARY_NAME` from a project's CMake file.
 ///
 /// Returns `null` if it cannot be found.
-String getCmakeExecutableName(LinuxProject project) {
+String getCmakeExecutableName(CmakeBasedProject project) {
   if (!project.cmakeFile.existsSync()) {
     return null;
   }
@@ -21,24 +21,30 @@
   return null;
 }
 
+String _escapeBackslashes(String s) {
+  return s.replaceAll(r'\', r'\\');
+}
+
 /// Writes a generated CMake configuration file for [project], including
 /// variables expected by the build template and an environment variable list
 /// for calling back into Flutter.
-void writeGeneratedCmakeConfig(String flutterRoot, LinuxProject project, Map<String, String> environment) {
+void writeGeneratedCmakeConfig(String flutterRoot, CmakeBasedProject project, Map<String, String> environment) {
   // Only a limited set of variables are needed by the CMake files themselves,
   // the rest are put into a list to pass to the re-entrant build step.
+  final String escapedFlutterRoot = _escapeBackslashes(flutterRoot);
+  final String escapedProjectDir = _escapeBackslashes(project.parent.directory.path);
   final StringBuffer buffer = StringBuffer('''
 # Generated code do not commit.
-set(FLUTTER_ROOT "$flutterRoot")
-set(PROJECT_DIR "${project.project.directory.path}")
+file(TO_CMAKE_PATH "$escapedFlutterRoot" FLUTTER_ROOT)
+file(TO_CMAKE_PATH "$escapedProjectDir" PROJECT_DIR)
 
 # Environment variables to pass to tool_backend.sh
 list(APPEND FLUTTER_TOOL_ENVIRONMENT
-  "FLUTTER_ROOT=\\"\${FLUTTER_ROOT}\\""
-  "PROJECT_DIR=\\"\${PROJECT_DIR}\\""
+  "FLUTTER_ROOT=\\"$escapedFlutterRoot\\""
+  "PROJECT_DIR=\\"$escapedProjectDir\\""
 ''');
   for (final String key in environment.keys) {
-    final String value = environment[key];
+    final String value = _escapeBackslashes(environment[key]);
     buffer.writeln('  "$key=\\"$value\\""');
   }
   buffer.writeln(')');
diff --git a/packages/flutter_tools/lib/src/linux/application_package.dart b/packages/flutter_tools/lib/src/linux/application_package.dart
index d1a579f..acc80f2 100644
--- a/packages/flutter_tools/lib/src/linux/application_package.dart
+++ b/packages/flutter_tools/lib/src/linux/application_package.dart
@@ -7,9 +7,9 @@
 import '../application_package.dart';
 import '../base/file_system.dart';
 import '../build_info.dart';
+import '../cmake.dart';
 import '../globals.dart' as globals;
 import '../project.dart';
-import 'cmake.dart';
 
 abstract class LinuxApp extends ApplicationPackage {
   LinuxApp({@required String projectBundleId}) : super(id: projectBundleId);
@@ -52,7 +52,7 @@
 }
 
 class BuildableLinuxApp extends LinuxApp {
-  BuildableLinuxApp({this.project}) : super(projectBundleId: project.project.manifest.appName);
+  BuildableLinuxApp({this.project}) : super(projectBundleId: project.parent.manifest.appName);
 
   final LinuxProject project;
 
@@ -68,5 +68,5 @@
   }
 
   @override
-  String get name => project.project.manifest.appName;
+  String get name => project.parent.manifest.appName;
 }
diff --git a/packages/flutter_tools/lib/src/linux/build_linux.dart b/packages/flutter_tools/lib/src/linux/build_linux.dart
index 2ff8690..fbbbf9e 100644
--- a/packages/flutter_tools/lib/src/linux/build_linux.dart
+++ b/packages/flutter_tools/lib/src/linux/build_linux.dart
@@ -10,10 +10,10 @@
 import '../base/utils.dart';
 import '../build_info.dart';
 import '../cache.dart';
+import '../cmake.dart';
 import '../globals.dart' as globals;
 import '../plugins.dart';
 import '../project.dart';
-import 'cmake.dart';
 
 /// Builds the Linux project through the Makefile.
 Future<void> buildLinux(
@@ -39,7 +39,7 @@
   }
   writeGeneratedCmakeConfig(Cache.flutterRoot, linuxProject, environmentConfig);
 
-  createPluginSymlinks(linuxProject.project);
+  createPluginSymlinks(linuxProject.parent);
 
   final Status status = globals.logger.startProgress(
     'Building Linux application...',
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart
index 97e3406..d8b32d9 100644
--- a/packages/flutter_tools/lib/src/plugins.dart
+++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -6,6 +6,7 @@
 
 import 'package:meta/meta.dart';
 import 'package:package_config/package_config.dart';
+import 'package:path/path.dart' as path; // ignore: package_path_import
 import 'package:yaml/yaml.dart';
 
 import 'android/gradle.dart';
@@ -17,8 +18,6 @@
 import 'globals.dart' as globals;
 import 'platform_plugins.dart';
 import 'project.dart';
-import 'windows/property_sheet.dart';
-import 'windows/visual_studio_solution_utils.dart';
 
 void _renderTemplateToFile(String template, dynamic context, String filePath) {
   final String renderedTemplate = globals.templateRenderer
@@ -801,7 +800,7 @@
 #include "generated_plugin_registrant.h"
 
 {{#plugins}}
-#include <{{filename}}.h>
+#include <{{name}}/{{filename}}.h>
 {{/plugins}}
 
 void RegisterPlugins(flutter::PluginRegistry* registry) {
@@ -848,7 +847,7 @@
 }
 ''';
 
-const String _linuxPluginCmakefileTemplate = r'''
+const String _pluginCmakefileTemplate = r'''
 #
 # Generated file, do not edit.
 #
@@ -862,7 +861,7 @@
 set(PLUGIN_BUNDLED_LIBRARIES)
 
 foreach(plugin ${FLUTTER_PLUGIN_LIST})
-  add_subdirectory({{pluginsDir}}/${plugin}/linux plugins/${plugin})
+  add_subdirectory({{pluginsDir}}/${plugin}/{{os}} plugins/${plugin})
   target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
   list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
   list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
@@ -917,6 +916,7 @@
   // that file's directory.
   final String makefileDirPath = project.linux.cmakeFile.parent.absolute.path;
   final Map<String, dynamic> context = <String, dynamic>{
+    'os': 'linux',
     'plugins': linuxPlugins,
     'pluginsDir': globals.fs.path.relative(
       project.linux.pluginSymlinkDirectory.absolute.path,
@@ -924,7 +924,7 @@
     ),
   };
   await _writeLinuxPluginRegistrant(project.linux.managedDirectory, context);
-  await _writeLinuxPluginCmakefile(project.linux.generatedPluginCmakeFile, context);
+  await _writePluginCmakefile(project.linux.generatedPluginCmakeFile, context);
 }
 
 Future<void> _writeLinuxPluginRegistrant(Directory destination, Map<String, dynamic> templateContext) async {
@@ -941,9 +941,9 @@
   );
 }
 
-Future<void> _writeLinuxPluginCmakefile(File destinationFile, Map<String, dynamic> templateContext) async {
+Future<void> _writePluginCmakefile(File destinationFile, Map<String, dynamic> templateContext) async {
   _renderTemplateToFile(
-    _linuxPluginCmakefileTemplate,
+    _pluginCmakefileTemplate,
     templateContext,
     destinationFile.path,
   );
@@ -984,11 +984,22 @@
 Future<void> _writeWindowsPluginFiles(FlutterProject project, List<Plugin> plugins) async {
   final List<Plugin>nativePlugins = _filterNativePlugins(plugins, WindowsPlugin.kConfigKey);
   final List<Map<String, dynamic>> windowsPlugins = _extractPlatformMaps(nativePlugins, WindowsPlugin.kConfigKey);
+  // The generated file is checked in, so can't use absolute paths. It is
+  // included by the main CMakeLists.txt, so relative paths must be relative to
+  // that file's directory.
+  final String makefileDirPath = project.windows.cmakeFile.parent.absolute.path;
+  final path.Context cmakePathContext = path.Context(style: path.Style.posix);
+  final List<String> relativePathComponents = globals.fs.path.split(globals.fs.path.relative(
+    project.windows.pluginSymlinkDirectory.absolute.path,
+    from: makefileDirPath,
+  ));
   final Map<String, dynamic> context = <String, dynamic>{
+    'os': 'windows',
     'plugins': windowsPlugins,
+    'pluginsDir': cmakePathContext.joinAll(relativePathComponents),
   };
   await _writeCppPluginRegistrant(project.windows.managedDirectory, context);
-  await _writeWindowsPluginProperties(project.windows, windowsPlugins);
+  await _writePluginCmakefile(project.windows.generatedPluginCmakeFile, context);
 }
 
 Future<void> _writeCppPluginRegistrant(Directory destination, Map<String, dynamic> templateContext) async {
@@ -1005,20 +1016,6 @@
   );
 }
 
-Future<void> _writeWindowsPluginProperties(WindowsProject project, List<Map<String, dynamic>> windowsPlugins) async {
-  final List<String> pluginLibraryFilenames = windowsPlugins.map(
-    (Map<String, dynamic> plugin) => '${plugin['name']}_plugin.lib').toList();
-  // Use paths relative to the VS project directory.
-  final String projectDir = project.vcprojFile.parent.path;
-  final String symlinkDirPath = project.pluginSymlinkDirectory.path.substring(projectDir.length + 1);
-  final List<String> pluginIncludePaths = windowsPlugins.map((Map<String, dynamic> plugin) =>
-    globals.fs.path.join(symlinkDirPath, plugin['name'] as String, 'windows')).toList();
-  project.generatedPluginPropertySheetFile.writeAsStringSync(PropertySheet(
-    includePaths: pluginIncludePaths,
-    libraryDependencies: pluginLibraryFilenames,
-  ).toString());
-}
-
 Future<void> _writeWebPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
   final List<Map<String, dynamic>> webPlugins = _extractPlatformMaps(plugins, WebPlugin.kConfigKey);
   final Map<String, dynamic> context = <String, dynamic>{
@@ -1159,9 +1156,6 @@
   }
   if (featureFlags.isWindowsEnabled && project.windows.existsSync()) {
     await _writeWindowsPluginFiles(project, plugins);
-
-    final List<Plugin> nativePlugins = _filterNativePlugins(plugins, WindowsPlugin.kConfigKey);
-    await VisualStudioSolutionUtils(project: project.windows, fileSystem: globals.fs).updatePlugins(nativePlugins);
   }
   for (final XcodeBasedProject subproject in <XcodeBasedProject>[project.ios, project.macos]) {
     if (!project.isModule && (!checkProjects || subproject.existsSync())) {
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 2fe310c..c3b566e 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -329,6 +329,27 @@
   File get podManifestLock;
 }
 
+/// Represents a CMake-based sub-project.
+///
+/// This defines interfaces common to Windows and Linux projects.
+abstract class CmakeBasedProject {
+  /// The parent of this project.
+  FlutterProject get parent;
+
+  /// Whether the subproject (either Windows or Linux) exists in the Flutter project.
+  bool existsSync();
+
+  /// The native project CMake specification.
+  File get cmakeFile;
+
+  /// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for
+  /// the build.
+  File get generatedCmakeConfigFile;
+
+  /// Includable CMake with rules and variables for plugin builds.
+  File get generatedPluginCmakeFile;
+}
+
 /// Represents the iOS sub-project of a Flutter project.
 ///
 /// Instances will reflect the contents of the `ios/` sub-folder of
@@ -995,18 +1016,28 @@
 }
 
 /// The Windows sub project
-class WindowsProject extends FlutterProjectPlatform {
-  WindowsProject._(this.project);
+class WindowsProject extends FlutterProjectPlatform implements CmakeBasedProject {
+  WindowsProject._(this.parent);
 
-  final FlutterProject project;
+  @override
+  final FlutterProject parent;
 
   @override
   String get pluginConfigKey => WindowsPlugin.kConfigKey;
 
   @override
-  bool existsSync() => _editableDirectory.existsSync() && solutionFile.existsSync();
+  bool existsSync() => _editableDirectory.existsSync() && cmakeFile.existsSync();
 
-  Directory get _editableDirectory => project.directory.childDirectory('windows');
+  @override
+  File get cmakeFile => _editableDirectory.childFile('CMakeLists.txt');
+
+  @override
+  File get generatedCmakeConfigFile => ephemeralDirectory.childFile('generated_config.cmake');
+
+  @override
+  File get generatedPluginCmakeFile => managedDirectory.childFile('generated_plugins.cmake');
+
+  Directory get _editableDirectory => parent.directory.childDirectory('windows');
 
   /// The directory in the project that is managed by Flutter. As much as
   /// possible, files that are edited by Flutter tooling after initial project
@@ -1018,24 +1049,6 @@
   /// checked in should live here.
   Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral');
 
-  /// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for
-  /// the build.
-  File get generatedPropertySheetFile => ephemeralDirectory.childFile('Generated.props');
-
-  /// Contains configuration to add plugins to the build.
-  File get generatedPluginPropertySheetFile => managedDirectory.childFile('GeneratedPlugins.props');
-
-  // The MSBuild project file.
-  File get vcprojFile => _editableDirectory.childFile('Runner.vcxproj');
-
-  // The MSBuild solution file.
-  File get solutionFile => _editableDirectory.childFile('Runner.sln');
-
-  /// The file where the VS build will write the name of the built app.
-  ///
-  /// Ideally this will be replaced in the future with inspection of the project.
-  File get nameFile => ephemeralDirectory.childFile('exe_filename');
-
   /// The directory to write plugin symlinks.
   Directory get pluginSymlinkDirectory => ephemeralDirectory.childDirectory('.plugin_symlinks');
 
@@ -1043,15 +1056,16 @@
 }
 
 /// The Linux sub project.
-class LinuxProject extends FlutterProjectPlatform {
-  LinuxProject._(this.project);
+class LinuxProject extends FlutterProjectPlatform implements CmakeBasedProject {
+  LinuxProject._(this.parent);
 
-  final FlutterProject project;
+  @override
+  final FlutterProject parent;
 
   @override
   String get pluginConfigKey => LinuxPlugin.kConfigKey;
 
-  Directory get _editableDirectory => project.directory.childDirectory('linux');
+  Directory get _editableDirectory => parent.directory.childDirectory('linux');
 
   /// The directory in the project that is managed by Flutter. As much as
   /// possible, files that are edited by Flutter tooling after initial project
@@ -1066,14 +1080,13 @@
   @override
   bool existsSync() => _editableDirectory.existsSync();
 
-  /// The Linux project CMake specification.
+  @override
   File get cmakeFile => _editableDirectory.childFile('CMakeLists.txt');
 
-  /// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for
-  /// the build.
+  @override
   File get generatedCmakeConfigFile => ephemeralDirectory.childFile('generated_config.cmake');
 
-  /// Includable CMake with rules and variables for plugin builds.
+  @override
   File get generatedPluginCmakeFile => managedDirectory.childFile('generated_plugins.cmake');
 
   /// The directory to write plugin symlinks.
diff --git a/packages/flutter_tools/lib/src/windows/application_package.dart b/packages/flutter_tools/lib/src/windows/application_package.dart
index bd93c80..8051b06 100644
--- a/packages/flutter_tools/lib/src/windows/application_package.dart
+++ b/packages/flutter_tools/lib/src/windows/application_package.dart
@@ -5,10 +5,10 @@
 import 'package:meta/meta.dart';
 
 import '../application_package.dart';
-import '../base/common.dart';
 import '../base/file_system.dart';
 import '../base/utils.dart';
 import '../build_info.dart';
+import '../cmake.dart';
 import '../globals.dart' as globals;
 import '../project.dart';
 
@@ -55,24 +55,21 @@
 class BuildableWindowsApp extends WindowsApp {
   BuildableWindowsApp({
     @required this.project,
-  }) : super(projectBundleId: project.project.manifest.appName);
+  }) : super(projectBundleId: project.parent.manifest.appName);
 
   final WindowsProject project;
 
   @override
   String executable(BuildMode buildMode) {
-    final File exeNameFile = project.nameFile;
-    if (!exeNameFile.existsSync()) {
-      throwToolExit('Failed to find Windows executable name');
-    }
+    final String binaryName = getCmakeExecutableName(project);
     return globals.fs.path.join(
         getWindowsBuildDirectory(),
-        'x64',
+        'runner',
         toTitleCase(getNameForBuildMode(buildMode)),
-        'Runner',
-        exeNameFile.readAsStringSync().trim());
+        '$binaryName.exe',
+    );
   }
 
   @override
-  String get name => project.project.manifest.appName;
+  String get name => project.parent.manifest.appName;
 }
diff --git a/packages/flutter_tools/lib/src/windows/build_windows.dart b/packages/flutter_tools/lib/src/windows/build_windows.dart
index 9a3f813..31a9967 100644
--- a/packages/flutter_tools/lib/src/windows/build_windows.dart
+++ b/packages/flutter_tools/lib/src/windows/build_windows.dart
@@ -10,18 +10,23 @@
 import '../base/utils.dart';
 import '../build_info.dart';
 import '../cache.dart';
+import '../cmake.dart';
 import '../globals.dart' as globals;
 import '../plugins.dart';
 import '../project.dart';
-import 'property_sheet.dart';
 import 'visual_studio.dart';
 
+// From https://cmake.org/cmake/help/v3.15/manual/cmake-generators.7.html#visual-studio-generators
+// This may need to become a getter on VisualStudio in the future to support
+// future major versions of Visual Studio.
+const String _cmakeVisualStudioGeneratorIdentifier = 'Visual Studio 16 2019';
+
 /// Builds the Windows project using msbuild.
 Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {
   String target,
   VisualStudio visualStudioOverride,
 }) async {
-  if (!windowsProject.solutionFile.existsSync()) {
+  if (!windowsProject.cmakeFile.existsSync()) {
     throwToolExit(
       'No Windows desktop project configured. '
       'See https://github.com/flutter/flutter/wiki/Desktop-shells#create '
@@ -43,8 +48,8 @@
   }
 
   // Ensure that necessary emphemeral files are generated and up to date.
-  _writeGeneratedFlutterProperties(windowsProject, buildInfo, target);
-  createPluginSymlinks(windowsProject.project);
+  _writeGeneratedFlutterConfig(windowsProject, buildInfo, target);
+  createPluginSymlinks(windowsProject.parent);
 
   final VisualStudio visualStudio = visualStudioOverride ?? VisualStudio(
     fileSystem: globals.fs,
@@ -52,57 +57,88 @@
     logger: globals.logger,
     processManager: globals.processManager,
   );
-  final String vcvarsScript = visualStudio.vcvarsPath;
-  if (vcvarsScript == null) {
+  final String cmakePath = visualStudio.cmakePath;
+  if (cmakePath == null) {
     throwToolExit('Unable to find suitable Visual Studio toolchain. '
         'Please run `flutter doctor` for more details.');
   }
 
-  final String buildScript = globals.fs.path.join(
-    Cache.flutterRoot,
-    'packages',
-    'flutter_tools',
-    'bin',
-    'vs_build.bat',
-  );
-
-  final String configuration = toTitleCase(getNameForBuildMode(buildInfo.mode ?? BuildMode.release));
-  final String solutionPath = windowsProject.solutionFile.path;
-  final Stopwatch sw = Stopwatch()..start();
+  final String buildModeName = getNameForBuildMode(buildInfo.mode ?? BuildMode.release);
+  final Directory buildDirectory = globals.fs.directory(getWindowsBuildDirectory());
   final Status status = globals.logger.startProgress(
     'Building Windows application...',
     timeout: null,
   );
+  try {
+    await _runCmakeGeneration(cmakePath, buildDirectory, windowsProject.cmakeFile.parent);
+    await _runBuild(cmakePath, buildDirectory, buildModeName);
+  } finally {
+    status.cancel();
+  }
+}
+
+Future<void> _runCmakeGeneration(String cmakePath, Directory buildDir, Directory sourceDir) async {
+  final Stopwatch sw = Stopwatch()..start();
+
+  await buildDir.create(recursive: true);
   int result;
   try {
-    // Run the script with a relative path to the project using the enclosing
-    // directory as the workingDirectory, to avoid hitting the limit on command
-    // lengths in batch scripts if the absolute path to the project is long.
     result = await processUtils.stream(
       <String>[
-        buildScript,
-        vcvarsScript,
-        globals.fs.path.basename(solutionPath),
-        configuration,
+        cmakePath,
+        '-S',
+        sourceDir.path,
+        '-B',
+        buildDir.path,
+        '-G',
+        _cmakeVisualStudioGeneratorIdentifier,
+      ],
+      trace: true,
+    );
+  } on ArgumentError {
+    throwToolExit("cmake not found. Run 'flutter doctor' for more information.");
+  }
+  if (result != 0) {
+    throwToolExit('Unable to generate build files');
+  }
+  globals.flutterUsage.sendTiming('build', 'windows-cmake-generation', Duration(milliseconds: sw.elapsedMilliseconds));
+}
+
+Future<void> _runBuild(String cmakePath, Directory buildDir, String buildModeName) async {
+  final Stopwatch sw = Stopwatch()..start();
+
+  int result;
+  try {
+    result = await processUtils.stream(
+      <String>[
+        cmakePath,
+        '--build',
+        buildDir.path,
+        '--config',
+        toTitleCase(buildModeName),
+        '--target',
+        'INSTALL',
+        if (globals.logger.isVerbose)
+          '--verbose'
       ],
       environment: <String, String>{
         if (globals.logger.isVerbose)
           'VERBOSE_SCRIPT_LOGGING': 'true'
       },
-      workingDirectory: globals.fs.path.dirname(solutionPath),
       trace: true,
     );
-  } finally {
-    status.cancel();
+  } on ArgumentError {
+    throwToolExit("cmake not found. Run 'flutter doctor' for more information.");
   }
   if (result != 0) {
-    throwToolExit('Build process failed. To view the stack trace, please run `flutter run -d windows -v`.');
+    final String verboseInstructions = globals.logger.isVerbose ? '' : ' To view the stack trace, please run `flutter run -d windows -v`.';
+    throwToolExit('Build process failed.$verboseInstructions');
   }
-  globals.flutterUsage.sendTiming('build', 'vs_build', Duration(milliseconds: sw.elapsedMilliseconds));
+  globals.flutterUsage.sendTiming('build', 'windows-cmake-build', Duration(milliseconds: sw.elapsedMilliseconds));
 }
 
-/// Writes the generatedPropertySheetFile with the configuration for the given build.
-void _writeGeneratedFlutterProperties(
+/// Writes the generated CMake file with the configuration for the given build.
+void _writeGeneratedFlutterConfig(
   WindowsProject windowsProject,
   BuildInfo buildInfo,
   String target,
@@ -110,7 +146,7 @@
   final Map<String, String> environment = <String, String>{
     'FLUTTER_ROOT': Cache.flutterRoot,
     'FLUTTER_EPHEMERAL_DIR': windowsProject.ephemeralDirectory.path,
-    'PROJECT_DIR': windowsProject.project.directory.path,
+    'PROJECT_DIR': windowsProject.parent.directory.path,
     if (target != null)
       'FLUTTER_TARGET': target,
     ...buildInfo.toEnvironmentConfig(),
@@ -121,10 +157,7 @@
     environment['FLUTTER_ENGINE'] = globals.fs.path.dirname(globals.fs.path.dirname(engineOutPath));
     environment['LOCAL_ENGINE'] = globals.fs.path.basename(engineOutPath);
   }
-
-  final File propsFile = windowsProject.generatedPropertySheetFile;
-  propsFile.createSync(recursive: true);
-  propsFile.writeAsStringSync(PropertySheet(environmentVariables: environment).toString());
+  writeGeneratedCmakeConfig(Cache.flutterRoot, windowsProject, environment);
 }
 
 // Checks the template version of [project] against the current template
diff --git a/packages/flutter_tools/lib/src/windows/property_sheet.dart b/packages/flutter_tools/lib/src/windows/property_sheet.dart
deleted file mode 100644
index e088485..0000000
--- a/packages/flutter_tools/lib/src/windows/property_sheet.dart
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright 2014 The Flutter 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;
-
-/// A utility class for building property sheet (.props) files for use
-/// with MSBuild/Visual Studio projects.
-class PropertySheet {
-  /// Creates a PropertySheet with the given properties.
-  const PropertySheet({
-    this.environmentVariables,
-    this.includePaths,
-    this.libraryDependencies,
-  });
-
-  /// Variables to make available both as build macros and as environment
-  /// variables for script steps.
-  final Map<String, String> environmentVariables;
-
-  /// Directories to search for headers.
-  final List<String> includePaths;
-
-  /// Libraries to link against.
-  final List<String> libraryDependencies;
-
-  @override
-  String toString() {
-    // See https://docs.microsoft.com/en-us/cpp/build/reference/vcxproj-file-structure#property-sheet-layout
-
-    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');
-      });
-      builder.element('PropertyGroup', nest: () {
-        builder.attribute('Label', 'UserMacros');
-
-        _addEnviromentVariableUserMacros(builder);
-      });
-      builder.element('PropertyGroup');
-      builder.element('ItemDefinitionGroup', nest: () {
-        _addIncludePaths(builder);
-        _addLibraryDependencies(builder);
-      });
-      builder.element('ItemGroup', nest: () {
-        _addEnvironmentVariableBuildMacros(builder);
-      });
-    });
-
-    return builder.build().toXmlString(pretty: true, indent: '  ');
-  }
-
-  /// Adds directories to the header search path.
-  ///
-  /// Must be called within the context of the ItemDefinitionGroup.
-  void _addIncludePaths(xml.XmlBuilder builder) {
-    if (includePaths == null || includePaths.isEmpty) {
-      return;
-    }
-    builder.element('ClCompile', nest: () {
-      builder.element('AdditionalIncludeDirectories', nest: () {
-        builder.text('${includePaths.join(';')};%(AdditionalIncludeDirectories)');
-      });
-    });
-  }
-
-  /// Adds libraries to the link step.
-  ///
-  /// Must be called within the context of the ItemDefinitionGroup.
-  void _addLibraryDependencies(xml.XmlBuilder builder) {
-    if (libraryDependencies == null || libraryDependencies.isEmpty) {
-      return;
-    }
-    builder.element('Link', nest: () {
-      builder.element('AdditionalDependencies', nest: () {
-        builder.text('${libraryDependencies.join(';')};%(AdditionalDependencies)');
-      });
-    });
-  }
-
-  /// Writes key/value pairs for any environment variables as user macros.
-  ///
-  /// Must be called within the context of the UserMacros PropertyGroup.
-  void _addEnviromentVariableUserMacros(xml.XmlBuilder builder) {
-    if (environmentVariables == null) {
-      return;
-    }
-    for (final MapEntry<String, String> variable in environmentVariables.entries) {
-      builder.element(variable.key, nest: () {
-        builder.text(variable.value);
-      });
-    }
-  }
-
-  /// Writes the BuildMacros to expose environment variable UserMacros to the
-  /// environment.
-  ///
-  /// Must be called within the context of the ItemGroup.
-  void _addEnvironmentVariableBuildMacros(xml.XmlBuilder builder) {
-    if (environmentVariables == null) {
-      return;
-    }
-    for (final String name in environmentVariables.keys) {
-      builder.element('BuildMacro', nest: () {
-        builder.attribute('Include', name);
-        builder.element('Value', nest: () {
-          builder.text('\$($name)');
-        });
-        builder.element('EnvironmentVariable', nest: () {
-          builder.text('true');
-        });
-      });
-    }
-  }
-}
diff --git a/packages/flutter_tools/lib/src/windows/visual_studio.dart b/packages/flutter_tools/lib/src/windows/visual_studio.dart
index ac2d9fa..6187fee 100644
--- a/packages/flutter_tools/lib/src/windows/visual_studio.dart
+++ b/packages/flutter_tools/lib/src/windows/visual_studio.dart
@@ -147,20 +147,24 @@
     return '2019';
   }
 
-  /// The path to vcvars64.bat, or null if no Visual Studio installation has
+  /// The path to CMake, or null if no Visual Studio installation has
   /// the components necessary to build.
-  String get vcvarsPath {
+  String get cmakePath {
     final Map<String, dynamic> details = _usableVisualStudioDetails;
     if (details.isEmpty) {
       return null;
     }
-    return _fileSystem.path.join(
+    return _fileSystem.path.joinAll(<String>[
       _usableVisualStudioDetails[_installationPathKey] as String,
-      'VC',
-      'Auxiliary',
-      'Build',
-      'vcvars64.bat',
-    );
+      'Common7',
+      'IDE',
+      'CommonExtensions',
+      'Microsoft',
+      'CMake',
+      'CMake',
+      'bin',
+      'cmake.exe',
+    ]);
   }
 
   /// The major version of the Visual Studio install, as an integer.
@@ -212,6 +216,8 @@
     return <String, String>{
       // The C++ toolchain required by the template.
       'Microsoft.VisualStudio.Component.VC.Tools.x86.x64': cppToolchainDescription,
+      // CMake
+      'Microsoft.VisualStudio.Component.VC.CMake.Project': 'C++ CMake tools for Windows',
     };
   }
 
diff --git a/packages/flutter_tools/lib/src/windows/visual_studio_project.dart b/packages/flutter_tools/lib/src/windows/visual_studio_project.dart
deleted file mode 100644
index 47a4f87..0000000
--- a/packages/flutter_tools/lib/src/windows/visual_studio_project.dart
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2014 The Flutter 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:meta/meta.dart';
-import 'package:xml/xml.dart' as xml;
-
-import '../base/file_system.dart';
-
-/// A utility class for interacting with Visual Studio project files (e.g.,
-/// .vcxproj).
-class VisualStudioProject {
-  /// Creates a project object from the project file at [file].
-  VisualStudioProject(this.file, {
-    @required FileSystem fileSystem,
-  }): _fileSystem = fileSystem {
-    try {
-      content = xml.parse(file.readAsStringSync());
-    } on xml.XmlParserException {
-      // Silently continue; formatUnderstood will return false.
-    }
-  }
-
-  final FileSystem _fileSystem;
-
-  /// The file corresponding to this object.
-  final File file;
-
-  /// The content of the project file.
-  xml.XmlDocument content;
-
-  /// Whether or not the project file was correctly parsed.
-  ///
-  /// If false, this could indicate that the project file is damaged, or that
-  /// it's an unsupported project type.
-  bool get formatUnderstood => content != null;
-
-  String _guid;
-
-  /// Returns the ProjectGuid for the project, or null if it's not present.
-  String get guid {
-    return _guid ??= _findGuid();
-  }
-
-  String _findGuid() {
-    if (!formatUnderstood) {
-      return null;
-    }
-    try {
-      final String guidValue = content.findAllElements('ProjectGuid').single.text.trim();
-      // Remove the enclosing {} from the value.
-      return guidValue.substring(1, guidValue.length - 1);
-    } on StateError {
-      // If there is not exactly one ProjectGuid, return null.
-      return null;
-    }
-  }
-
-  String _name;
-
-  /// Returns the ProjectName for the project.
-  ///
-  /// If not explicitly set in the project, uses the basename of the project
-  /// file.
-  String get name {
-    return _name ??= _findName();
-  }
-
-  String _findName() {
-    if (!formatUnderstood) {
-      return null;
-    }
-    try {
-      return content.findAllElements('ProjectName').first.text.trim();
-    } on StateError {
-      // If there is no name, fall back to filename.
-      return _fileSystem.path.basenameWithoutExtension(file.path);
-    }
-  }
-}
diff --git a/packages/flutter_tools/lib/src/windows/visual_studio_solution_utils.dart b/packages/flutter_tools/lib/src/windows/visual_studio_solution_utils.dart
deleted file mode 100644
index f5a40fe..0000000
--- a/packages/flutter_tools/lib/src/windows/visual_studio_solution_utils.dart
+++ /dev/null
@@ -1,345 +0,0 @@
-// Copyright 2014 The Flutter 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:meta/meta.dart';
-
-import '../base/common.dart';
-import '../base/file_system.dart';
-import '../convert.dart';
-import '../plugins.dart';
-import '../project.dart';
-import 'visual_studio_project.dart';
-
-// Constants corresponding to specific reference types in a solution file.
-// These values are defined by the .sln format.
-const String _kSolutionTypeGuidFolder = '2150E333-8FDC-42A3-9474-1A3956D46DE8';
-const String _kSolutionTypeGuidVcxproj = '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942';
-
-// The GUID for the folder above, managed by this class. This is an arbitrary
-// value that was randomly generated, but it should not be changed since that
-// would cause issues for existing Flutter projects.
-const String _kFlutterPluginSolutionFolderGuid = '5C2E738A-1DD3-445A-AAC8-EEB9648DD07C';
-// The FlutterBuild project GUID. This is an arbitrary
-// value that was randomly generated, but it should not be changed since that
-// would cause issues for existing Flutter projects.
-const String _kFlutterBuildProjectGuid = '6419BF13-6ECD-4CD2-9E85-E566A1F03F8F';
-
-/// Extracts and stores the plugin name and vcxproj GUID for [plugin].
-class _PluginProjectInfo {
-  _PluginProjectInfo(Plugin plugin, {
-    @required FileSystem fileSystem,
-  }) {
-    name = plugin.name;
-    final File projectFile = fileSystem.directory(plugin.path).childDirectory('windows').childFile('plugin.vcxproj');
-    try {
-    guid = VisualStudioProject(projectFile, fileSystem: fileSystem).guid;
-    } on FileSystemException {
-      throwToolExit('Unable to find a plugin.vcxproj for plugin "$name"');
-    }
-    if (guid == null) {
-      throwToolExit('Unable to find a plugin.vcxproj ID for plugin "$name"');
-    }
-  }
-
-  // The name of the plugin, which is also the name of the symlink folder.
-  String name;
-
-  // The GUID of the plugin's project.
-  String guid;
-}
-
-// TODO(stuartmorgan): Consider replacing this class with a real parser. See
-// https://github.com/flutter/flutter/issues/51430.
-
-class VisualStudioSolutionUtils {
-  const VisualStudioSolutionUtils({
-    @required WindowsProject project,
-    @required FileSystem fileSystem,
-  }) : _project = project,
-       _fileSystem = fileSystem;
-
-  final WindowsProject _project;
-  final FileSystem _fileSystem;
-
-  /// Updates the solution file for [project] to have the project references and
-  /// dependencies to include [plugins], removing any previous plugins from the
-  /// solution.
-  Future<void> updatePlugins(List<Plugin> plugins) async {
-    if (!_project.solutionFile.existsSync()) {
-      throwToolExit(
-        'Attempted to update Windows plugins on a project that does not '
-        'support Windows.',
-      );
-    }
-    final String solutionContent = await _project.solutionFile.readAsString();
-
-    // Map of GUID to name for the current plugin list.
-    final Map<String, String> currentPluginInfo = _getWindowsPluginNamesByGuid(plugins);
-
-    // Find any plugins referenced in the project that are no longer used, and
-    // any that are new.
-    //
-    // While the simplest approach to updating the solution would be to remove all
-    // entries associated with plugins, and then add all the current plugins in
-    // one block, Visual Studio has its own (unknown, likely data-structure-hash
-    // based) order that it will use each time it writes out the file due to any
-    // solution-level changes made in the UI. To avoid thrashing, and resulting
-    // confusion (e.g., in review diffs), this update attempts to instead preserve
-    // the ordering that is already there, so that once Visual Studio has
-    // reordered the plugins, that order will be stable.
-    final Set<String> existingPlugins = _findPreviousPluginGuids(solutionContent);
-    final Set<String> currentPlugins = currentPluginInfo.keys.toSet();
-    final Set<String> removedPlugins = existingPlugins.difference(currentPlugins);
-    final Set<String> addedPlugins = currentPlugins.difference(existingPlugins);
-
-    final RegExp projectStartPattern = RegExp(r'^Project\("{' + _kSolutionTypeGuidVcxproj + r'}"\)\s*=\s*".*",\s*"(.*)",\s*"{([A-Fa-f0-9\-]*)}"\s*$');
-    final RegExp pluginsFolderProjectStartPattern = RegExp(r'^Project\("{' + _kSolutionTypeGuidFolder + r'}"\)\s*=.*"{' + _kFlutterPluginSolutionFolderGuid + r'}"\s*$');
-    final RegExp projectEndPattern = RegExp(r'^EndProject\s*$');
-    final RegExp globalStartPattern = RegExp(r'^Global\s*$');
-    final RegExp globalEndPattern = RegExp(r'^EndGlobal\s*$');
-    final RegExp projectDependenciesStartPattern = RegExp(r'^\s*ProjectSection\(ProjectDependencies\)\s*=\s*postProject\s*$');
-    final RegExp globalSectionProjectConfigurationStartPattern = RegExp(r'^\s*GlobalSection\(ProjectConfigurationPlatforms\)\s*=\s*postSolution\s*$');
-    final RegExp globalSectionNestedProjectsStartPattern = RegExp(r'^\s*GlobalSection\(NestedProjects\)\s*=\s*preSolution\s*$');
-
-    final StringBuffer newSolutionContent = StringBuffer();
-    // readAsString drops the BOM; re-add it.
-    newSolutionContent.writeCharCode(unicodeBomCharacterRune);
-
-    final Iterator<String> lineIterator = solutionContent.split('\n').iterator;
-    bool foundFlutterPluginsFolder = false;
-    bool foundNestedProjectsSection = false;
-    bool foundRunnerProject = false;
-    while (lineIterator.moveNext()) {
-      final Match projectStartMatch = projectStartPattern.firstMatch(lineIterator.current);
-      if (projectStartMatch != null) {
-        final String guid = projectStartMatch.group(2);
-        if (currentPlugins.contains(guid)) {
-          // Write an up-to-date version at this location (in case, e.g., the name
-          // has changed).
-          _writePluginProjectEntry(guid, currentPluginInfo[guid], newSolutionContent);
-          // Drop the old copy.
-          _skipUntil(lineIterator, projectEndPattern);
-          continue;
-        } else if (removedPlugins.contains(guid)) {
-          // Drop the stale plugin project.
-          _skipUntil(lineIterator, projectEndPattern);
-          continue;
-        } else if (projectStartMatch.group(1) == _project.vcprojFile.basename) {
-          foundRunnerProject = true;
-          // Update the Runner project's dependencies on the plugins.
-          // Skip to the dependencies section, or if there isn't one the end of
-          // the project.
-          while (!projectDependenciesStartPattern.hasMatch(lineIterator.current) &&
-              !projectEndPattern.hasMatch(lineIterator.current)) {
-            newSolutionContent.writeln(lineIterator.current);
-            lineIterator.moveNext();
-          }
-          // Add/update the dependencies section.
-          if (projectDependenciesStartPattern.hasMatch(lineIterator.current)) {
-            newSolutionContent.writeln(lineIterator.current);
-            _processSectionPluginReferences(removedPlugins, addedPlugins, lineIterator, _writeProjectDependency, newSolutionContent);
-          } else {
-            _writeDependenciesSection(currentPlugins, newSolutionContent);
-          }
-        }
-      }
-
-      if (pluginsFolderProjectStartPattern.hasMatch(lineIterator.current)) {
-          foundFlutterPluginsFolder = true;
-      }
-
-      if (globalStartPattern.hasMatch(lineIterator.current)) {
-        // The Global section is the end of the project list. Add any new plugins
-        // here, since the location VS will use is unknown. They will likely be
-        // reordered the next time VS writes the file.
-        for (final String guid in addedPlugins) {
-          _writePluginProjectEntry(guid, currentPluginInfo[guid], newSolutionContent);
-        }
-        // Also add the plugins folder if there wasn't already one.
-        if (!foundFlutterPluginsFolder) {
-          _writePluginFolderProjectEntry(newSolutionContent);
-        }
-      }
-
-      // Update the ProjectConfiguration section once it is reached.
-      if (globalSectionProjectConfigurationStartPattern.hasMatch(lineIterator.current)) {
-        newSolutionContent.writeln(lineIterator.current);
-        _processSectionPluginReferences(removedPlugins, addedPlugins, lineIterator, _writePluginConfigurationEntries, newSolutionContent);
-      }
-
-      // Update the NestedProjects section once it is reached.
-      if (globalSectionNestedProjectsStartPattern.hasMatch(lineIterator.current)) {
-        newSolutionContent.writeln(lineIterator.current);
-        _processSectionPluginReferences(removedPlugins, addedPlugins, lineIterator, _writePluginNestingEntry, newSolutionContent);
-        foundNestedProjectsSection = true;
-      }
-
-      // If there wasn't a NestedProjects global section, add one at the end.
-      if (!foundNestedProjectsSection && globalEndPattern.hasMatch(lineIterator.current)) {
-        newSolutionContent.writeln('\tGlobalSection(NestedProjects) = preSolution\r');
-        for (final String guid in currentPlugins) {
-          _writePluginNestingEntry(guid, newSolutionContent);
-        }
-        newSolutionContent.writeln('\tEndGlobalSection\r');
-      }
-
-      // Re-output anything that hasn't been explicitly skipped above.
-      newSolutionContent.writeln(lineIterator.current);
-    }
-
-    if (!foundRunnerProject) {
-      throwToolExit(
-          'Could not add plugins to Windows project:\n'
-          'Unable to find a "${_project.vcprojFile.basename}" project in ${_project.solutionFile.path}');
-    }
-
-    await _project.solutionFile.writeAsString(newSolutionContent.toString().trimRight());
-  }
-
-  /// Advances [iterator] it reaches an element that matches [pattern].
-  ///
-  /// Note that the current element at the time of calling is *not* checked.
-  void _skipUntil(Iterator<String> iterator, RegExp pattern) {
-    while (iterator.moveNext()) {
-      if (pattern.hasMatch(iterator.current)) {
-        return;
-      }
-    }
-  }
-
-  /// Writes the main project entry for the plugin with the given [guid] and
-  /// [name].
-  void _writePluginProjectEntry(String guid, String name, StringBuffer output) {
-    output.write('''
-Project("{$_kSolutionTypeGuidVcxproj}") = "$name", "Flutter\\ephemeral\\.plugin_symlinks\\$name\\windows\\plugin.vcxproj", "{$guid}"\r
-\tProjectSection(ProjectDependencies) = postProject\r
-\t\t{$_kFlutterBuildProjectGuid} = {$_kFlutterBuildProjectGuid}\r
-\tEndProjectSection\r
-EndProject\r
-''');
-  }
-
-  /// Writes the main project entry for the Flutter Plugins solution folder.
-  void _writePluginFolderProjectEntry(StringBuffer output) {
-    const String folderName = 'Flutter Plugins';
-    output.write('''
-Project("{$_kSolutionTypeGuidFolder}") = "$folderName", "$folderName", "{$_kFlutterPluginSolutionFolderGuid}"\r
-EndProject\r
-''');
-  }
-
-  /// Writes a project dependencies section, depending on all the GUIDs in
-  /// [dependencies].
-  void _writeDependenciesSection(Iterable<String> dependencies, StringBuffer output) {
-    output.writeln('ProjectSection(ProjectDependencies) = postProject\r');
-    for (final String guid in dependencies) {
-      _writeProjectDependency(guid, output);
-    }
-    output.writeln('EndProjectSection\r');
-  }
-
-  /// Returns the GUIDs of all the Flutter plugin projects in the given solution.
-  Set<String> _findPreviousPluginGuids(String solutionContent) {
-    // Find the plugin folder's known GUID in ProjectDependencies lines.
-    // Each line in that section has the form:
-    //   {project GUID} = {solution folder GUID}
-    final RegExp pluginFolderChildrenPattern = RegExp(
-        r'^\s*{([A-Fa-f0-9\-]*)}\s*=\s*{' + _kFlutterPluginSolutionFolderGuid + r'}\s*$',
-        multiLine: true,
-    );
-    return pluginFolderChildrenPattern
-        .allMatches(solutionContent)
-        .map((Match match) => match.group(1)).toSet();
-  }
-
-  /// Returns a mapping of plugin project GUID to name for all the Windows plugins
-  /// in [plugins].
-  Map<String, String> _getWindowsPluginNamesByGuid(List<Plugin> plugins) {
-    final Map<String, String> currentPluginInfo = <String, String>{};
-    for (final Plugin plugin in plugins) {
-      if (plugin.platforms.containsKey(_project.pluginConfigKey)) {
-        final _PluginProjectInfo info = _PluginProjectInfo(plugin, fileSystem: _fileSystem);
-        if (currentPluginInfo.containsKey(info.guid)) {
-          throwToolExit('The plugins "${currentPluginInfo[info.guid]}" and "${info.name}" '
-              'have the same ProjectGuid, which prevents them from being used together.\n\n'
-              'Please contact the plugin authors to resolve this, and/or remove one of the '
-              'plugins from your project.');
-        }
-        currentPluginInfo[info.guid] = info.name;
-      }
-    }
-    return currentPluginInfo;
-  }
-
-  /// Walks a GlobalSection or ProjectSection, removing entries related to removed
-  /// plugins and adding entries for new plugins at the end using
-  /// [newEntryWriter], which takes the guid of the plugin to write entries for.
-  ///
-  /// The caller is responsible for printing the section start line, which should
-  /// be [lineIterator.current] when this is called, and the section end line,
-  /// which will be [lineIterator.current] on return.
-  void _processSectionPluginReferences(
-      Set<String> removedPlugins,
-      Set<String> addedPlugins,
-      Iterator<String> lineIterator,
-      Function(String, StringBuffer) newEntryWriter,
-      StringBuffer output,
-  ) {
-    // Extracts the guid of the project that a line refers to. Currently all
-    // sections this function is used for start with "{project guid}", even though
-    // the rest of the line varies by section, so the pattern can currently be
-    // shared rather than parameterized.
-    final RegExp entryPattern = RegExp(r'^\s*{([A-Fa-f0-9\-]*)}');
-    final RegExp sectionEndPattern = RegExp(r'^\s*End\w*Section\s*$');
-    while (lineIterator.moveNext()) {
-      if (sectionEndPattern.hasMatch(lineIterator.current)) {
-        // The end of the section; add entries for new plugins, then exit.
-        for (final String guid in addedPlugins) {
-          newEntryWriter(guid, output);
-        }
-        return;
-      }
-      // Otherwise it's the sectino body. Drop any lines associated with old
-      // plugins, but pass everything else through as output.
-      final Match entryMatch = entryPattern.firstMatch(lineIterator.current);
-      if (entryMatch != null && removedPlugins.contains(entryMatch.group(1))) {
-        continue;
-      }
-      output.writeln(lineIterator.current);
-    }
-  }
-
-  /// Writes all the configuration entries for the plugin project with the given
-  /// [guid].
-  ///
-  /// Should be called within the context of writing
-  /// GlobalSection(ProjectConfigurationPlatforms).
-  void _writePluginConfigurationEntries(String guid, StringBuffer output) {
-    final List<String> configurations = <String>['Debug', 'Profile', 'Release'];
-    final List<String> entryTypes = <String>['ActiveCfg', 'Build.0'];
-    for (final String configuration in configurations) {
-      for (final String entryType in entryTypes) {
-        output.writeln('\t\t{$guid}.$configuration|x64.$entryType = $configuration|x64\r');
-      }
-    }
-  }
-
-  /// Writes the entries to nest the plugin projects with the given [guid] under
-  /// the Flutter Plugins solution folder.
-  ///
-  /// Should be called within the context of writing
-  /// GlobalSection(NestedProjects).
-  void _writePluginNestingEntry(String guid, StringBuffer output) {
-    output.writeln('\t\t{$guid} = {$_kFlutterPluginSolutionFolderGuid}\r');
-  }
-
-  /// Writes the entrie to make a project depend on another project with the
-  /// given [guid].
-  ///
-  /// Should be called within the context of writing
-  /// ProjectSection(ProjectDependencies).
-  void _writeProjectDependency(String guid, StringBuffer output) {
-    output.writeln('\t\t{$guid} = {$guid}\r');
-  }
-}