summaryrefslogtreecommitdiff
path: root/gn/tools/gn/command_check.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gn/tools/gn/command_check.cc')
-rw-r--r--gn/tools/gn/command_check.cc256
1 files changed, 256 insertions, 0 deletions
diff --git a/gn/tools/gn/command_check.cc b/gn/tools/gn/command_check.cc
new file mode 100644
index 00000000000..99fbff502f3
--- /dev/null
+++ b/gn/tools/gn/command_check.cc
@@ -0,0 +1,256 @@
+// 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 "tools/gn/commands.h"
+#include "tools/gn/header_checker.h"
+#include "tools/gn/setup.h"
+#include "tools/gn/standard_out.h"
+#include "tools/gn/switches.h"
+#include "tools/gn/target.h"
+#include "tools/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
+ whitelisted.
+
+ 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]
+
+ 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
+
+ --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. Only these targets
+ will be checked if no label_pattern is specified on the command line.
+ Otherwise, the command-line list is used instead. See "gn help dotfile".
+
+ 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.
+
+ - Includes with a "nogncheck" annotation are skipped (see
+ "gn help nogncheck").
+
+ - Only includes using "quotes" are checked. <brackets> are assumed to be
+ system includes.
+
+ - 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(), "You're holding it wrong.",
+ "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;
+
+ 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, false, &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 {
+ // No global filter, check everything.
+ targets_to_check = all_targets;
+ }
+ }
+
+ const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+ bool force = cmdline->HasSwitch("force");
+
+ if (!CheckPublicHeaders(&setup->build_settings(), all_targets,
+ targets_to_check, force))
+ 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 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) {
+ ScopedTrace trace(TraceItem::TRACE_CHECK_HEADERS, "Check headers");
+
+ scoped_refptr<HeaderChecker> header_checker(
+ new HeaderChecker(build_settings, all_targets));
+
+ 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