blob: 38a474c3a75af00b6b02cf0200b6e80fb0b1708f [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 "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
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] [--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(), "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;
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