• 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   --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