• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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