Switch to CMake for Linux desktop (#57238)

Updates the Linux templates to use CMake+ninja, rather than Make, and updates the tooling to generate CMake support files rather than Make support files, and to drive the build using cmake and ninja.

Also updates doctor to check for cmake and ninja in place of make.

Note: While we could use CMake+Make rather than CMake+ninja, in testing ninja handled the tool_backend.sh call much better, calling it only once rather than once per dependent target. While it does add another dependency that people are less likely to already have, it's widely available in package managers, as well as being available as a direct download. Longer term, we could potentially switch from ninja to Make if it's an issue.

Fixes #52751
diff --git a/packages/flutter_tools/lib/src/base/user_messages.dart b/packages/flutter_tools/lib/src/base/user_messages.dart
index a5dc91b..133232e 100644
--- a/packages/flutter_tools/lib/src/base/user_messages.dart
+++ b/packages/flutter_tools/lib/src/base/user_messages.dart
@@ -213,6 +213,21 @@
   String get visualStudioIsIncomplete => 'The current Visual Studio installation is incomplete. Please reinstall Visual Studio.';
   String get visualStudioRebootRequired => 'Visual Studio requires a reboot of your system to complete installation.';
 
+  // Messages used in LinuxDoctorValidator
+  String get clangMissing => 'clang++ is required for Linux development.\n'
+      'It is likely available from your distribution (e.g.: apt install clang), or '
+      'can be downloaded from https://releases.llvm.org/';
+  String clangTooOld(String minimumVersion) => 'clang++ $minimumVersion or later is required.';
+  String get cmakeMissing => 'CMake is required for Linux development.\n'
+      'It is likely available from your distribution (e.g.: apt install cmake), or '
+      'can be downloaded from https://cmake.org/download/';
+  String cmakeTooOld(String minimumVersion) => 'cmake $minimumVersion or later is required.';
+  String ninjaVersion(String version) => 'ninja version $version';
+  String get ninjaMissing => 'ninja is required for Linux development.\n'
+      'It is likely available from your distribution (e.g.: apt install ninja-build), or '
+      'can be downloaded from https://github.com/ninja-build/ninja/releases';
+  String ninjaTooOld(String minimumVersion) => 'ninja $minimumVersion or later is required.';
+
   // Messages used in FlutterCommand
   String flutterElapsedTime(String name, String elapsedTime) => '"flutter $name" took $elapsedTime.';
   String get flutterNoDevelopmentDevice =>
diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart
index c883bab..f492045 100644
--- a/packages/flutter_tools/lib/src/doctor.dart
+++ b/packages/flutter_tools/lib/src/doctor.dart
@@ -97,6 +97,7 @@
       if (linuxWorkflow.appliesToHostPlatform)
         LinuxDoctorValidator(
           processManager: globals.processManager,
+          userMessages: userMessages,
         ),
       if (windowsWorkflow.appliesToHostPlatform)
         visualStudioValidator,
diff --git a/packages/flutter_tools/lib/src/linux/application_package.dart b/packages/flutter_tools/lib/src/linux/application_package.dart
index 13568eb..d1a579f 100644
--- a/packages/flutter_tools/lib/src/linux/application_package.dart
+++ b/packages/flutter_tools/lib/src/linux/application_package.dart
@@ -9,7 +9,7 @@
 import '../build_info.dart';
 import '../globals.dart' as globals;
 import '../project.dart';
-import 'makefile.dart';
+import 'cmake.dart';
 
 abstract class LinuxApp extends ApplicationPackage {
   LinuxApp({@required String projectBundleId}) : super(id: projectBundleId);
@@ -58,8 +58,13 @@
 
   @override
   String executable(BuildMode buildMode) {
-    final String binaryName = makefileExecutableName(project);
-    return globals.fs.path.join(getLinuxBuildDirectory(), getNameForBuildMode(buildMode), binaryName);
+    final String binaryName = getCmakeExecutableName(project);
+    return globals.fs.path.join(
+        getLinuxBuildDirectory(),
+        getNameForBuildMode(buildMode),
+        'bundle',
+        binaryName,
+    );
   }
 
   @override
diff --git a/packages/flutter_tools/lib/src/linux/build_linux.dart b/packages/flutter_tools/lib/src/linux/build_linux.dart
index 609f2a9..a823999 100644
--- a/packages/flutter_tools/lib/src/linux/build_linux.dart
+++ b/packages/flutter_tools/lib/src/linux/build_linux.dart
@@ -7,11 +7,13 @@
 import '../base/file_system.dart';
 import '../base/logger.dart';
 import '../base/process.dart';
+import '../base/utils.dart';
 import '../build_info.dart';
 import '../cache.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(
@@ -19,7 +21,7 @@
   BuildInfo buildInfo, {
     String target = 'lib/main.dart',
   }) async {
-  if (!linuxProject.makeFile.existsSync()) {
+  if (!linuxProject.cmakeFile.existsSync()) {
     throwToolExit('No Linux desktop project configured. See '
       'https://github.com/flutter/flutter/wiki/Desktop-shells#create '
       'to learn about adding Linux support to a project.');
@@ -39,29 +41,18 @@
       'Upgrade Flutter and try again.');
   }
 
-  final StringBuffer buffer = StringBuffer('''
-# Generated code do not commit.
-export FLUTTER_ROOT=${Cache.flutterRoot}
-export FLUTTER_TARGET=$target
-export PROJECT_DIR=${linuxProject.project.directory.path}
-''');
+  // Build the environment that needs to be set for the re-entrant flutter build
+  // step.
   final Map<String, String> environmentConfig = buildInfo.toEnvironmentConfig();
-  for (final String key in environmentConfig.keys) {
-    final String value = environmentConfig[key];
-    buffer.writeln('export $key=$value');
-  }
-
+  environmentConfig['FLUTTER_TARGET'] = target;
   if (globals.artifacts is LocalEngineArtifacts) {
     final LocalEngineArtifacts localEngineArtifacts = globals.artifacts as LocalEngineArtifacts;
     final String engineOutPath = localEngineArtifacts.engineOutPath;
-    buffer.writeln('export FLUTTER_ENGINE=${globals.fs.path.dirname(globals.fs.path.dirname(engineOutPath))}');
-    buffer.writeln('export LOCAL_ENGINE=${globals.fs.path.basename(engineOutPath)}');
+    environmentConfig['FLUTTER_ENGINE'] = globals.fs.path.dirname(globals.fs.path.dirname(engineOutPath));
+    environmentConfig['LOCAL_ENGINE'] = globals.fs.path.basename(engineOutPath);
   }
+  writeGeneratedCmakeConfig(Cache.flutterRoot, linuxProject, environmentConfig);
 
-  /// Cache flutter configuration files in the linux directory.
-  linuxProject.generatedMakeConfigFile
-    ..createSync(recursive: true)
-    ..writeAsStringSync(buffer.toString());
   createPluginSymlinks(linuxProject.project);
 
   if (!buildInfo.isDebug) {
@@ -73,36 +64,77 @@
     globals.printStatus('');
   }
 
-  // Invoke make.
-  final String buildFlag = getNameForBuildMode(buildInfo.mode ?? BuildMode.release);
-  final Stopwatch sw = Stopwatch()..start();
   final Status status = globals.logger.startProgress(
     'Building Linux application...',
     timeout: null,
   );
+  try {
+    final String buildModeName = getNameForBuildMode(buildInfo.mode ?? BuildMode.release);
+    final Directory buildDirectory = globals.fs.directory(getLinuxBuildDirectory()).childDirectory(buildModeName);
+    await _runCmake(buildModeName, linuxProject.cmakeFile.parent, buildDirectory);
+    await _runBuild(buildDirectory);
+  } finally {
+    status.cancel();
+  }
+}
+
+Future<void> _runCmake(String buildModeName, Directory sourceDir, Directory buildDir) async {
+  final Stopwatch sw = Stopwatch()..start();
+
+  final String buildFlag = toTitleCase(buildModeName);
   int result;
   try {
     result = await processUtils.stream(
       <String>[
-        'make',
+        'cmake',
+        '-S',
+        sourceDir.path,
+        '-B',
+        buildDir.path,
+        '-G',
+        'Ninja',
+        '-DCMAKE_BUILD_TYPE=$buildFlag',
+      ],
+      environment: <String, String>{
+        'CC': 'clang',
+        'CXX': 'clang++'
+      },
+      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', 'cmake-linux', Duration(milliseconds: sw.elapsedMilliseconds));
+}
+
+Future<void> _runBuild(Directory buildDir) async {
+  final Stopwatch sw = Stopwatch()..start();
+
+  int result;
+  try {
+    result = await processUtils.stream(
+      <String>[
+        'ninja',
         '-C',
-        linuxProject.makeFile.parent.path,
-        'BUILD=$buildFlag',
+        buildDir.path,
+        'install',
       ],
       environment: <String, String>{
         if (globals.logger.isVerbose)
           'VERBOSE_SCRIPT_LOGGING': 'true'
-      }, trace: true,
+      },
+      trace: true,
     );
   } on ArgumentError {
-    throwToolExit("make not found. Run 'flutter doctor' for more information.");
-  } finally {
-    status.cancel();
+    throwToolExit("ninja not found. Run 'flutter doctor' for more information.");
   }
   if (result != 0) {
     throwToolExit('Build process failed');
   }
-  globals.flutterUsage.sendTiming('build', 'make-linux', Duration(milliseconds: sw.elapsedMilliseconds));
+  globals.flutterUsage.sendTiming('build', 'linux-ninja', Duration(milliseconds: sw.elapsedMilliseconds));
 }
 
 // Checks the template version of [project] against the current template
diff --git a/packages/flutter_tools/lib/src/linux/cmake.dart b/packages/flutter_tools/lib/src/linux/cmake.dart
new file mode 100644
index 0000000..8a50066
--- /dev/null
+++ b/packages/flutter_tools/lib/src/linux/cmake.dart
@@ -0,0 +1,49 @@
+// 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 '../project.dart';
+
+/// Extracts the `BINARY_NAME` from a Linux project CMake file.
+///
+/// Returns `null` if it cannot be found.
+String getCmakeExecutableName(LinuxProject project) {
+  if (!project.cmakeFile.existsSync()) {
+    return null;
+  }
+  final RegExp nameSetPattern = RegExp(r'^\s*set\(BINARY_NAME\s*"(.*)"\s*\)\s*$');
+  for (final String line in project.cmakeFile.readAsLinesSync()) {
+    final RegExpMatch match = nameSetPattern.firstMatch(line);
+    if (match != null) {
+      return match.group(1);
+    }
+  }
+  return null;
+}
+
+/// 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) {
+  // 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 StringBuffer buffer = StringBuffer('''
+# Generated code do not commit.
+set(FLUTTER_ROOT "$flutterRoot")
+set(PROJECT_DIR "${project.project.directory.path}")
+
+# Environment variables to pass to tool_backend.sh
+list(APPEND FLUTTER_TOOL_ENVIRONMENT
+  "FLUTTER_ROOT=\\"\${FLUTTER_ROOT}\\""
+  "PROJECT_DIR=\\"\${PROJECT_DIR}\\""
+''');
+  for (final String key in environment.keys) {
+    final String value = environment[key];
+    buffer.writeln('  "$key=\\"$value\\""');
+  }
+  buffer.writeln(')');
+
+  project.generatedCmakeConfigFile
+    ..createSync(recursive: true)
+    ..writeAsStringSync(buffer.toString());
+}
diff --git a/packages/flutter_tools/lib/src/linux/linux_doctor.dart b/packages/flutter_tools/lib/src/linux/linux_doctor.dart
index 026052c..94e1c72 100644
--- a/packages/flutter_tools/lib/src/linux/linux_doctor.dart
+++ b/packages/flutter_tools/lib/src/linux/linux_doctor.dart
@@ -6,70 +6,133 @@
 import 'package:process/process.dart';
 
 import '../base/io.dart';
+import '../base/user_messages.dart';
 import '../base/version.dart';
 import '../doctor.dart';
 
+/// A combination of version description and parsed version number.
+class _VersionInfo {
+  /// Constructs a VersionInfo from a version description string.
+  ///
+  /// This should contain a version number. For example:
+  ///     "clang version 9.0.1-6+build1"
+  _VersionInfo(this.description) {
+    final String versionString = RegExp(r'[0-9]+\.[0-9]+\.[0-9]+').firstMatch(description).group(0);
+    number = Version.parse(versionString);
+  }
+
+  // The full info string reported by the binary.
+  String description;
+
+  // The parsed Version.
+  Version number;
+}
+
 /// A validator that checks for Clang and Make build dependencies
 class LinuxDoctorValidator extends DoctorValidator {
   LinuxDoctorValidator({
     @required ProcessManager processManager,
+    @required UserMessages userMessages,
   }) : _processManager = processManager,
+       _userMessages = userMessages,
        super('Linux toolchain - develop for Linux desktop');
 
   final ProcessManager _processManager;
+  final UserMessages _userMessages;
 
-  /// The minimum version of clang supported.
-  final Version minimumClangVersion = Version(3, 4, 0);
+  static const String kClangBinary = 'clang++';
+  static const String kCmakeBinary = 'cmake';
+  static const String kNinjaBinary = 'ninja';
+
+  final Map<String, Version> _requiredBinaryVersions = <String, Version>{
+    kClangBinary: Version(3, 4, 0),
+    kCmakeBinary: Version(3, 10, 0),
+    kNinjaBinary: Version(1, 8, 0),
+  };
 
   @override
   Future<ValidationResult> validate() async {
     ValidationType validationType = ValidationType.installed;
     final List<ValidationMessage> messages = <ValidationMessage>[];
-    /// Check for a minimum version of Clang.
-    ProcessResult clangResult;
-    try {
-      clangResult = await _processManager.run(const <String>[
-        'clang++',
-        '--version',
-      ]);
-    } on ArgumentError {
-      // ignore error.
-    }
-    if (clangResult == null || clangResult.exitCode != 0) {
+
+    final Map<String, _VersionInfo> installedVersions = <String, _VersionInfo>{
+      // Sort the check to make the call order predictable for unit tests.
+      for (String binary in _requiredBinaryVersions.keys.toList()..sort())
+          binary: await _getBinaryVersion(binary)
+    };
+
+    // Determine overall validation level.
+    if (installedVersions.values.contains(null)) {
       validationType = ValidationType.missing;
-      messages.add(const ValidationMessage.error('clang++ is not installed'));
-    } else {
-      final String firstLine = (clangResult.stdout as String).split('\n').first.trim();
-      final String versionString = RegExp(r'[0-9]+\.[0-9]+\.[0-9]+').firstMatch(firstLine).group(0);
-      final Version version = Version.parse(versionString);
-      if (version >= minimumClangVersion) {
-        messages.add(ValidationMessage('clang++ $version'));
+    } else if (installedVersions.keys.any((String binary) =>
+          installedVersions[binary].number < _requiredBinaryVersions[binary])) {
+      validationType = ValidationType.partial;
+    }
+
+    // Message for Clang.
+    {
+      final _VersionInfo version = installedVersions[kClangBinary];
+      if (version == null) {
+        messages.add(ValidationMessage.error(_userMessages.clangMissing));
       } else {
-        validationType = ValidationType.partial;
-        messages.add(ValidationMessage.error('clang++ $version is below minimum version of $minimumClangVersion'));
+        messages.add(ValidationMessage(version.description));
+        final Version requiredVersion = _requiredBinaryVersions[kClangBinary];
+        if (version.number < requiredVersion) {
+          messages.add(ValidationMessage.error(_userMessages.clangTooOld(requiredVersion.toString())));
+        }
       }
     }
 
-    /// Check for make.
-    // TODO(jonahwilliams): tighten this check to include a version when we have
-    // a better idea about what is supported.
-    ProcessResult makeResult;
+    // Message for CMake.
+    {
+      final _VersionInfo version = installedVersions[kCmakeBinary];
+      if (version == null) {
+        messages.add(ValidationMessage.error(_userMessages.cmakeMissing));
+      } else {
+        messages.add(ValidationMessage(version.description));
+        final Version requiredVersion = _requiredBinaryVersions[kCmakeBinary];
+        if (version.number < requiredVersion) {
+          messages.add(ValidationMessage.error(_userMessages.cmakeTooOld(requiredVersion.toString())));
+        }
+      }
+    }
+
+    // Message for ninja.
+    {
+      final _VersionInfo version = installedVersions[kNinjaBinary];
+      if (version == null) {
+        messages.add(ValidationMessage.error(_userMessages.ninjaMissing));
+      } else {
+        // The full version description is just the number, so context.
+        messages.add(ValidationMessage(_userMessages.ninjaVersion(version.description)));
+        final Version requiredVersion = _requiredBinaryVersions[kNinjaBinary];
+        if (version.number < requiredVersion) {
+          messages.add(ValidationMessage.error(_userMessages.ninjaTooOld(requiredVersion.toString())));
+        }
+      }
+    }
+
+    return ValidationResult(validationType, messages);
+  }
+
+  /// Returns the installed version of [binary], or null if it's not installed.
+  ///
+  /// Requires tha [binary] take a '--version' flag, and print a version of the
+  /// form x.y.z somewhere on the first line of output.
+  Future<_VersionInfo> _getBinaryVersion(String binary) async {
+    ProcessResult result;
     try {
-      makeResult = await _processManager.run(const <String>[
-        'make',
+      result = await _processManager.run(<String>[
+        binary,
         '--version',
       ]);
     } on ArgumentError {
       // ignore error.
     }
-    if (makeResult == null || makeResult.exitCode != 0) {
-      validationType = ValidationType.missing;
-      messages.add(const ValidationMessage.error('make is not installed'));
-    } else {
-      final String firstLine = (makeResult.stdout as String).split('\n').first.trim();
-      messages.add(ValidationMessage(firstLine));
+    if (result == null || result.exitCode != 0) {
+      return null;
     }
-
-    return ValidationResult(validationType, messages);
+    final String firstLine = (result.stdout as String).split('\n').first.trim();
+    return _VersionInfo(firstLine);
   }
 }
diff --git a/packages/flutter_tools/lib/src/linux/makefile.dart b/packages/flutter_tools/lib/src/linux/makefile.dart
deleted file mode 100644
index 3876795..0000000
--- a/packages/flutter_tools/lib/src/linux/makefile.dart
+++ /dev/null
@@ -1,32 +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 '../base/file_system.dart';
-import '../project.dart';
-
-// The setting that controls the executable name in the linux makefile.
-const String _kBinaryNameVariable = 'BINARY_NAME=';
-
-/// Extracts the `BINARY_NAME` from a linux project Makefile.
-///
-/// Returns `null` if it cannot be found.
-String makefileExecutableName(LinuxProject project) {
-  // Support the binary name being set either in the Makefile, or in the
-  // separate configution include file used by the template.
-  final List<File> makeFiles = <File>[
-    project.makeFile.parent.childFile('app_configuration.mk'),
-    project.makeFile,
-  ];
-  for (final File file in makeFiles) {
-    if (!file.existsSync()) {
-      continue;
-    }
-    for (final String line in file.readAsLinesSync()) {
-      if (line.startsWith(_kBinaryNameVariable)) {
-        return line.split(_kBinaryNameVariable).last.trim();
-      }
-    }
-  }
-  return null;
-}
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart
index 4ed7421..b4124ae 100644
--- a/packages/flutter_tools/lib/src/plugins.dart
+++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -794,38 +794,17 @@
 }
 ''';
 
-const String _linuxPluginMakefileTemplate = '''
-# Plugins to include in the build.
-GENERATED_PLUGINS=\\
+const String _linuxPluginCmakefileTemplate = r'''
+list(APPEND FLUTTER_PLUGIN_LIST
 {{#plugins}}
-\t{{name}} \\
+  {{name}}
 {{/plugins}}
+)
 
-GENERATED_PLUGINS_DIR={{pluginsDir}}
-# A plugin library name plugin name with _plugin appended.
-GENERATED_PLUGIN_LIB_NAMES=\$(foreach plugin,\$(GENERATED_PLUGINS),\$(plugin)_plugin)
-
-# Variables for use in the enclosing Makefile. Changes to these names are
-# breaking changes.
-PLUGIN_TARGETS=\$(GENERATED_PLUGINS)
-PLUGIN_LIBRARIES=\$(foreach plugin,\$(GENERATED_PLUGIN_LIB_NAMES),\\
-\t\$(OUT_DIR)/lib\$(plugin).so)
-PLUGIN_LDFLAGS=\$(patsubst %,-l%,\$(GENERATED_PLUGIN_LIB_NAMES))
-PLUGIN_CPPFLAGS=\$(foreach plugin,\$(GENERATED_PLUGINS),\\
-\t-I\$(GENERATED_PLUGINS_DIR)/\$(plugin)/linux)
-
-# Targets
-
-# Implicit rules don't match phony targets, so list plugin builds explicitly.
-{{#plugins}}
-\$(OUT_DIR)/lib{{name}}_plugin.so: | {{name}}
-{{/plugins}}
-
-.PHONY: \$(GENERATED_PLUGINS)
-\$(GENERATED_PLUGINS):
-	make -C \$(GENERATED_PLUGINS_DIR)/\$@/linux \\
-		OUT_DIR=\$(OUT_DIR) \\
-		FLUTTER_EPHEMERAL_DIR="\$(abspath {{ephemeralDir}})"
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory({{pluginsDir}}/${plugin}/linux plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+endforeach(plugin)
 ''';
 
 Future<void> _writeIOSPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
@@ -871,31 +850,26 @@
 Future<void> _writeLinuxPluginFiles(FlutterProject project, List<Plugin> plugins) async {
   final List<Plugin>nativePlugins = _filterNativePlugins(plugins, LinuxPlugin.kConfigKey);
   final List<Map<String, dynamic>> linuxPlugins = _extractPlatformMaps(nativePlugins, LinuxPlugin.kConfigKey);
-  // The generated makefile is checked in, so can't use absolute paths. It is
-  // included by the main makefile, so relative paths must be relative to that
-  // file's directory.
-  final String makefileDirPath = project.linux.makeFile.parent.absolute.path;
+  // 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.linux.cmakeFile.parent.absolute.path;
   final Map<String, dynamic> context = <String, dynamic>{
     'plugins': linuxPlugins,
-    'ephemeralDir': globals.fs.path.relative(
-      project.linux.ephemeralDirectory.absolute.path,
-      from: makefileDirPath,
-    ),
     'pluginsDir': globals.fs.path.relative(
       project.linux.pluginSymlinkDirectory.absolute.path,
       from: makefileDirPath,
     ),
   };
   await _writeCppPluginRegistrant(project.linux.managedDirectory, context);
-  await _writeLinuxPluginMakefile(project.linux.managedDirectory, context);
+  await _writeLinuxPluginCmakefile(project.linux.generatedPluginCmakeFile, context);
 }
 
-Future<void> _writeLinuxPluginMakefile(Directory destination, Map<String, dynamic> templateContext) async {
-  final String registryDirectory = destination.path;
+Future<void> _writeLinuxPluginCmakefile(File destinationFile, Map<String, dynamic> templateContext) async {
   _renderTemplateToFile(
-    _linuxPluginMakefileTemplate,
+    _linuxPluginCmakefileTemplate,
     templateContext,
-    globals.fs.path.join(registryDirectory, 'generated_plugins.mk'),
+    destinationFile.path,
   );
 }
 
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 8ef4656..3070135 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -1068,15 +1068,15 @@
   @override
   bool existsSync() => _editableDirectory.existsSync();
 
-  /// The Linux project makefile.
-  File get makeFile => _editableDirectory.childFile('Makefile');
+  /// The Linux project CMake specification.
+  File get cmakeFile => _editableDirectory.childFile('CMakeLists.txt');
 
   /// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for
   /// the build.
-  File get generatedMakeConfigFile => ephemeralDirectory.childFile('generated_config.mk');
+  File get generatedCmakeConfigFile => ephemeralDirectory.childFile('generated_config.cmake');
 
-  /// Makefile with rules and variables for plugin builds.
-  File get generatedPluginMakeFile => managedDirectory.childFile('generated_plugins.mk');
+  /// Includable CMake with rules and variables for plugin builds.
+  File get generatedPluginCmakeFile => managedDirectory.childFile('generated_plugins.cmake');
 
   /// The directory to write plugin symlinks.
   Directory get pluginSymlinkDirectory => ephemeralDirectory.childDirectory('.plugin_symlinks');
diff --git a/packages/flutter_tools/templates/app/linux.tmpl/CMakeLists.txt.tmpl b/packages/flutter_tools/templates/app/linux.tmpl/CMakeLists.txt.tmpl
new file mode 100644
index 0000000..82a6975
--- /dev/null
+++ b/packages/flutter_tools/templates/app/linux.tmpl/CMakeLists.txt.tmpl
@@ -0,0 +1,75 @@
+cmake_minimum_required(VERSION 3.10)
+project(runner LANGUAGES CXX)
+
+set(BINARY_NAME "{{projectName}}")
+
+set(CMAKE_INSTALL_RPATH "\$ORIGIN")
+
+# Configure build options.
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+  set(CMAKE_BUILD_TYPE "Debug" CACHE
+    STRING "Flutter build mode" FORCE)
+  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+    "Debug" "Profile" "Release")
+endif()
+
+# Compilation settings that should be applied to most targets.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_14)
+  target_compile_options(${TARGET} PRIVATE -Wall -Werror)
+  target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
+endfunction()
+
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+
+# Flutter library and tool build rules.
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# Application build
+add_executable(${BINARY_NAME}
+  "main.cc"
+  "window_configuration.cc"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+)
+apply_standard_settings(${BINARY_NAME})
+target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
+add_dependencies(${BINARY_NAME} flutter_assemble)
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# By default, "installing" just makes a relocatable bundle in the build
+# directory.
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${PROJECT_BINARY_DIR}/bundle" CACHE PATH "..." FORCE)
+endif()
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+file(GLOB FLUTTER_PLUGIN_DIRS "${PROJECT_BINARY_DIR}/plugins/*")
+
+INSTALL(CODE "
+  include(BundleUtilities)
+  fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/${BINARY_NAME}\" \"\"
+    \"${FLUTTER_PLUGIN_DIRS};${FLUTTER_LIBRARY_DIR}\")
+  " COMPONENT Runtime)
+
+INSTALL(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+INSTALL(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+INSTALL(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
diff --git a/packages/flutter_tools/templates/app/linux.tmpl/Makefile b/packages/flutter_tools/templates/app/linux.tmpl/Makefile
deleted file mode 100644
index 2a3e5ba..0000000
--- a/packages/flutter_tools/templates/app/linux.tmpl/Makefile
+++ /dev/null
@@ -1,147 +0,0 @@
-include app_configuration.mk
-
-# Default build type.
-BUILD=debug
-
-FLUTTER_MANAGED_DIR=flutter
-FLUTTER_EPHEMERAL_DIR=$(FLUTTER_MANAGED_DIR)/ephemeral
-
-# Configuration provided via flutter tool.
-FLUTTER_CONFIG_FILE=$(FLUTTER_EPHEMERAL_DIR)/generated_config.mk
-include $(FLUTTER_CONFIG_FILE)
-
-# Dependency locations
-FLUTTER_APP_DIR=$(CURDIR)/..
-FLUTTER_APP_BUILD_DIR=$(FLUTTER_APP_DIR)/build
-
-OUT_DIR=$(FLUTTER_APP_BUILD_DIR)/linux
-OBJ_DIR=$(OUT_DIR)/obj/$(BUILD)
-
-# Libraries
-FLUTTER_LIB_NAME=flutter_linux_glfw
-FLUTTER_LIB=$(FLUTTER_EPHEMERAL_DIR)/lib$(FLUTTER_LIB_NAME).so
-
-# Tools
-FLUTTER_BIN=$(FLUTTER_ROOT)/bin/flutter
-LINUX_BUILD=$(FLUTTER_ROOT)/packages/flutter_tools/bin/tool_backend.sh
-
-# Resources
-ICU_DATA_NAME=icudtl.dat
-ICU_DATA_SOURCE=$(FLUTTER_EPHEMERAL_DIR)/$(ICU_DATA_NAME)
-FLUTTER_ASSETS_NAME=flutter_assets
-FLUTTER_ASSETS_SOURCE=$(FLUTTER_APP_BUILD_DIR)/$(FLUTTER_ASSETS_NAME)
-
-# Bundle structure
-BUNDLE_OUT_DIR=$(OUT_DIR)/$(BUILD)
-BUNDLE_DATA_DIR=$(BUNDLE_OUT_DIR)/data
-BUNDLE_LIB_DIR=$(BUNDLE_OUT_DIR)/lib
-
-BIN_OUT=$(BUNDLE_OUT_DIR)/$(BINARY_NAME)
-ICU_DATA_OUT=$(BUNDLE_DATA_DIR)/$(ICU_DATA_NAME)
-FLUTTER_LIB_OUT=$(BUNDLE_LIB_DIR)/$(notdir $(FLUTTER_LIB))
-ALL_LIBS_OUT=$(FLUTTER_LIB_OUT) \
-	$(foreach lib,$(EXTRA_BUNDLED_LIBRARIES),$(BUNDLE_LIB_DIR)/$(notdir $(lib)))
-
-# Add relevant code from the wrapper library, which is intended to be statically
-# built into the client.
-# Use abspath for the wrapper root, which can contain relative paths; the
-# intermediate build files will be based on the source path, which will cause
-# issues if they start with one or more '../'s.
-WRAPPER_ROOT=$(abspath $(FLUTTER_EPHEMERAL_DIR)/cpp_client_wrapper_glfw)
-WRAPPER_SOURCES= \
-	$(WRAPPER_ROOT)/flutter_engine.cc \
-	$(WRAPPER_ROOT)/flutter_window_controller.cc \
-	$(WRAPPER_ROOT)/plugin_registrar.cc \
-	$(WRAPPER_ROOT)/engine_method_result.cc
-
-# Use abspath for extra sources, which may also contain relative paths (see
-# note above about WRAPPER_ROOT).
-SOURCES=main.cc window_configuration.cc \
-	flutter/generated_plugin_registrant.cc \
-	$(WRAPPER_SOURCES) $(abspath $(EXTRA_SOURCES))
-
-# Headers
-WRAPPER_INCLUDE_DIR=$(WRAPPER_ROOT)/include
-INCLUDE_DIRS=$(FLUTTER_EPHEMERAL_DIR) $(WRAPPER_INCLUDE_DIR)
-
-# Build settings
-ifneq ($(strip $(SYSTEM_LIBRARIES)),)
-EXTRA_CPPFLAGS+=$(patsubst -I%,-isystem%,$(shell pkg-config --cflags $(SYSTEM_LIBRARIES)))
-EXTRA_LDFLAGS+=$(shell pkg-config --libs $(SYSTEM_LIBRARIES))
-endif
-CXX=clang++
-CPPFLAGS.release=-DNDEBUG
-CPPFLAGS.profile=$(CPPFLAGS.release)
-CXXFLAGS.release=-O2
-CXXFLAGS.profile=$(CXXFLAGS.release)
-CXXFLAGS=-std=c++14 -Wall -Werror $(CXXFLAGS.$(BUILD)) $(EXTRA_CXXFLAGS)
-CPPFLAGS=$(patsubst %,-I%,$(INCLUDE_DIRS)) \
-	$(CPPFLAGS.$(BUILD)) $(EXTRA_CPPFLAGS)
-LDFLAGS=-L$(BUNDLE_LIB_DIR) \
-	-l$(FLUTTER_LIB_NAME) \
-	$(EXTRA_LDFLAGS) \
-	-Wl,-rpath=\$$ORIGIN/lib
-
-# Intermediate files.
-OBJ_FILES=$(SOURCES:%.cc=$(OBJ_DIR)/%.o)
-DEPENDENCY_FILES=$(OBJ_FILES:%.o=%.d)
-
-# Targets
-
-.PHONY: all
-all: $(BIN_OUT) bundle
-
-# Add the plugin targets, and their associated settings.
-include $(FLUTTER_MANAGED_DIR)/generated_plugins.mk
-EXTRA_BUNDLED_LIBRARIES+=$(PLUGIN_LIBRARIES)
-EXTRA_LDFLAGS+=$(PLUGIN_LDFLAGS)
-EXTRA_CPPFLAGS+=$(PLUGIN_CPPFLAGS)
-
-# This is a phony target because the flutter tool cannot describe
-# its inputs and outputs yet.
-.PHONY: sync
-sync: $(FLUTTER_CONFIG_FILE)
-	$(LINUX_BUILD) linux-x64 $(BUILD)
-
-.PHONY: bundle
-bundle: $(ICU_DATA_OUT) $(ALL_LIBS_OUT) bundleflutterassets
-
-$(BIN_OUT): $(OBJ_FILES) $(ALL_LIBS_OUT)
-	mkdir -p $(@D)
-	$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(OBJ_FILES) $(LDFLAGS) -o $@
-
-$(WRAPPER_SOURCES) $(FLUTTER_LIB) $(ICU_DATA_SOURCE) $(FLUTTER_ASSETS_SOURCE) \
-	$(PLUGIN_TARGETS): | sync
-
-# Plugin library bundling pattern.
-$(BUNDLE_LIB_DIR)/%: $(OUT_DIR)/%
-	mkdir -p $(BUNDLE_LIB_DIR)
-	cp $< $@
-
-$(FLUTTER_LIB_OUT): $(FLUTTER_LIB)
-	mkdir -p $(@D)
-	cp $< $@
-
-$(ICU_DATA_OUT): $(ICU_DATA_SOURCE)
-	mkdir -p $(@D)
-	cp $< $@
-
--include $(DEPENDENCY_FILES)
-
-$(OBJ_DIR)/%.o : %.cc | sync
-	mkdir -p $(@D)
-	$(CXX) $(CXXFLAGS) $(CPPFLAGS) -MMD -c $< -o $@
-
-# Fully re-copy the assets directory on each build to avoid having to keep a
-# comprehensive list of all asset files here, which would be fragile to changes
-# in other files (e.g., adding a new font to pubspec.yaml).
-.PHONY: bundleflutterassets
-bundleflutterassets: $(FLUTTER_ASSETS_SOURCE)
-	mkdir -p $(BUNDLE_DATA_DIR)
-	rsync -rpu --delete $(FLUTTER_ASSETS_SOURCE) $(BUNDLE_DATA_DIR)
-
-.PHONY: clean
-clean:
-	rm -rf $(OUT_DIR); \
-	cd $(FLUTTER_APP_DIR); \
-	$(FLUTTER_BIN) clean
diff --git a/packages/flutter_tools/templates/app/linux.tmpl/app_configuration.mk.tmpl b/packages/flutter_tools/templates/app/linux.tmpl/app_configuration.mk.tmpl
deleted file mode 100644
index caf88d9..0000000
--- a/packages/flutter_tools/templates/app/linux.tmpl/app_configuration.mk.tmpl
+++ /dev/null
@@ -1,16 +0,0 @@
-# This file contains variables that applications are likely to need to
-# change, to isolate them from the main Makefile where the build rules are still
-# in flux. This should simplify re-creating the runner while preserving local
-# changes.
-
-# Executable name.
-BINARY_NAME={{projectName}}
-# Any extra source files to build.
-EXTRA_SOURCES=
-# Paths of any additional libraries to be bundled in the output directory.
-EXTRA_BUNDLED_LIBRARIES=
-# Extra flags (e.g., for library dependencies).
-SYSTEM_LIBRARIES=
-EXTRA_CXXFLAGS=
-EXTRA_CPPFLAGS=
-EXTRA_LDFLAGS=
\ No newline at end of file
diff --git a/packages/flutter_tools/templates/app/linux.tmpl/flutter/.template_version b/packages/flutter_tools/templates/app/linux.tmpl/flutter/.template_version
index 56a6051..d8263ee 100644
--- a/packages/flutter_tools/templates/app/linux.tmpl/flutter/.template_version
+++ b/packages/flutter_tools/templates/app/linux.tmpl/flutter/.template_version
@@ -1 +1 @@
-1
\ No newline at end of file
+2
\ No newline at end of file
diff --git a/packages/flutter_tools/templates/app/linux.tmpl/flutter/CMakeLists.txt b/packages/flutter_tools/templates/app/linux.tmpl/flutter/CMakeLists.txt
new file mode 100644
index 0000000..3102a79
--- /dev/null
+++ b/packages/flutter_tools/templates/app/linux.tmpl/flutter/CMakeLists.txt
@@ -0,0 +1,94 @@
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper_glfw")
+
+# === Flutter Library ===
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_glfw.so")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY_DIR "${EPHEMERAL_DIR}" PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "flutter_export.h"
+  "flutter_glfw.h"
+  "flutter_messenger.h"
+  "flutter_plugin_registrar.h"
+)
+list(TRANSFORM FLUTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
+add_dependencies(flutter flutter_assemble)
+
+# === Wrapper ===
+list(APPEND CPP_WRAPPER_SOURCES_CORE
+  "engine_method_result.cc"
+	"standard_codec.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
+  "plugin_registrar.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_APP
+  "flutter_engine.cc"
+  "flutter_window_controller.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
+
+# Wrapper sources needed for a plugin.
+add_library(flutter_wrapper_plugin STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+)
+apply_standard_settings(flutter_wrapper_plugin)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  POSITION_INDEPENDENT_CODE ON)
+target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
+target_include_directories(flutter_wrapper_plugin PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_plugin flutter_assemble)
+
+# Wrapper sources needed for the runner.
+add_library(flutter_wrapper_app STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
+apply_standard_settings(flutter_wrapper_app)
+target_link_libraries(flutter_wrapper_app PUBLIC flutter)
+target_include_directories(flutter_wrapper_app PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_app flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
+    ${CPP_WRAPPER_SOURCES_APP}
+    ${CMAKE_CURRENT_BINARY_DIR}/_phony_
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
+      linux-x64 ${CMAKE_BUILD_TYPE}
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
diff --git a/packages/flutter_tools/templates/plugin/linux.tmpl/CMakeLists.txt.tmpl b/packages/flutter_tools/templates/plugin/linux.tmpl/CMakeLists.txt.tmpl
new file mode 100644
index 0000000..65da6ad
--- /dev/null
+++ b/packages/flutter_tools/templates/plugin/linux.tmpl/CMakeLists.txt.tmpl
@@ -0,0 +1,13 @@
+cmake_minimum_required(VERSION 3.10)
+set(PROJECT_NAME "{{projectName}}")
+project(${PROJECT_NAME} LANGUAGES CXX)
+
+set(PLUGIN_NAME "${PROJECT_NAME}_plugin")
+
+add_library(${PLUGIN_NAME} SHARED
+  "${PLUGIN_NAME}.cc"
+)
+apply_standard_settings(${PLUGIN_NAME})
+target_include_directories(${PLUGIN_NAME} INTERFACE
+  "${CMAKE_CURRENT_SOURCE_DIR}/include")
+target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin)
diff --git a/packages/flutter_tools/templates/plugin/linux.tmpl/Makefile b/packages/flutter_tools/templates/plugin/linux.tmpl/Makefile
deleted file mode 100644
index 8a875fd..0000000
--- a/packages/flutter_tools/templates/plugin/linux.tmpl/Makefile
+++ /dev/null
@@ -1,83 +0,0 @@
-include plugin_configuration.mk
-
-# Default build type.
-BUILD=debug
-
-# Plugins must be provided a populated flutter/ephemeral/ dir. Normally this
-# would be the ephemeral directory of the application using the plugin.
-ifeq ($(strip $(FLUTTER_EPHEMERAL_DIR)),)
-$(error FLUTTER_EPHEMERAL_DIR must be provided)
-endif
-
-# Dependency locations
-# Default to building in the plugin directory.
-OUT_DIR=$(CURDIR)/../build/linux
-# Sharing an OUT_DIR will be common, so use a subdirectory for intermediates.
-PLUGIN_OUT_DIR=$(OUT_DIR)/$(PLUGIN_NAME)
-OBJ_DIR=$(PLUGIN_OUT_DIR)/obj/$(BUILD)
-
-# Flutter library
-FLUTTER_LIB_NAME=flutter_linux_glfw
-FLUTTER_LIB=$(FLUTTER_EPHEMERAL_DIR)/lib$(FLUTTER_LIB_NAME).so
-
-# Add relevant code from the wrapper library, which is intended to be statically
-# built into the plugin.
-# Use abspath for the wrapper root, which can contain relative paths; the
-# intermediate build files will be based on the source path, which will cause
-# issues if they start with one or more '../'s.
-WRAPPER_ROOT=$(abspath $(FLUTTER_EPHEMERAL_DIR)/cpp_client_wrapper_glfw)
-# TODO: Once JSON codec files are merged, make a PLUGIN_CODEC variable in the
-# top section. For now, using JSON codec would require changes here.
-WRAPPER_SOURCES= \
-	$(WRAPPER_ROOT)/engine_method_result.cc \
-	$(WRAPPER_ROOT)/plugin_registrar.cc \
-	$(WRAPPER_ROOT)/standard_codec.cc
-
-# Use abspath for extra sources, which may also contain relative paths (see
-# note above about WRAPPER_ROOT).
-SOURCES=$(PLUGIN_NAME)_plugin.cc $(WRAPPER_SOURCES) $(abspath $(EXTRA_SOURCES))
-
-WRAPPER_INCLUDE_DIR=$(WRAPPER_ROOT)/include
-INCLUDE_DIRS=$(FLUTTER_EPHEMERAL_DIR) $(WRAPPER_INCLUDE_DIR)
-
-# Build settings
-CXX=clang++
-CPPFLAGS.release=-DNDEBUG
-CPPFLAGS.profile=$(CPPFLAGS.release)
-CXXFLAGS.release=-O2
-CXXFLAGS.profile=$(CXXFLAGS.release)
-CXXFLAGS=-std=c++14 -Wall -Werror -fPIC -fvisibility=hidden \
-	$(CXXFLAGS.$(BUILD)) $(EXTRA_CXXFLAGS)
-CPPFLAGS=-DFLUTTER_PLUGIN_IMPL $(patsubst %,-I%,$(INCLUDE_DIRS)) \
-	$(CPPFLAGS.$(BUILD)) $(EXTRA_CPPFLAGS)
-LDFLAGS=-shared -L$(FLUTTER_EPHEMERAL_DIR) -l$(FLUTTER_LIB_NAME) $(EXTRA_LDFLAGS)
-
-# Final output files that will be used by applications.
-LIBRARY_OUT=$(OUT_DIR)/lib$(PLUGIN_NAME)_plugin.so
-
-# Intermediate files.
-OBJ_FILES=$(SOURCES:%.cc=$(OBJ_DIR)/%.o)
-DEPENDENCY_FILES=$(OBJ_FILES:%.o=%.d)
-
-# Targets
-
-.PHONY: all
-all: $(PLUGIN_NAME)
-
-.PHONY: $(PLUGIN_NAME)
-$(PLUGIN_NAME) : $(LIBRARY_OUT)
-
-$(LIBRARY_OUT): $(OBJ_FILES)
-	mkdir -p $(@D)
-	$(CXX) $(CXXFLAGS) $(CPPFLAGS) $^ $(LDFLAGS) -o $@
-
--include $(DEPENDENCY_FILES)
-
-$(OBJ_DIR)/%.o : %.cc
-	mkdir -p $(@D)
-	$(CXX) $(CXXFLAGS) $(CPPFLAGS) -MMD -c $< -o $@
-
-.PHONY: clean
-clean:
-	rm -f $(LIBRARY_OUT)
-	rm -rf $(PLUGINOUT_DIR)
diff --git a/packages/flutter_tools/templates/plugin/linux.tmpl/projectName_plugin.h.tmpl b/packages/flutter_tools/templates/plugin/linux.tmpl/include/projectName_plugin.h.tmpl
similarity index 100%
rename from packages/flutter_tools/templates/plugin/linux.tmpl/projectName_plugin.h.tmpl
rename to packages/flutter_tools/templates/plugin/linux.tmpl/include/projectName_plugin.h.tmpl
diff --git a/packages/flutter_tools/templates/plugin/linux.tmpl/plugin_configuration.mk.tmpl b/packages/flutter_tools/templates/plugin/linux.tmpl/plugin_configuration.mk.tmpl
deleted file mode 100644
index 3903f41..0000000
--- a/packages/flutter_tools/templates/plugin/linux.tmpl/plugin_configuration.mk.tmpl
+++ /dev/null
@@ -1,11 +0,0 @@
-# The name of the plugin.
-# The primary source file is assumed to be $(PLUGIN_NAME)_plugin.cc, with
-# $(PLUGIN_NAME)_plugin.h as the public header meant for inclusion by the
-# application.
-PLUGIN_NAME={{projectName}}
-# Any files other than the plugin class files that need to be compiled.
-EXTRA_SOURCES=
-# Extra flags (e.g., for library dependencies).
-EXTRA_CXXFLAGS=
-EXTRA_CPPFLAGS=
-EXTRA_LDFLAGS=
\ No newline at end of file
diff --git a/packages/flutter_tools/templates/plugin/linux.tmpl/projectName_plugin.cc.tmpl b/packages/flutter_tools/templates/plugin/linux.tmpl/projectName_plugin.cc.tmpl
index 5a0822a..dd3cfb8 100644
--- a/packages/flutter_tools/templates/plugin/linux.tmpl/projectName_plugin.cc.tmpl
+++ b/packages/flutter_tools/templates/plugin/linux.tmpl/projectName_plugin.cc.tmpl
@@ -1,4 +1,4 @@
-#include "{{projectName}}_plugin.h"
+#include "include/{{projectName}}_plugin.h"
 
 #include <flutter/method_channel.h>
 #include <flutter/plugin_registrar_glfw.h>
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart
index 11e2dc0..dd22f15 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart
@@ -7,11 +7,12 @@
 import 'package:file_testing/file_testing.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/base/utils.dart';
 import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/commands/build.dart';
 import 'package:flutter_tools/src/commands/build_linux.dart';
 import 'package:flutter_tools/src/features.dart';
-import 'package:flutter_tools/src/linux/makefile.dart';
+import 'package:flutter_tools/src/linux/cmake.dart';
 import 'package:flutter_tools/src/project.dart';
 import 'package:process/process.dart';
 
@@ -58,7 +59,7 @@
   // Creates the mock files necessary to run a build.
   void setUpMockProjectFilesForBuild({int templateVersion}) {
     setUpMockCoreProjectFiles();
-    fileSystem.file(fileSystem.path.join('linux', 'Makefile')).createSync(recursive: true);
+    fileSystem.file(fileSystem.path.join('linux', 'CMakeLists.txt')).createSync(recursive: true);
 
     final String versionFileSubpath = fileSystem.path.join('flutter', '.template_version');
     const int expectedTemplateVersion = 10;  // Arbitrary value for tests.
@@ -81,6 +82,30 @@
     projectTemplateVersionFile.writeAsStringSync(templateVersion.toString());
   }
 
+  // Returns the command list matching the build_linux call to cmake.
+  List<String> cmakeCommand(String buildMode) {
+    return <String>[
+      'cmake',
+      '-S',
+      '/linux',
+      '-B',
+      'build/linux/$buildMode',
+      '-G',
+      'Ninja',
+      '-DCMAKE_BUILD_TYPE=${toTitleCase(buildMode)}',
+    ];
+  }
+
+  // Returns the command list matching the build_linux call to ninja.
+  List<String> ninjaCommand(String buildMode) {
+    return <String>[
+      'ninja',
+      '-C',
+      'build/linux/$buildMode',
+      'install',
+    ];
+  }
+
   testUsingContext('Linux build fails when there is no linux project', () async {
     final BuildCommand command = BuildCommand();
     setUpMockCoreProjectFiles();
@@ -137,15 +162,11 @@
     FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
   });
 
-  testUsingContext('Linux build invokes make and writes temporary files', () async {
+  testUsingContext('Linux build invokes CMake and ninja, and writes temporary files', () async {
     final BuildCommand command = BuildCommand();
     processManager = FakeProcessManager.list(<FakeCommand>[
-      FakeCommand(command: const <String>[
-        'make',
-        '-C',
-        '/linux',
-        'BUILD=release',
-      ], onRun: () { })
+      FakeCommand(command: cmakeCommand('release')),
+      FakeCommand(command: ninjaCommand('release')),
     ]);
 
     setUpMockProjectFilesForBuild();
@@ -153,7 +174,7 @@
     await createTestCommandRunner(command).run(
       const <String>['build', 'linux', '--no-pub']
     );
-    expect(fileSystem.file('linux/flutter/ephemeral/generated_config.mk'), exists);
+    expect(fileSystem.file('linux/flutter/ephemeral/generated_config.cmake'), exists);
   }, overrides: <Type, Generator>{
     FileSystem: () => fileSystem,
     ProcessManager: () => processManager,
@@ -161,23 +182,38 @@
     FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
   });
 
-  testUsingContext('Handles argument error from missing make', () async {
+  testUsingContext('Handles argument error from missing cmake', () async {
     final BuildCommand command = BuildCommand();
     setUpMockProjectFilesForBuild();
     processManager = FakeProcessManager.list(<FakeCommand>[
-      FakeCommand(command: const <String>[
-        'make',
-        '-C',
-        '/linux',
-        'BUILD=release',
-      ], onRun: () {
+      FakeCommand(command: cmakeCommand('release'), onRun: () {
         throw ArgumentError();
       }),
     ]);
 
     expect(createTestCommandRunner(command).run(
       const <String>['build', 'linux', '--no-pub']
-    ), throwsToolExit(message: "make not found. Run 'flutter doctor' for more information."));
+    ), throwsToolExit(message: "cmake not found. Run 'flutter doctor' for more information."));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+    Platform: () => linuxPlatform,
+    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
+  });
+
+  testUsingContext('Handles argument error from missing ninja', () async {
+    final BuildCommand command = BuildCommand();
+    setUpMockProjectFilesForBuild();
+    processManager = FakeProcessManager.list(<FakeCommand>[
+      FakeCommand(command: cmakeCommand('release')),
+      FakeCommand(command: ninjaCommand('release'), onRun: () {
+        throw ArgumentError();
+      }),
+    ]);
+
+    expect(createTestCommandRunner(command).run(
+      const <String>['build', 'linux', '--no-pub']
+    ), throwsToolExit(message: "ninja not found. Run 'flutter doctor' for more information."));
   }, overrides: <Type, Generator>{
     FileSystem: () => fileSystem,
     ProcessManager: () => processManager,
@@ -189,12 +225,10 @@
     final BuildCommand command = BuildCommand();
     setUpMockProjectFilesForBuild();
     processManager = FakeProcessManager.list(<FakeCommand>[
-      const FakeCommand(command: <String>[
-        'make',
-        '-C',
-        '/linux',
-        'BUILD=debug',
-      ], stdout: 'STDOUT STUFF'),
+      FakeCommand(command: cmakeCommand('debug')),
+      FakeCommand(
+        command: ninjaCommand('debug'),
+        stdout: 'STDOUT STUFF',),
     ]);
 
     await createTestCommandRunner(command).run(
@@ -213,14 +247,10 @@
     final BuildCommand command = BuildCommand();
     setUpMockProjectFilesForBuild();
     processManager = FakeProcessManager.list(<FakeCommand>[
-      const FakeCommand(
-        command: <String>[
-          'make',
-          '-C',
-          '/linux',
-          'BUILD=debug',
-        ],
-        environment: <String, String>{
+      FakeCommand(command: cmakeCommand('debug')),
+      FakeCommand(
+        command: ninjaCommand('debug'),
+        environment: const <String, String>{
           'VERBOSE_SCRIPT_LOGGING': 'true'
         },
         stdout: 'STDOUT STUFF',
@@ -239,16 +269,12 @@
     FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
   });
 
-  testUsingContext('Linux build --debug passes debug mode to make', () async {
+  testUsingContext('Linux build --debug passes debug mode to cmake and ninja', () async {
     final BuildCommand command = BuildCommand();
     setUpMockProjectFilesForBuild();
     processManager = FakeProcessManager.list(<FakeCommand>[
-      const FakeCommand(command: <String>[
-        'make',
-        '-C',
-        '/linux',
-        'BUILD=debug',
-      ]),
+      FakeCommand(command: cmakeCommand('debug')),
+      FakeCommand(command: ninjaCommand('debug')),
     ]);
 
 
@@ -266,12 +292,8 @@
     final BuildCommand command = BuildCommand();
     setUpMockProjectFilesForBuild();
     processManager = FakeProcessManager.list(<FakeCommand>[
-      const FakeCommand(command: <String>[
-        'make',
-        '-C',
-        '/linux',
-        'BUILD=profile',
-      ]),
+      FakeCommand(command: cmakeCommand('profile')),
+      FakeCommand(command: ninjaCommand('profile')),
     ]);
 
     await createTestCommandRunner(command).run(
@@ -288,12 +310,8 @@
     final BuildCommand command = BuildCommand();
     setUpMockProjectFilesForBuild();
     processManager = FakeProcessManager.list(<FakeCommand>[
-      const FakeCommand(command: <String>[
-        'make',
-        '-C',
-        '/linux',
-        'BUILD=release',
-      ]),
+      FakeCommand(command: cmakeCommand('release')),
+      FakeCommand(command: ninjaCommand('release')),
     ]);
     fileSystem.file('lib/other.dart')
       .createSync(recursive: true);
@@ -314,26 +332,29 @@
       ]
     );
 
-    final File makeConfig = fileSystem.currentDirectory
+    final File cmakeConfig = fileSystem.currentDirectory
       .childDirectory('linux')
       .childDirectory('flutter')
       .childDirectory('ephemeral')
-      .childFile('generated_config.mk');
+      .childFile('generated_config.cmake');
 
-    expect(makeConfig, exists);
+    expect(cmakeConfig, exists);
 
-    final List<String> configLines = makeConfig.readAsLinesSync();
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
 
     expect(configLines, containsAll(<String>[
-      'export DART_DEFINES=foo.bar=2,fizz.far=3',
-      'export DART_OBFUSCATION=true',
-      'export EXTRA_FRONT_END_OPTIONS=--enable-experiment=non-nullable',
-      'export EXTRA_GEN_SNAPSHOT_OPTIONS=--enable-experiment=non-nullable',
-      'export SPLIT_DEBUG_INFO=foo/',
-      'export TRACK_WIDGET_CREATION=true',
-      'export TREE_SHAKE_ICONS=true',
-      'export FLUTTER_ROOT=$_kTestFlutterRoot',
-      'export FLUTTER_TARGET=lib/other.dart',
+      'set(FLUTTER_ROOT "$_kTestFlutterRoot")',
+      'set(PROJECT_DIR "${fileSystem.currentDirectory.path}")',
+      '  "DART_DEFINES=\\"foo.bar=2,fizz.far=3\\""',
+      '  "DART_OBFUSCATION=\\"true\\""',
+      '  "EXTRA_FRONT_END_OPTIONS=\\"--enable-experiment=non-nullable\\""',
+      '  "EXTRA_GEN_SNAPSHOT_OPTIONS=\\"--enable-experiment=non-nullable\\""',
+      '  "SPLIT_DEBUG_INFO=\\"foo/\\""',
+      '  "TRACK_WIDGET_CREATION=\\"true\\""',
+      '  "TREE_SHAKE_ICONS=\\"true\\""',
+      '  "FLUTTER_ROOT=\\"\${FLUTTER_ROOT}\\""',
+      '  "PROJECT_DIR=\\"\${PROJECT_DIR}\\""',
+      '  "FLUTTER_TARGET=\\"lib/other.dart\\""',
     ]));
   }, overrides: <Type, Generator>{
     FileSystem: () => fileSystem,
@@ -342,45 +363,20 @@
     FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
   });
 
-  testUsingContext('linux can extract binary name from Makefile', () async {
-    fileSystem.file('linux/Makefile')
+  testUsingContext('linux can extract binary name from CMake file', () async {
+    fileSystem.file('linux/CMakeLists.txt')
       ..createSync(recursive: true)
       ..writeAsStringSync(r'''
-# Comment
-SOMETHING_ELSE=FOO
-BINARY_NAME=fizz_bar
+cmake_minimum_required(VERSION 3.10)
+project(runner LANGUAGES CXX)
+
+set(BINARY_NAME "fizz_bar")
 ''');
     fileSystem.file('pubspec.yaml').createSync();
     fileSystem.file('.packages').createSync();
     final FlutterProject flutterProject = FlutterProject.current();
 
-    expect(makefileExecutableName(flutterProject.linux), 'fizz_bar');
-  }, overrides: <Type, Generator>{
-    FileSystem: () => fileSystem,
-    ProcessManager: () => FakeProcessManager.any(),
-    FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
-  });
-
-  testUsingContext('linux can extract binary name from app config', () async {
-    fileSystem.file('linux/Makefile')
-      ..createSync(recursive: true)
-      ..writeAsStringSync(r'''
-# Comment
-SOMETHING_ELSE=FOO
-include app_configuration.mk
-''');
-    fileSystem.file('linux/app_configuration.mk')
-      ..createSync(recursive: true)
-      ..writeAsStringSync(r'''
-# Comment
-SOMETHING_ELSE=FOO
-BINARY_NAME=fizz_bar
-''');
-    fileSystem.file('pubspec.yaml').createSync();
-    fileSystem.file('.packages').createSync();
-    final FlutterProject flutterProject = FlutterProject.current();
-
-    expect(makefileExecutableName(flutterProject.linux), 'fizz_bar');
+    expect(getCmakeExecutableName(flutterProject.linux), 'fizz_bar');
   }, overrides: <Type, Generator>{
     FileSystem: () => fileSystem,
     ProcessManager: () => FakeProcessManager.any(),
@@ -400,12 +396,8 @@
     final BuildCommand command = BuildCommand();
     setUpMockProjectFilesForBuild();
     processManager = FakeProcessManager.list(<FakeCommand>[
-      const FakeCommand(command: <String>[
-        'make',
-        '-C',
-        '/linux',
-        'BUILD=release',
-      ]),
+      FakeCommand(command: cmakeCommand('release')),
+      FakeCommand(command: ninjaCommand('release')),
     ]);
 
     await createTestCommandRunner(command).run(
diff --git a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
index 6b63f74..db5f090 100644
--- a/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
+++ b/packages/flutter_tools/test/commands.shard/permeable/create_test.dart
@@ -634,7 +634,7 @@
 
     await runner.run(<String>['create', '--no-pub', projectDir.path]);
 
-    expect(projectDir.childDirectory('linux').childFile('Makefile').existsSync(), true);
+    expect(projectDir.childDirectory('linux').childFile('CMakeLists.txt').existsSync(), true);
   }, overrides: <Type, Generator>{
     FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
   });
@@ -649,7 +649,7 @@
 
     await runner.run(<String>['create', '--no-pub', projectDir.path]);
 
-    expect(projectDir.childDirectory('linux').childFile('Makefile').existsSync(), false);
+    expect(projectDir.childDirectory('linux').childFile('CMakeLists.txt').existsSync(), false);
   }, overrides: <Type, Generator>{
     FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: false),
   });
@@ -664,7 +664,7 @@
 
     await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
 
-    expect(projectDir.childDirectory('linux').childFile('Makefile').existsSync(), true);
+    expect(projectDir.childDirectory('linux').childFile('CMakeLists.txt').existsSync(), true);
   }, overrides: <Type, Generator>{
     FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
   });
@@ -679,7 +679,7 @@
 
     await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
 
-    expect(projectDir.childDirectory('linux').childFile('Makefile').existsSync(), false);
+    expect(projectDir.childDirectory('linux').childFile('CMakeLists.txt').existsSync(), false);
   }, overrides: <Type, Generator>{
     FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: false),
   });
diff --git a/packages/flutter_tools/test/general.shard/linux/linux_doctor_test.dart b/packages/flutter_tools/test/general.shard/linux/linux_doctor_test.dart
index 63af2e3..5fb50a0 100644
--- a/packages/flutter_tools/test/general.shard/linux/linux_doctor_test.dart
+++ b/packages/flutter_tools/test/general.shard/linux/linux_doctor_test.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flutter_tools/src/base/user_messages.dart';
 import 'package:flutter_tools/src/doctor.dart';
 import 'package:flutter_tools/src/linux/linux_doctor.dart';
 import 'package:process/process.dart';
@@ -9,112 +10,206 @@
 import '../../src/common.dart';
 import '../../src/context.dart';
 
+// A command that will return typical-looking 'clang++ --version' output with
+// the given version number.
+FakeCommand _clangPresentCommand(String version) {
+  return FakeCommand(
+    command: const <String>['clang++', '--version'],
+    stdout: '''
+clang version $version-6+build1
+Target: x86_64-pc-linux-gnu
+Thread model: posix
+InstalledDir: /usr/bin
+''',
+  );
+}
+
+// A command that will return typical-looking 'cmake --version' output with the
+// given version number.
+FakeCommand _cmakePresentCommand(String version) {
+  return FakeCommand(
+    command: const <String>['cmake', '--version'],
+    stdout: '''
+cmake version $version
+
+CMake suite maintained and supported by Kitware (kitware.com/cmake).
+''',
+  );
+}
+
+// A command that will return typical-looking 'ninja --version' output with the
+// given version number.
+FakeCommand _ninjaPresentCommand(String version) {
+  return FakeCommand(
+    command: const <String>['ninja', '--version'],
+    stdout: version,
+  );
+}
+
+// A command that will failure when running '[binary] --version'.
+FakeCommand _missingBinaryCommand(String binary) {
+  return FakeCommand(
+    command: <String>[binary, '--version'],
+    exitCode: 1,
+  );
+}
+
 void main() {
-  testWithoutContext('Full validation when clang++ and Make are available',() async {
+  testWithoutContext('Full validation when everything is available at the necessary version',() async {
     final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
-      const FakeCommand(
-        command: <String>['clang++', '--version'],
-        stdout: 'clang version 4.0.1-10 (tags/RELEASE_401/final)\njunk',
-      ),
-      const FakeCommand(
-        command: <String>['make', '--version'],
-        stdout: 'GNU Make 4.1\njunk',
-      ),
+      _clangPresentCommand('4.0.1'),
+      _cmakePresentCommand('3.16.3'),
+      _ninjaPresentCommand('1.10.0'),
     ]);
     final DoctorValidator linuxDoctorValidator = LinuxDoctorValidator(
       processManager: processManager,
+      userMessages: UserMessages(),
     );
     final ValidationResult result = await linuxDoctorValidator.validate();
 
     expect(result.type, ValidationType.installed);
     expect(result.messages, const <ValidationMessage>[
-      ValidationMessage('clang++ 4.0.1'),
-      ValidationMessage('GNU Make 4.1'),
+      ValidationMessage('clang version 4.0.1-6+build1'),
+      ValidationMessage('cmake version 3.16.3'),
+      ValidationMessage('ninja version 1.10.0'),
     ]);
   });
 
   testWithoutContext('Partial validation when clang++ version is too old', () async {
     final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
-      const FakeCommand(
-        command: <String>['clang++', '--version'],
-        stdout: 'clang version 2.0.1-10 (tags/RELEASE_401/final)\njunk',
-      ),
-      const FakeCommand(
-        command: <String>['make', '--version'],
-        stdout: 'GNU Make 4.1\njunk',
-      ),
+      _clangPresentCommand('2.0.1'),
+      _cmakePresentCommand('3.16.3'),
+      _ninjaPresentCommand('1.10.0'),
     ]);
     final DoctorValidator linuxDoctorValidator = LinuxDoctorValidator(
       processManager: processManager,
+      userMessages: UserMessages(),
     );
     final ValidationResult result = await linuxDoctorValidator.validate();
 
     expect(result.type, ValidationType.partial);
     expect(result.messages, const <ValidationMessage>[
-      ValidationMessage.error('clang++ 2.0.1 is below minimum version of 3.4.0'),
-      ValidationMessage('GNU Make 4.1'),
+      ValidationMessage('clang version 2.0.1-6+build1'),
+      ValidationMessage.error('clang++ 3.4.0 or later is required.'),
+      ValidationMessage('cmake version 3.16.3'),
+      ValidationMessage('ninja version 1.10.0'),
     ]);
   });
 
-  testWithoutContext('Missing validation when Make is not available', () async {
+  testWithoutContext('Partial validation when CMake version is too old', () async {
     final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
-      const FakeCommand(
-        command: <String>['clang++', '--version'],
-        stdout: 'clang version 4.0.1-10 (tags/RELEASE_401/final)\njunk',
-      ),
-      const FakeCommand(
-        command: <String>['make', '--version'],
-        exitCode: 1,
-      ),
+      _clangPresentCommand('4.0.1'),
+      _cmakePresentCommand('3.2.0'),
+      _ninjaPresentCommand('1.10.0'),
     ]);
     final DoctorValidator linuxDoctorValidator = LinuxDoctorValidator(
       processManager: processManager,
+      userMessages: UserMessages(),
+    );
+    final ValidationResult result = await linuxDoctorValidator.validate();
+
+    expect(result.type, ValidationType.partial);
+    expect(result.messages, const <ValidationMessage>[
+      ValidationMessage('clang version 4.0.1-6+build1'),
+      ValidationMessage('cmake version 3.2.0'),
+      ValidationMessage.error('cmake 3.10.0 or later is required.'),
+      ValidationMessage('ninja version 1.10.0'),
+    ]);
+  });
+
+  testWithoutContext('Partial validation when ninja version is too old', () async {
+    final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
+      _clangPresentCommand('4.0.1'),
+      _cmakePresentCommand('3.16.3'),
+      _ninjaPresentCommand('0.8.1'),
+    ]);
+    final DoctorValidator linuxDoctorValidator = LinuxDoctorValidator(
+      processManager: processManager,
+      userMessages: UserMessages(),
+    );
+    final ValidationResult result = await linuxDoctorValidator.validate();
+
+    expect(result.type, ValidationType.partial);
+    expect(result.messages, const <ValidationMessage>[
+      ValidationMessage('clang version 4.0.1-6+build1'),
+      ValidationMessage('cmake version 3.16.3'),
+      ValidationMessage('ninja version 0.8.1'),
+      ValidationMessage.error('ninja 1.8.0 or later is required.'),
+    ]);
+  });
+
+  testWithoutContext('Missing validation when CMake is not available', () async {
+    final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
+      _clangPresentCommand('4.0.1'),
+      _missingBinaryCommand('cmake'),
+      _ninjaPresentCommand('1.10.0'),
+    ]);
+    final UserMessages userMessages = UserMessages();
+    final DoctorValidator linuxDoctorValidator = LinuxDoctorValidator(
+      processManager: processManager,
+      userMessages: userMessages,
     );
     final ValidationResult result = await linuxDoctorValidator.validate();
 
     expect(result.type, ValidationType.missing);
-    expect(result.messages, const <ValidationMessage>[
-      ValidationMessage('clang++ 4.0.1'),
-      ValidationMessage.error('make is not installed'),
+    expect(result.messages, <ValidationMessage>[
+      const ValidationMessage('clang version 4.0.1-6+build1'),
+      ValidationMessage.error(userMessages.cmakeMissing),
+      const ValidationMessage('ninja version 1.10.0'),
     ]);
   });
 
   testWithoutContext('Missing validation when clang++ is not available', () async {
     final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
-      const FakeCommand(
-        command: <String>['clang++', '--version'],
-        exitCode: 1
-      ),
-      const FakeCommand(
-        command: <String>['make', '--version'],
-        stdout: 'GNU Make 4.1\njunk'
-      ),
+      _missingBinaryCommand('clang++'),
+      _cmakePresentCommand('3.16.3'),
+      _ninjaPresentCommand('1.10.0'),
     ]);
+    final UserMessages userMessages = UserMessages();
     final DoctorValidator linuxDoctorValidator = LinuxDoctorValidator(
       processManager: processManager,
+      userMessages: userMessages,
     );
     final ValidationResult result = await linuxDoctorValidator.validate();
 
     expect(result.type, ValidationType.missing);
-    expect(result.messages, const <ValidationMessage>[
-      ValidationMessage.error('clang++ is not installed'),
-      ValidationMessage('GNU Make 4.1'),
+    expect(result.messages, <ValidationMessage>[
+      ValidationMessage.error(userMessages.clangMissing),
+      const ValidationMessage('cmake version 3.16.3'),
+      const ValidationMessage('ninja version 1.10.0'),
     ]);
   });
 
-  testWithoutContext('Missing validation when clang++ and Make are not available', () async {
+  testWithoutContext('Missing validation when ninja is not available', () async {
     final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
-      const FakeCommand(
-        command: <String>['clang++', '--version'],
-        exitCode: 1,
-      ),
-      const FakeCommand(
-        command: <String>['make', '--version'],
-        exitCode: 1,
-      ),
+      _clangPresentCommand('4.0.1'),
+      _cmakePresentCommand('3.16.3'),
+      _missingBinaryCommand('ninja'),
+    ]);
+    final UserMessages userMessages = UserMessages();
+    final DoctorValidator linuxDoctorValidator = LinuxDoctorValidator(
+      processManager: processManager,
+      userMessages: userMessages,
+    );
+    final ValidationResult result = await linuxDoctorValidator.validate();
+
+    expect(result.type, ValidationType.missing);
+    expect(result.messages, <ValidationMessage>[
+      const ValidationMessage('clang version 4.0.1-6+build1'),
+      const ValidationMessage('cmake version 3.16.3'),
+      ValidationMessage.error(userMessages.ninjaMissing),
+    ]);
+  });
+
+  testWithoutContext('Missing validation when multiple dependencies are not available', () async {
+    final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
+      _missingBinaryCommand('clang++'),
+      _missingBinaryCommand('cmake'),
+      _ninjaPresentCommand('1.10.0'),
     ]);
     final DoctorValidator linuxDoctorValidator = LinuxDoctorValidator(
       processManager: processManager,
+      userMessages: UserMessages(),
     );
 
     final ValidationResult result = await linuxDoctorValidator.validate();
diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart
index 709ac06..4c2aecd 100644
--- a/packages/flutter_tools/test/general.shard/plugins_test.dart
+++ b/packages/flutter_tools/test/general.shard/plugins_test.dart
@@ -91,8 +91,8 @@
       when(linuxProject.managedDirectory).thenReturn(linuxManagedDirectory);
       when(linuxProject.ephemeralDirectory).thenReturn(linuxEphemeralDirectory);
       when(linuxProject.pluginSymlinkDirectory).thenReturn(linuxEphemeralDirectory.childDirectory('.plugin_symlinks'));
-      when(linuxProject.makeFile).thenReturn(linuxManagedDirectory.parent.childFile('Makefile'));
-      when(linuxProject.generatedPluginMakeFile).thenReturn(linuxManagedDirectory.childFile('generated_plugins.mk'));
+      when(linuxProject.cmakeFile).thenReturn(linuxManagedDirectory.parent.childFile('CMakeLists.txt'));
+      when(linuxProject.generatedPluginCmakeFile).thenReturn(linuxManagedDirectory.childFile('generated_plugins.mk'));
       when(linuxProject.existsSync()).thenReturn(false);
 
       when(mockClock.now()).thenAnswer(
@@ -961,7 +961,7 @@
         FeatureFlags: () => featureFlags,
       });
 
-      testUsingContext('Injecting creates generated Linux plugin makefile', () async {
+      testUsingContext('Injecting creates generated Linux plugin Cmake file', () async {
         when(linuxProject.existsSync()).thenReturn(true);
         when(featureFlags.isLinuxEnabled).thenReturn(true);
         when(flutterProject.isModule).thenReturn(false);
@@ -969,16 +969,12 @@
 
         await injectPlugins(flutterProject, checkProjects: true);
 
-        final File pluginMakefile = linuxProject.generatedPluginMakeFile;
+        final File pluginMakefile = linuxProject.generatedPluginCmakeFile;
 
         expect(pluginMakefile.existsSync(), isTrue);
         final String contents = pluginMakefile.readAsStringSync();
-        expect(contents, contains('libapackage_plugin.so'));
-        // Verify all the variables the app-level Makefile rely on.
-        expect(contents, contains('PLUGIN_TARGETS='));
-        expect(contents, contains('PLUGIN_LIBRARIES='));
-        expect(contents, contains('PLUGIN_LDFLAGS='));
-        expect(contents, contains('PLUGIN_CPPFLAGS='));
+        expect(contents, contains('apackage'));
+        expect(contents, contains('target_link_libraries(\${BINARY_NAME} PRIVATE \${plugin}_plugin)'));
       }, overrides: <Type, Generator>{
         FileSystem: () => fs,
         ProcessManager: () => FakeProcessManager.any(),