blob: 8bc16c234891c72974a0d89888cd1a72db92024f [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:collection';
import 'dart:io' as io;
import 'package:args/args.dart';
import 'package:engine_repo_tools/engine_repo_tools.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as p;
import 'src/header_file.dart';
/// Checks C++ header files for header guards.
@immutable
final class HeaderGuardCheck {
/// Creates a new header guard checker.
HeaderGuardCheck({
required this.source,
required this.exclude,
this.include = const <String>[],
this.fix = false,
StringSink? stdOut,
StringSink? stdErr,
}) : _stdOut = stdOut ?? io.stdout, _stdErr = stdErr ?? io.stderr;
/// Parses the command line arguments and creates a new header guard checker.
factory HeaderGuardCheck.fromCommandLine(List<String> arguments) {
final ArgResults argResults = _parser.parse(arguments);
return HeaderGuardCheck(
source: Engine.fromSrcPath(argResults['root'] as String),
include: argResults['include'] as List<String>,
exclude: argResults['exclude'] as List<String>,
fix: argResults['fix'] as bool,
);
}
/// Engine source root.
final Engine source;
/// Whether to automatically fix most header guards.
final bool fix;
/// Path directories to include in the check.
final List<String> include;
/// Path directories to exclude from the check.
final List<String> exclude;
/// Stdout.
final StringSink _stdOut;
/// Stderr.
final StringSink _stdErr;
/// Runs the header guard check.
Future<int> run() async {
final List<HeaderFile> badFiles = _checkFiles(_findIncludedHeaderFiles()).toList();
if (badFiles.isNotEmpty) {
_stdOut.writeln('The following ${badFiles.length} files have invalid header guards:');
for (final HeaderFile headerFile in badFiles) {
_stdOut.writeln(' ${headerFile.path}');
}
// If we're fixing, fix the files.
if (fix) {
for (final HeaderFile headerFile in badFiles) {
headerFile.fix(engineRoot: source.flutterDir.path);
}
_stdOut.writeln('Fixed ${badFiles.length} files.');
return 0;
}
return 1;
}
return 0;
}
Iterable<io.File> _findIncludedHeaderFiles() sync* {
final Queue<String> queue = Queue<String>();
final Set<String> yielded = <String>{};
if (include.isEmpty) {
queue.add(source.flutterDir.path);
} else {
queue.addAll(include);
}
while (queue.isNotEmpty) {
final String path = queue.removeFirst();
if (path.endsWith('.h')) {
if (!_isExcluded(path) && yielded.add(path)) {
yield io.File(path);
}
} else if (io.FileSystemEntity.isDirectorySync(path)) {
if (_isExcluded(path)) {
continue;
}
final io.Directory directory = io.Directory(path);
for (final io.FileSystemEntity entity in directory.listSync(recursive: true)) {
if (entity is io.File && entity.path.endsWith('.h')) {
queue.add(entity.path);
}
}
} else {
// Neither a header file nor a directory that might contain header files.
}
}
}
bool _isExcluded(String path) {
for (final String excludePath in exclude) {
final String relativePath = p.relative(excludePath, from: source.flutterDir.path);
if (p.isWithin(relativePath, path) || p.equals(relativePath, path)) {
return true;
}
}
return false;
}
Iterable<HeaderFile> _checkFiles(Iterable<io.File> headers) sync* {
for (final io.File header in headers) {
final HeaderFile headerFile = HeaderFile.parse(header.path);
if (headerFile.pragmaOnce != null) {
_stdErr.writeln(headerFile.pragmaOnce!.message('Unexpected #pragma once'));
yield headerFile;
continue;
}
if (headerFile.guard == null) {
_stdErr.writeln('Missing header guard in ${headerFile.path}');
yield headerFile;
continue;
}
final String expectedGuard = headerFile.computeExpectedName(engineRoot: source.flutterDir.path);
if (headerFile.guard!.ifndefValue != expectedGuard) {
_stdErr.writeln(headerFile.guard!.ifndefSpan!.message('Expected #ifndef $expectedGuard'));
yield headerFile;
continue;
}
if (headerFile.guard!.defineValue != expectedGuard) {
_stdErr.writeln(headerFile.guard!.defineSpan!.message('Expected #define $expectedGuard'));
yield headerFile;
continue;
}
if (headerFile.guard!.endifValue != expectedGuard) {
_stdErr.writeln(headerFile.guard!.endifSpan!.message('Expected #endif // $expectedGuard'));
yield headerFile;
continue;
}
}
}
}
final Engine? _engine = Engine.tryFindWithin(p.dirname(p.fromUri(io.Platform.script)));
final ArgParser _parser = ArgParser()
..addFlag(
'fix',
help: 'Automatically fixes most header guards.',
)
..addOption(
'root',
abbr: 'r',
help: 'Path to the engine source root.',
valueHelp: 'path/to/engine/src',
defaultsTo: _engine?.srcDir.path,
)
..addMultiOption(
'include',
abbr: 'i',
help: 'Paths to include in the check.',
valueHelp: 'path/to/dir/or/file (relative to the engine root)',
defaultsTo: <String>[],
)
..addMultiOption(
'exclude',
abbr: 'e',
help: 'Paths to exclude from the check.',
valueHelp: 'path/to/dir/or/file (relative to the engine root)',
defaultsTo: _engine != null ? <String>[
'build',
'buildtools',
'impeller/compiler/code_gen_template.h',
'prebuilts',
'third_party',
] : null,
);