| // Copyright 2014 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include <stddef.h> | |
| #include "base/command_line.h" | |
| #include "base/strings/stringprintf.h" | |
| #include "gn/commands.h" | |
| #include "gn/header_checker.h" | |
| #include "gn/setup.h" | |
| #include "gn/standard_out.h" | |
| #include "gn/switches.h" | |
| #include "gn/target.h" | |
| #include "gn/trace.h" | |
| namespace commands { | |
| const char kNoGnCheck_Help[] = | |
| R"(nogncheck: Skip an include line from checking. | |
| GN's header checker helps validate that the includes match the build | |
| dependency graph. Sometimes an include might be conditional or otherwise | |
| problematic, but you want to specifically allow it. In this case, it can be | |
| allowlisted. | |
| Include lines containing the substring "nogncheck" will be excluded from | |
| header checking. The most common case is a conditional include: | |
| #if defined(ENABLE_DOOM_MELON) | |
| #include "tools/doom_melon/doom_melon.h" // nogncheck | |
| #endif | |
| If the build file has a conditional dependency on the corresponding target | |
| that matches the conditional include, everything will always link correctly: | |
| source_set("mytarget") { | |
| ... | |
| if (enable_doom_melon) { | |
| defines = [ "ENABLE_DOOM_MELON" ] | |
| deps += [ "//tools/doom_melon" ] | |
| } | |
| But GN's header checker does not understand preprocessor directives, won't | |
| know it matches the build dependencies, and will flag this include as | |
| incorrect when the condition is false. | |
| More information | |
| The topic "gn help check" has general information on how checking works and | |
| advice on fixing problems. Targets can also opt-out of checking, see | |
| "gn help check_includes". | |
| )"; | |
| const char kCheck[] = "check"; | |
| const char kCheck_HelpShort[] = "check: Check header dependencies."; | |
| const char kCheck_Help[] = | |
| R"(gn check <out_dir> [<label_pattern>] [--force] [--check-generated] | |
| GN's include header checker validates that the includes for C-like source | |
| files match the build dependency graph. | |
| "gn check" is the same thing as "gn gen" with the "--check" flag except that | |
| this command does not write out any build files. It's intended to be an easy | |
| way to manually trigger include file checking. | |
| The <label_pattern> can take exact labels or patterns that match more than | |
| one (although not general regular expressions). If specified, only those | |
| matching targets will be checked. See "gn help label_pattern" for details. | |
| Command-specific switches | |
| --check-generated | |
| Generated files are normally not checked since they do not exist | |
| until after a build. With this flag, those generated files that | |
| can be found on disk are also checked. | |
| --check-system | |
| Check system style includes (using <angle brackets>) in addition to | |
| "double quote" includes. | |
| )" DEFAULT_TOOLCHAIN_SWITCH_HELP | |
| R"( | |
| --force | |
| Ignores specifications of "check_includes = false" and checks all | |
| target's files that match the target label. | |
| What gets checked | |
| The .gn file may specify a list of targets to be checked in the list | |
| check_targets (see "gn help dotfile"). Alternatively, the .gn file may | |
| specify a list of targets not to be checked in no_check_targets. If a label | |
| pattern is specified on the command line, neither check_targets or | |
| no_check_targets is used. | |
| Targets can opt-out from checking with "check_includes = false" (see | |
| "gn help check_includes"). | |
| For targets being checked: | |
| - GN opens all C-like source files in the targets to be checked and scans | |
| the top for includes. | |
| - Generated files (that might not exist yet) are ignored unless | |
| the --check-generated flag is provided. | |
| - Includes with a "nogncheck" annotation are skipped (see | |
| "gn help nogncheck"). | |
| - Includes using "quotes" are always checked. | |
| If system style checking is enabled, includes using <angle brackets> | |
| are also checked. | |
| - Include paths are assumed to be relative to any of the "include_dirs" for | |
| the target (including the implicit current dir). | |
| - GN does not run the preprocessor so will not understand conditional | |
| includes. | |
| - Only includes matching known files in the build are checked: includes | |
| matching unknown paths are ignored. | |
| For an include to be valid: | |
| - The included file must be in the current target, or there must be a path | |
| following only public dependencies to a target with the file in it | |
| ("gn path" is a good way to diagnose problems). | |
| - There can be multiple targets with an included file: only one needs to be | |
| valid for the include to be allowed. | |
| - If there are only "sources" in a target, all are considered to be public | |
| and can be included by other targets with a valid public dependency path. | |
| - If a target lists files as "public", only those files are able to be | |
| included by other targets. Anything in the sources will be considered | |
| private and will not be includable regardless of dependency paths. | |
| - Outputs from actions are treated like public sources on that target. | |
| - A target can include headers from a target that depends on it if the | |
| other target is annotated accordingly. See "gn help | |
| allow_circular_includes_from". | |
| Advice on fixing problems | |
| If you have a third party project that is difficult to fix or doesn't care | |
| about include checks it's generally best to exclude that target from checking | |
| altogether via "check_includes = false". | |
| If you have conditional includes, make sure the build conditions and the | |
| preprocessor conditions match, and annotate the line with "nogncheck" (see | |
| "gn help nogncheck" for an example). | |
| If two targets are hopelessly intertwined, use the | |
| "allow_circular_includes_from" annotation. Ideally each should have identical | |
| dependencies so configs inherited from those dependencies are consistent (see | |
| "gn help allow_circular_includes_from"). | |
| If you have a standalone header file or files that need to be shared between | |
| a few targets, you can consider making a source_set listing only those | |
| headers as public sources. With only header files, the source set will be a | |
| no-op from a build perspective, but will give a central place to refer to | |
| those headers. That source set's files will still need to pass "gn check" in | |
| isolation. | |
| In rare cases it makes sense to list a header in more than one target if it | |
| could be considered conceptually a member of both. | |
| Examples | |
| gn check out/Debug | |
| Check everything. | |
| gn check out/Default //foo:bar | |
| Check only the files in the //foo:bar target. | |
| gn check out/Default "//foo/* | |
| Check only the files in targets in the //foo directory tree. | |
| )"; | |
| int RunCheck(const std::vector<std::string>& args) { | |
| if (args.size() != 1 && args.size() != 2) { | |
| Err(Location(), "Unknown command format. See \"gn help check\"", | |
| "Usage: \"gn check <out_dir> [<target_label>]\"") | |
| .PrintToStdout(); | |
| return 1; | |
| } | |
| // Deliberately leaked to avoid expensive process teardown. | |
| Setup* setup = new Setup(); | |
| if (!setup->DoSetup(args[0], false)) | |
| return 1; | |
| if (!setup->Run()) | |
| return 1; | |
| const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess(); | |
| bool default_toolchain_only = cmdline->HasSwitch(switches::kDefaultToolchain); | |
| std::vector<const Target*> all_targets = | |
| setup->builder().GetAllResolvedTargets(); | |
| bool filtered_by_build_config = false; | |
| std::vector<const Target*> targets_to_check; | |
| if (args.size() > 1) { | |
| // Compute the targets to check. | |
| std::vector<std::string> inputs(args.begin() + 1, args.end()); | |
| UniqueVector<const Target*> target_matches; | |
| UniqueVector<const Config*> config_matches; | |
| UniqueVector<const Toolchain*> toolchain_matches; | |
| UniqueVector<SourceFile> file_matches; | |
| if (!ResolveFromCommandLineInput(setup, inputs, default_toolchain_only, | |
| &target_matches, &config_matches, | |
| &toolchain_matches, &file_matches)) | |
| return 1; | |
| if (target_matches.size() == 0) { | |
| OutputString("No matching targets.\n"); | |
| return 1; | |
| } | |
| targets_to_check.insert(targets_to_check.begin(), target_matches.begin(), | |
| target_matches.end()); | |
| } else { | |
| // No argument means to check everything allowed by the filter in | |
| // the build config file. | |
| if (setup->check_patterns()) { | |
| FilterTargetsByPatterns(all_targets, *setup->check_patterns(), | |
| &targets_to_check); | |
| filtered_by_build_config = targets_to_check.size() != all_targets.size(); | |
| } else if (setup->no_check_patterns()) { | |
| FilterOutTargetsByPatterns(all_targets, *setup->no_check_patterns(), | |
| &targets_to_check); | |
| filtered_by_build_config = targets_to_check.size() != all_targets.size(); | |
| } else { | |
| // No global filter, check everything. | |
| targets_to_check = all_targets; | |
| } | |
| } | |
| bool force = cmdline->HasSwitch("force"); | |
| bool check_generated = cmdline->HasSwitch("check-generated"); | |
| bool check_system = | |
| setup->check_system_includes() || cmdline->HasSwitch("check-system"); | |
| if (!CheckPublicHeaders(&setup->build_settings(), all_targets, | |
| targets_to_check, force, check_generated, | |
| check_system)) | |
| return 1; | |
| if (!base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kQuiet)) { | |
| if (filtered_by_build_config) { | |
| // Tell the user about the implicit filtering since this is obscure. | |
| OutputString(base::StringPrintf( | |
| "%d targets out of %d checked based on the check_targets or " | |
| "no_check_targets defined in \".gn\".\n", | |
| static_cast<int>(targets_to_check.size()), | |
| static_cast<int>(all_targets.size()))); | |
| } | |
| OutputString("Header dependency check OK\n", DECORATION_GREEN); | |
| } | |
| return 0; | |
| } | |
| bool CheckPublicHeaders(const BuildSettings* build_settings, | |
| const std::vector<const Target*>& all_targets, | |
| const std::vector<const Target*>& to_check, | |
| bool force_check, | |
| bool check_generated, | |
| bool check_system) { | |
| ScopedTrace trace(TraceItem::TRACE_CHECK_HEADERS, "Check headers"); | |
| scoped_refptr<HeaderChecker> header_checker(new HeaderChecker( | |
| build_settings, all_targets, check_generated, check_system)); | |
| std::vector<Err> header_errors; | |
| header_checker->Run(to_check, force_check, &header_errors); | |
| for (size_t i = 0; i < header_errors.size(); i++) { | |
| if (i > 0) | |
| OutputString("___________________\n", DECORATION_YELLOW); | |
| header_errors[i].PrintToStdout(); | |
| } | |
| return header_errors.empty(); | |
| } | |
| } // namespace commands |