blob: 80b653cbabe71e5e4404218ff04367e6ee435d86 [file] [log] [blame]
// 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 String flutterRoot = globalResults!['flutter']! as String;
final List<bool> checkResults = <bool>[
await _runFormatter(flutterRoot, verbose),
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;
}
}