| // Copyright 2013 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 'dart:io' as io; |
| |
| import 'package:args/command_runner.dart'; |
| import 'package:clang_tidy/clang_tidy.dart'; |
| import 'package:path/path.dart' as path; |
| |
| /// The command that implements the pre-push githook |
| class PrePushCommand extends Command<bool> { |
| @override |
| final String name = 'pre-push'; |
| |
| @override |
| final String description = 'Checks to run before a "git push"'; |
| |
| @override |
| Future<bool> run() async { |
| final Stopwatch sw = Stopwatch()..start(); |
| final bool verbose = globalResults!['verbose']! as bool; |
| final bool enableClangTidy = globalResults!['enable-clang-tidy']! as bool; |
| final String flutterRoot = globalResults!['flutter']! as String; |
| |
| if (!enableClangTidy) { |
| print('The clang-tidy check is disabled. To enable set the environment ' |
| 'variable PRE_PUSH_CLANG_TIDY to any value.'); |
| } |
| |
| final List<bool> checkResults = <bool>[ |
| await _runFormatter(flutterRoot, verbose), |
| if (enableClangTidy) |
| await _runClangTidy(flutterRoot, verbose), |
| ]; |
| sw.stop(); |
| io.stdout.writeln('pre-push checks finished in ${sw.elapsed}'); |
| return !checkResults.contains(false); |
| } |
| |
| Future<bool> _runClangTidy(String flutterRoot, bool verbose) async { |
| io.stdout.writeln('Starting clang-tidy checks.'); |
| final Stopwatch sw = Stopwatch()..start(); |
| // First ensure that out/host_debug/compile_commands.json exists by running |
| // //flutter/tools/gn. |
| io.File compileCommands = io.File(path.join( |
| flutterRoot, |
| '..', |
| 'out', |
| 'host_debug', |
| 'compile_commands.json', |
| )); |
| if (!compileCommands.existsSync()) { |
| compileCommands = io.File(path.join( |
| flutterRoot, |
| '..', |
| 'out', |
| 'host_debug_unopt', |
| 'compile_commands.json', |
| )); |
| if (!compileCommands.existsSync()) { |
| io.stderr.writeln('clang-tidy requires a fully built host_debug or ' |
| 'host_debug_unopt build directory'); |
| return false; |
| } |
| } |
| final StringBuffer outBuffer = StringBuffer(); |
| final StringBuffer errBuffer = StringBuffer(); |
| final ClangTidy clangTidy = ClangTidy( |
| buildCommandsPath: compileCommands, |
| outSink: outBuffer, |
| errSink: errBuffer, |
| ); |
| final int clangTidyResult = await clangTidy.run(); |
| sw.stop(); |
| io.stdout.writeln('clang-tidy checks finished in ${sw.elapsed}'); |
| if (clangTidyResult != 0) { |
| io.stderr.write(errBuffer); |
| return false; |
| } |
| return true; |
| } |
| |
| Future<bool> _runFormatter(String flutterRoot, bool verbose) async { |
| io.stdout.writeln('Starting formatting checks.'); |
| final Stopwatch sw = Stopwatch()..start(); |
| final String ext = io.Platform.isWindows ? '.bat' : '.sh'; |
| final bool result = await _runCheck( |
| flutterRoot, |
| path.join(flutterRoot, 'ci', 'format$ext'), |
| <String>[], |
| 'Formatting check', |
| verbose: verbose, |
| ); |
| sw.stop(); |
| io.stdout.writeln('formatting checks finished in ${sw.elapsed}'); |
| return result; |
| } |
| |
| Future<bool> _runCheck( |
| String flutterRoot, |
| String scriptPath, |
| List<String> scriptArgs, |
| String checkName, { |
| bool verbose = false, |
| }) async { |
| if (verbose) { |
| io.stdout.writeln('Starting "$checkName": $scriptPath'); |
| } |
| final io.ProcessResult result = await io.Process.run( |
| scriptPath, |
| scriptArgs, |
| workingDirectory: flutterRoot, |
| ); |
| if (result.exitCode != 0) { |
| final StringBuffer message = StringBuffer(); |
| message.writeln('Check "$checkName" failed.'); |
| message.writeln('command: $scriptPath ${scriptArgs.join(" ")}'); |
| message.writeln('working directory: $flutterRoot'); |
| message.writeln('exit code: ${result.exitCode}'); |
| message.writeln('stdout:'); |
| message.writeln(result.stdout); |
| message.writeln('stderr:'); |
| message.writeln(result.stderr); |
| io.stderr.write(message.toString()); |
| return false; |
| } |
| if (verbose) { |
| io.stdout.writeln('Check "$checkName" finished successfully.'); |
| } |
| return true; |
| } |
| } |