1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <stddef.h>
6
7 #include "base/command_line.h"
8 #include "base/strings/stringprintf.h"
9 #include "gn/commands.h"
10 #include "gn/header_checker.h"
11 #include "gn/setup.h"
12 #include "gn/standard_out.h"
13 #include "gn/switches.h"
14 #include "gn/target.h"
15 #include "gn/trace.h"
16
17 namespace commands {
18
19 const char kNoGnCheck_Help[] =
20 R"(nogncheck: Skip an include line from checking.
21
22 GN's header checker helps validate that the includes match the build
23 dependency graph. Sometimes an include might be conditional or otherwise
24 problematic, but you want to specifically allow it. In this case, it can be
25 whitelisted.
26
27 Include lines containing the substring "nogncheck" will be excluded from
28 header checking. The most common case is a conditional include:
29
30 #if defined(ENABLE_DOOM_MELON)
31 #include "tools/doom_melon/doom_melon.h" // nogncheck
32 #endif
33
34 If the build file has a conditional dependency on the corresponding target
35 that matches the conditional include, everything will always link correctly:
36
37 source_set("mytarget") {
38 ...
39 if (enable_doom_melon) {
40 defines = [ "ENABLE_DOOM_MELON" ]
41 deps += [ "//tools/doom_melon" ]
42 }
43
44 But GN's header checker does not understand preprocessor directives, won't
45 know it matches the build dependencies, and will flag this include as
46 incorrect when the condition is false.
47
48 More information
49
50 The topic "gn help check" has general information on how checking works and
51 advice on fixing problems. Targets can also opt-out of checking, see
52 "gn help check_includes".
53 )";
54
55 const char kCheck[] = "check";
56 const char kCheck_HelpShort[] = "check: Check header dependencies.";
57 const char kCheck_Help[] =
58 R"(gn check <out_dir> [<label_pattern>] [--force] [--check-generated]
59
60 GN's include header checker validates that the includes for C-like source
61 files match the build dependency graph.
62
63 "gn check" is the same thing as "gn gen" with the "--check" flag except that
64 this command does not write out any build files. It's intended to be an easy
65 way to manually trigger include file checking.
66
67 The <label_pattern> can take exact labels or patterns that match more than
68 one (although not general regular expressions). If specified, only those
69 matching targets will be checked. See "gn help label_pattern" for details.
70
71 Command-specific switches
72
73 --check-generated
74 Generated files are normally not checked since they do not exist
75 until after a build. With this flag, those generated files that
76 can be found on disk are also checked.
77
78 --check-system
79 Check system style includes (using <angle brackets>) in addition to
80 "double quote" includes.
81
82 )" DEFAULT_TOOLCHAIN_SWITCH_HELP
83 R"(
84 --force
85 Ignores specifications of "check_includes = false" and checks all
86 target's files that match the target label.
87
88 What gets checked
89
90 The .gn file may specify a list of targets to be checked in the list
91 check_targets (see "gn help dotfile"). Alternatively, the .gn file may
92 specify a list of targets not to be checked in no_check_targets. If a label
93 pattern is specified on the command line, neither check_targets or
94 no_check_targets is used.
95
96 Targets can opt-out from checking with "check_includes = false" (see
97 "gn help check_includes").
98
99 For targets being checked:
100
101 - GN opens all C-like source files in the targets to be checked and scans
102 the top for includes.
103
104 - Generated files (that might not exist yet) are ignored unless
105 the --check-generated flag is provided.
106
107 - Includes with a "nogncheck" annotation are skipped (see
108 "gn help nogncheck").
109
110 - Includes using "quotes" are always checked.
111 If system style checking is enabled, includes using <angle brackets>
112 are also checked.
113
114 - Include paths are assumed to be relative to any of the "include_dirs" for
115 the target (including the implicit current dir).
116
117 - GN does not run the preprocessor so will not understand conditional
118 includes.
119
120 - Only includes matching known files in the build are checked: includes
121 matching unknown paths are ignored.
122
123 For an include to be valid:
124
125 - The included file must be in the current target, or there must be a path
126 following only public dependencies to a target with the file in it
127 ("gn path" is a good way to diagnose problems).
128
129 - There can be multiple targets with an included file: only one needs to be
130 valid for the include to be allowed.
131
132 - If there are only "sources" in a target, all are considered to be public
133 and can be included by other targets with a valid public dependency path.
134
135 - If a target lists files as "public", only those files are able to be
136 included by other targets. Anything in the sources will be considered
137 private and will not be includable regardless of dependency paths.
138
139 - Outputs from actions are treated like public sources on that target.
140
141 - A target can include headers from a target that depends on it if the
142 other target is annotated accordingly. See "gn help
143 allow_circular_includes_from".
144
145 Advice on fixing problems
146
147 If you have a third party project that is difficult to fix or doesn't care
148 about include checks it's generally best to exclude that target from checking
149 altogether via "check_includes = false".
150
151 If you have conditional includes, make sure the build conditions and the
152 preprocessor conditions match, and annotate the line with "nogncheck" (see
153 "gn help nogncheck" for an example).
154
155 If two targets are hopelessly intertwined, use the
156 "allow_circular_includes_from" annotation. Ideally each should have identical
157 dependencies so configs inherited from those dependencies are consistent (see
158 "gn help allow_circular_includes_from").
159
160 If you have a standalone header file or files that need to be shared between
161 a few targets, you can consider making a source_set listing only those
162 headers as public sources. With only header files, the source set will be a
163 no-op from a build perspective, but will give a central place to refer to
164 those headers. That source set's files will still need to pass "gn check" in
165 isolation.
166
167 In rare cases it makes sense to list a header in more than one target if it
168 could be considered conceptually a member of both.
169
170 Examples
171
172 gn check out/Debug
173 Check everything.
174
175 gn check out/Default //foo:bar
176 Check only the files in the //foo:bar target.
177
178 gn check out/Default "//foo/*
179 Check only the files in targets in the //foo directory tree.
180 )";
181
RunCheck(const std::vector<std::string> & args)182 int RunCheck(const std::vector<std::string>& args) {
183 if (args.size() != 1 && args.size() != 2) {
184 Err(Location(), "Unknown command format. See \"gn help check\"",
185 "Usage: \"gn check <out_dir> [<target_label>]\"")
186 .PrintToStdout();
187 return 1;
188 }
189
190 // Deliberately leaked to avoid expensive process teardown.
191 Setup* setup = new Setup();
192 if (!setup->DoSetup(args[0], false))
193 return 1;
194 if (!setup->Run())
195 return 1;
196
197 const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
198 bool default_toolchain_only = cmdline->HasSwitch(switches::kDefaultToolchain);
199
200 std::vector<const Target*> all_targets =
201 setup->builder().GetAllResolvedTargets();
202
203 bool filtered_by_build_config = false;
204 std::vector<const Target*> targets_to_check;
205 if (args.size() > 1) {
206 // Compute the targets to check.
207 std::vector<std::string> inputs(args.begin() + 1, args.end());
208 UniqueVector<const Target*> target_matches;
209 UniqueVector<const Config*> config_matches;
210 UniqueVector<const Toolchain*> toolchain_matches;
211 UniqueVector<SourceFile> file_matches;
212 if (!ResolveFromCommandLineInput(setup, inputs, default_toolchain_only,
213 &target_matches, &config_matches,
214 &toolchain_matches, &file_matches))
215 return 1;
216
217 if (target_matches.size() == 0) {
218 OutputString("No matching targets.\n");
219 return 1;
220 }
221 targets_to_check.insert(targets_to_check.begin(), target_matches.begin(),
222 target_matches.end());
223 } else {
224 // No argument means to check everything allowed by the filter in
225 // the build config file.
226 if (setup->check_patterns()) {
227 FilterTargetsByPatterns(all_targets, *setup->check_patterns(),
228 &targets_to_check);
229 filtered_by_build_config = targets_to_check.size() != all_targets.size();
230 } else if (setup->no_check_patterns()) {
231 FilterOutTargetsByPatterns(all_targets, *setup->no_check_patterns(),
232 &targets_to_check);
233 filtered_by_build_config = targets_to_check.size() != all_targets.size();
234 } else {
235 // No global filter, check everything.
236 targets_to_check = all_targets;
237 }
238 }
239
240 bool force = cmdline->HasSwitch("force");
241 bool check_generated = cmdline->HasSwitch("check-generated");
242 bool check_system =
243 setup->check_system_includes() || cmdline->HasSwitch("check-system");
244
245 if (!CheckPublicHeaders(&setup->build_settings(), all_targets,
246 targets_to_check, force, check_generated,
247 check_system))
248 return 1;
249
250 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kQuiet)) {
251 if (filtered_by_build_config) {
252 // Tell the user about the implicit filtering since this is obscure.
253 OutputString(base::StringPrintf(
254 "%d targets out of %d checked based on the check_targets or "
255 "no_check_targets defined in \".gn\".\n",
256 static_cast<int>(targets_to_check.size()),
257 static_cast<int>(all_targets.size())));
258 }
259 OutputString("Header dependency check OK\n", DECORATION_GREEN);
260 }
261 return 0;
262 }
263
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)264 bool CheckPublicHeaders(const BuildSettings* build_settings,
265 const std::vector<const Target*>& all_targets,
266 const std::vector<const Target*>& to_check,
267 bool force_check,
268 bool check_generated,
269 bool check_system) {
270 ScopedTrace trace(TraceItem::TRACE_CHECK_HEADERS, "Check headers");
271
272 scoped_refptr<HeaderChecker> header_checker(new HeaderChecker(
273 build_settings, all_targets, check_generated, check_system));
274
275 std::vector<Err> header_errors;
276 header_checker->Run(to_check, force_check, &header_errors);
277 for (size_t i = 0; i < header_errors.size(); i++) {
278 if (i > 0)
279 OutputString("___________________\n", DECORATION_YELLOW);
280 header_errors[i].PrintToStdout();
281 }
282 return header_errors.empty();
283 }
284
285 } // namespace commands
286