| // 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, |
| ); |