blob: 40931a68709347d82d907c7fd161e29ea40ef57a [file] [log] [blame]
// 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[] =
"nogncheck: Skip an include line from checking.\n"
"\n"
" GN's header checker helps validate that the includes match the build\n"
" dependency graph. Sometimes an include might be conditional or\n"
" otherwise problematic, but you want to specifically allow it. In this\n"
" case, it can be whitelisted.\n"
"\n"
" Include lines containing the substring \"nogncheck\" will be excluded\n"
" from header checking. The most common case is a conditional include:\n"
"\n"
" #if defined(ENABLE_DOOM_MELON)\n"
" #include \"tools/doom_melon/doom_melon.h\" // nogncheck\n"
" #endif\n"
"\n"
" If the build file has a conditional dependency on the corresponding\n"
" target that matches the conditional include, everything will always\n"
" link correctly:\n"
"\n"
" source_set(\"mytarget\") {\n"
" ...\n"
" if (enable_doom_melon) {\n"
" defines = [ \"ENABLE_DOOM_MELON\" ]\n"
" deps += [ \"//tools/doom_melon\" ]\n"
" }\n"
"\n"
" But GN's header checker does not understand preprocessor directives,\n"
" won't know it matches the build dependencies, and will flag this\n"
" include as incorrect when the condition is false.\n"
"\n"
"More information\n"
"\n"
" The topic \"gn help check\" has general information on how checking\n"
" works and advice on fixing problems. Targets can also opt-out of\n"
" checking, see \"gn help check_includes\".\n";
const char kCheck[] = "check";
const char kCheck_HelpShort[] =
"check: Check header dependencies.";
const char kCheck_Help[] =
"gn check <out_dir> [<label_pattern>] [--force]\n"
"\n"
" GN's include header checker validates that the includes for C-like\n"
" source files match the build dependency graph.\n"
"\n"
" \"gn check\" is the same thing as \"gn gen\" with the \"--check\" flag\n"
" except that this command does not write out any build files. It's\n"
" intended to be an easy way to manually trigger include file checking.\n"
"\n"
" The <label_pattern> can take exact labels or patterns that match more\n"
" than one (although not general regular expressions). If specified,\n"
" only those matching targets will be checked. See\n"
" \"gn help label_pattern\" for details.\n"
"\n"
"Command-specific switches\n"
"\n"
" --force\n"
" Ignores specifications of \"check_includes = false\" and checks\n"
" all target's files that match the target label.\n"
"\n"
"What gets checked\n"
"\n"
" The .gn file may specify a list of targets to be checked. Only these\n"
" targets will be checked if no label_pattern is specified on the\n"
" command line. Otherwise, the command-line list is used instead. See\n"
" \"gn help dotfile\".\n"
"\n"
" Targets can opt-out from checking with \"check_includes = false\"\n"
" (see \"gn help check_includes\").\n"
"\n"
" For targets being checked:\n"
"\n"
" - GN opens all C-like source files in the targets to be checked and\n"
" scans the top for includes.\n"
"\n"
" - Includes with a \"nogncheck\" annotation are skipped (see\n"
" \"gn help nogncheck\").\n"
"\n"
" - Only includes using \"quotes\" are checked. <brackets> are assumed\n"
" to be system includes.\n"
"\n"
" - Include paths are assumed to be relative to either the source root\n"
" or the \"root_gen_dir\" and must include all the path components.\n"
" (It might be nice in the future to incorporate GN's knowledge of\n"
" the include path to handle other include styles.)\n"
"\n"
" - GN does not run the preprocessor so will not understand\n"
" conditional includes.\n"
"\n"
" - Only includes matching known files in the build are checked:\n"
" includes matching unknown paths are ignored.\n"
"\n"
" For an include to be valid:\n"
"\n"
" - The included file must be in the current target, or there must\n"
" be a path following only public dependencies to a target with the\n"
" file in it (\"gn path\" is a good way to diagnose problems).\n"
"\n"
" - There can be multiple targets with an included file: only one\n"
" needs to be valid for the include to be allowed.\n"
"\n"
" - If there are only \"sources\" in a target, all are considered to\n"
" be public and can be included by other targets with a valid public\n"
" dependency path.\n"
"\n"
" - If a target lists files as \"public\", only those files are\n"
" able to be included by other targets. Anything in the sources\n"
" will be considered private and will not be includable regardless\n"
" of dependency paths.\n"
"\n"
" - Ouptuts from actions are treated like public sources on that\n"
" target.\n"
"\n"
" - A target can include headers from a target that depends on it\n"
" if the other target is annotated accordingly. See\n"
" \"gn help allow_circular_includes_from\".\n"
"\n"
"Advice on fixing problems\n"
"\n"
" If you have a third party project that uses relative includes,\n"
" it's generally best to exclude that target from checking altogether\n"
" via \"check_includes = false\".\n"
"\n"
" If you have conditional includes, make sure the build conditions\n"
" and the preprocessor conditions match, and annotate the line with\n"
" \"nogncheck\" (see \"gn help nogncheck\" for an example).\n"
"\n"
" If two targets are hopelessly intertwined, use the\n"
" \"allow_circular_includes_from\" annotation. Ideally each should have\n"
" identical dependencies so configs inherited from those dependencies\n"
" are consistent (see \"gn help allow_circular_includes_from\").\n"
"\n"
" If you have a standalone header file or files that need to be shared\n"
" between a few targets, you can consider making a source_set listing\n"
" only those headers as public sources. With only header files, the\n"
" source set will be a no-op from a build perspective, but will give a\n"
" central place to refer to those headers. That source set's files\n"
" will still need to pass \"gn check\" in isolation.\n"
"\n"
" In rare cases it makes sense to list a header in more than one\n"
" target if it could be considered conceptually a member of both.\n"
"\n"
"Examples\n"
"\n"
" gn check out/Debug\n"
" Check everything.\n"
"\n"
" gn check out/Default //foo:bar\n"
" Check only the files in the //foo:bar target.\n"
"\n"
" gn check out/Default \"//foo/*\n"
" Check only the files in targets in the //foo directory tree.\n";
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