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 --force
74 Ignores specifications of "check_includes = false" and checks all
75 target's files that match the target label.
76
77 --check-generated
78 Generated files are normally not checked since they do not exist
79 until after a build. With this flag, those generated files that
80 can be found on disk are also checked.
81
82 --check-system
83 Check system style includes (using <angle brackets>) in addition to
84 "double quote" includes.
85
86 What gets checked
87
88 The .gn file may specify a list of targets to be checked in the list
89 check_targets (see "gn help dotfile"). If a label pattern is specified
90 on the command line, check_targets is not used.
91
92 Targets can opt-out from checking with "check_includes = false" (see
93 "gn help check_includes").
94
95 For targets being checked:
96
97 - GN opens all C-like source files in the targets to be checked and scans
98 the top for includes.
99
100 - Generated files (that might not exist yet) are ignored unless
101 the --check-generated flag is provided.
102
103 - Includes with a "nogncheck" annotation are skipped (see
104 "gn help nogncheck").
105
106 - Includes using "quotes" are always checked.
107 If system style checking is enabled, includes using <angle brackets>
108 are also checked.
109
110 - Include paths are assumed to be relative to any of the "include_dirs" for
111 the target (including the implicit current dir).
112
113 - GN does not run the preprocessor so will not understand conditional
114 includes.
115
116 - Only includes matching known files in the build are checked: includes
117 matching unknown paths are ignored.
118
119 For an include to be valid:
120
121 - The included file must be in the current target, or there must be a path
122 following only public dependencies to a target with the file in it
123 ("gn path" is a good way to diagnose problems).
124
125 - There can be multiple targets with an included file: only one needs to be
126 valid for the include to be allowed.
127
128 - If there are only "sources" in a target, all are considered to be public
129 and can be included by other targets with a valid public dependency path.
130
131 - If a target lists files as "public", only those files are able to be
132 included by other targets. Anything in the sources will be considered
133 private and will not be includable regardless of dependency paths.
134
135 - Outputs from actions are treated like public sources on that target.
136
137 - A target can include headers from a target that depends on it if the
138 other target is annotated accordingly. See "gn help
139 allow_circular_includes_from".
140
141 Advice on fixing problems
142
143 If you have a third party project that is difficult to fix or doesn't care
144 about include checks it's generally best to exclude that target from checking
145 altogether via "check_includes = false".
146
147 If you have conditional includes, make sure the build conditions and the
148 preprocessor conditions match, and annotate the line with "nogncheck" (see
149 "gn help nogncheck" for an example).
150
151 If two targets are hopelessly intertwined, use the
152 "allow_circular_includes_from" annotation. Ideally each should have identical
153 dependencies so configs inherited from those dependencies are consistent (see
154 "gn help allow_circular_includes_from").
155
156 If you have a standalone header file or files that need to be shared between
157 a few targets, you can consider making a source_set listing only those
158 headers as public sources. With only header files, the source set will be a
159 no-op from a build perspective, but will give a central place to refer to
160 those headers. That source set's files will still need to pass "gn check" in
161 isolation.
162
163 In rare cases it makes sense to list a header in more than one target if it
164 could be considered conceptually a member of both.
165
166 Examples
167
168 gn check out/Debug
169 Check everything.
170
171 gn check out/Default //foo:bar
172 Check only the files in the //foo:bar target.
173
174 gn check out/Default "//foo/*
175 Check only the files in targets in the //foo directory tree.
176 )";
177
RunCheck(const std::vector<std::string> & args)178 int RunCheck(const std::vector<std::string>& args) {
179 if (args.size() != 1 && args.size() != 2) {
180 Err(Location(), "You're holding it wrong.",
181 "Usage: \"gn check <out_dir> [<target_label>]\"")
182 .PrintToStdout();
183 return 1;
184 }
185
186 // Deliberately leaked to avoid expensive process teardown.
187 Setup* setup = new Setup();
188 if (!setup->DoSetup(args[0], false))
189 return 1;
190 if (!setup->Run())
191 return 1;
192
193 std::vector<const Target*> all_targets =
194 setup->builder().GetAllResolvedTargets();
195
196 bool filtered_by_build_config = false;
197 std::vector<const Target*> targets_to_check;
198 if (args.size() > 1) {
199 // Compute the targets to check.
200 std::vector<std::string> inputs(args.begin() + 1, args.end());
201 UniqueVector<const Target*> target_matches;
202 UniqueVector<const Config*> config_matches;
203 UniqueVector<const Toolchain*> toolchain_matches;
204 UniqueVector<SourceFile> file_matches;
205 if (!ResolveFromCommandLineInput(setup, inputs, false, &target_matches,
206 &config_matches, &toolchain_matches,
207 &file_matches))
208 return 1;
209
210 if (target_matches.size() == 0) {
211 OutputString("No matching targets.\n");
212 return 1;
213 }
214 targets_to_check.insert(targets_to_check.begin(), target_matches.begin(),
215 target_matches.end());
216 } else {
217 // No argument means to check everything allowed by the filter in
218 // the build config file.
219 if (setup->check_patterns()) {
220 FilterTargetsByPatterns(all_targets, *setup->check_patterns(),
221 &targets_to_check);
222 filtered_by_build_config = targets_to_check.size() != all_targets.size();
223 } else {
224 // No global filter, check everything.
225 targets_to_check = all_targets;
226 }
227 }
228
229 const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
230 bool force = cmdline->HasSwitch("force");
231 bool check_generated = cmdline->HasSwitch("check-generated");
232 bool check_system = setup->check_system_includes() ||
233 cmdline->HasSwitch("check-system");
234
235 if (!CheckPublicHeaders(&setup->build_settings(), all_targets,
236 targets_to_check, force, check_generated,
237 check_system))
238 return 1;
239
240 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kQuiet)) {
241 if (filtered_by_build_config) {
242 // Tell the user about the implicit filtering since this is obscure.
243 OutputString(base::StringPrintf(
244 "%d targets out of %d checked based on the check_targets defined in"
245 " \".gn\".\n",
246 static_cast<int>(targets_to_check.size()),
247 static_cast<int>(all_targets.size())));
248 }
249 OutputString("Header dependency check OK\n", DECORATION_GREEN);
250 }
251 return 0;
252 }
253
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)254 bool CheckPublicHeaders(const BuildSettings* build_settings,
255 const std::vector<const Target*>& all_targets,
256 const std::vector<const Target*>& to_check,
257 bool force_check, bool check_generated,
258 bool check_system) {
259 ScopedTrace trace(TraceItem::TRACE_CHECK_HEADERS, "Check headers");
260
261 scoped_refptr<HeaderChecker> header_checker(
262 new HeaderChecker(build_settings, all_targets, check_generated, check_system));
263
264 std::vector<Err> header_errors;
265 header_checker->Run(to_check, force_check, &header_errors);
266 for (size_t i = 0; i < header_errors.size(); i++) {
267 if (i > 0)
268 OutputString("___________________\n", DECORATION_YELLOW);
269 header_errors[i].PrintToStdout();
270 }
271 return header_errors.empty();
272 }
273
274 } // namespace commands
275