created a linter that runs clang-tidy (#18607)

diff --git a/.cirrus.yml b/.cirrus.yml
index 6f37dab..1a6720e 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -146,3 +146,7 @@
       build_script: |
         cd $ENGINE_PATH/src/flutter
         ./ci/build.sh
+    - name: lint_test
+      lint_script: |
+        cd $ENGINE_PATH/src
+        ./flutter/ci/lint.sh
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000..fe6d3fc
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1 @@
+Checks: 'google-*'
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index be25344..fff517b 100755
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -10,6 +10,7 @@
 LIBRARY: txt
 ORIGIN: ../../../flutter/LICENSE
 TYPE: LicenseType.bsd
+FILE: ../../../flutter/.clang-tidy
 FILE: ../../../flutter/DEPS
 FILE: ../../../flutter/assets/asset_manager.cc
 FILE: ../../../flutter/assets/asset_manager.h
diff --git a/ci/lint.dart b/ci/lint.dart
new file mode 100644
index 0000000..adff165
--- /dev/null
+++ b/ci/lint.dart
@@ -0,0 +1,127 @@
+/// Runs clang-tidy on files with changes.
+///
+/// usage:
+/// dart lint.dart <path to compile_commands.json> <path to git repository> [clang-tidy checks]
+///
+/// User environment variable FLUTTER_LINT_ALL to run on all files.
+
+import 'dart:io'
+    show
+        File,
+        Process,
+        ProcessResult,
+        exit,
+        Directory,
+        FileSystemEntity,
+        Platform;
+import 'dart:convert' show jsonDecode, utf8;
+import 'dart:async' show Completer;
+
+class Command {
+  String directory;
+  String command;
+  String file;
+}
+
+Command parseCommand(Map<String, dynamic> map) {
+  return Command()
+    ..directory = map['directory']
+    ..command = map['command']
+    ..file = map['file'];
+}
+
+String calcTidyArgs(Command command) {
+  String result = command.command;
+  result = result.replaceAll(RegExp(r'\S*clang/bin/clang'), '');
+  result = result.replaceAll(RegExp(r'-MF \S*'), '');
+  return result;
+}
+
+String calcTidyPath(Command command) {
+  final RegExp regex = RegExp(r'\S*clang/bin/clang');
+  return regex
+      .stringMatch(command.command)
+      .replaceAll('clang/bin/clang', 'clang/bin/clang-tidy');
+}
+
+bool isNonEmptyString(String str) => str.length > 0;
+
+bool containsAny(String str, List<String> queries) {
+  for (String query in queries) {
+    if (str.contains(query)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+/// Returns a list of all files with current changes or differ from `master`.
+List<String> getListOfChangedFiles(String repoPath) {
+  final Set<String> result = Set<String>();
+  final ProcessResult diffResult = Process.runSync(
+      'git', ['diff', '--name-only'],
+      workingDirectory: repoPath);
+  final ProcessResult diffCachedResult = Process.runSync(
+      'git', ['diff', '--cached', '--name-only'],
+      workingDirectory: repoPath);
+
+  final ProcessResult mergeBaseResult = Process.runSync(
+      'git', ['merge-base', 'master', 'HEAD'],
+      workingDirectory: repoPath);
+  final String mergeBase = mergeBaseResult.stdout.trim();
+  final ProcessResult masterResult = Process.runSync(
+      'git', ['diff', '--name-only', mergeBase],
+      workingDirectory: repoPath);
+  result.addAll(diffResult.stdout.split('\n').where(isNonEmptyString));
+  result.addAll(diffCachedResult.stdout.split('\n').where(isNonEmptyString));
+  result.addAll(masterResult.stdout.split('\n').where(isNonEmptyString));
+  return result.toList();
+}
+
+Future<List<String>> dirContents(String repoPath) {
+  Directory dir = Directory(repoPath);
+  var files = <String>[];
+  var completer = new Completer<List<String>>();
+  var lister = dir.list(recursive: true);
+  lister.listen((FileSystemEntity file) => files.add(file.path),
+      // should also register onError
+      onDone: () => completer.complete(files));
+  return completer.future;
+}
+
+void main(List<String> arguments) async {
+  final String buildCommandsPath = arguments[0];
+  final String repoPath = arguments[1];
+  final String checks =
+      arguments.length >= 3 ? '--checks=${arguments[2]}' : '--config=';
+  final List<String> changedFiles =
+      Platform.environment['FLUTTER_LINT_ALL'] != null
+          ? await dirContents(repoPath)
+          : getListOfChangedFiles(repoPath);
+
+  final List<dynamic> buildCommandMaps =
+      jsonDecode(await new File(buildCommandsPath).readAsString());
+  final List<Command> buildCommands =
+      buildCommandMaps.map((x) => parseCommand(x)).toList();
+  final Command firstCommand = buildCommands[0];
+  final String tidyPath = calcTidyPath(firstCommand);
+  final List<Command> changedFileBuildCommands =
+      buildCommands.where((x) => containsAny(x.file, changedFiles)).toList();
+
+  int exitCode = 0;
+  //TODO(aaclarke): Coalesce this into one call using the `-p` arguement.
+  for (Command command in changedFileBuildCommands) {
+    final String tidyArgs = calcTidyArgs(command);
+    final List<String> args = [command.file, checks, '--'];
+    args.addAll(tidyArgs.split(' '));
+    print('# linting ${command.file}');
+    final Process process = await Process.start(tidyPath, args,
+        workingDirectory: command.directory, runInShell: false);
+    process.stdout.transform(utf8.decoder).listen((data) {
+      print(data);
+      exitCode = 1;
+    });
+    await process.exitCode;
+  }
+  exit(exitCode);
+}
diff --git a/ci/lint.sh b/ci/lint.sh
new file mode 100755
index 0000000..023d982
--- /dev/null
+++ b/ci/lint.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+set -e
+
+COMPILE_COMMANDS="out/compile_commands.json"
+if [ ! -f $COMPILE_COMMANDS ]; then
+  ./flutter/tools/gn
+fi
+
+dart flutter/ci/lint.dart $COMPILE_COMMANDS flutter/