1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
|
// 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
|