Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(272)

Side by Side Diff: tools/gn/header_checker.cc

Issue 216903004: Add optional public header checking to GN build (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: unit test Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « tools/gn/header_checker.h ('k') | tools/gn/header_checker_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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 "tools/gn/header_checker.h"
6
7 #include <algorithm>
8
9 #include "base/bind.h"
10 #include "base/file_util.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/threading/sequenced_worker_pool.h"
13 #include "tools/gn/build_settings.h"
14 #include "tools/gn/builder.h"
15 #include "tools/gn/c_include_iterator.h"
16 #include "tools/gn/err.h"
17 #include "tools/gn/filesystem_utils.h"
18 #include "tools/gn/scheduler.h"
19 #include "tools/gn/target.h"
20 #include "tools/gn/trace.h"
21
22 HeaderChecker::HeaderChecker(const BuildSettings* build_settings,
23 const std::vector<const Target*>& targets)
24 : main_loop_(base::MessageLoop::current()),
25 build_settings_(build_settings) {
26 for (size_t i = 0; i < targets.size(); i++)
27 AddTargetToFileMap(targets[i]);
28 }
29
30 HeaderChecker::~HeaderChecker() {
31 }
32
33 bool HeaderChecker::Run(std::vector<Err>* errors) {
34 ScopedTrace trace(TraceItem::TRACE_CHECK_HEADERS, "Check headers");
35
36 if (file_map_.empty())
37 return true;
38
39 scoped_refptr<base::SequencedWorkerPool> pool(
40 new base::SequencedWorkerPool(16, "HeaderChecker"));
41 for (FileMap::const_iterator file_i = file_map_.begin();
42 file_i != file_map_.end(); ++file_i) {
43 const TargetVector& vect = file_i->second;
44
45 // Only check C-like source files (RC files also have includes).
46 SourceFileType type = GetSourceFileType(file_i->first);
47 if (type != SOURCE_CC && type != SOURCE_H && type != SOURCE_C &&
48 type != SOURCE_M && type != SOURCE_MM && type != SOURCE_RC)
49 continue;
50
51 for (size_t vect_i = 0; vect_i < vect.size(); ++vect_i) {
52 pool->PostWorkerTaskWithShutdownBehavior(
53 FROM_HERE,
54 base::Bind(&HeaderChecker::DoWork, this,
55 vect[vect_i].target, file_i->first),
56 base::SequencedWorkerPool::BLOCK_SHUTDOWN);
57 }
58 }
59
60 // After this call we're single-threaded again.
61 pool->Shutdown();
62
63 if (errors_.empty())
64 return true;
65 *errors = errors_;
66 return false;
67 }
68
69 void HeaderChecker::DoWork(const Target* target, const SourceFile& file) {
70 Err err;
71 if (!CheckFile(target, file, &err)) {
72 base::AutoLock lock(lock_);
73 errors_.push_back(err);
74 }
75 }
76
77 void HeaderChecker::AddTargetToFileMap(const Target* target) {
78 // Files in the sources have this public bit by default.
79 bool default_public = target->all_headers_public();
80
81 // First collect the normal files, they get the default visibility.
82 std::map<SourceFile, bool> files_to_public;
83 const Target::FileList& sources = target->sources();
84 for (size_t i = 0; i < sources.size(); i++)
85 files_to_public[sources[i]] = default_public;
86
87 // Add in the public files, forcing them to public. This may overwrite some
88 // entries, and it may add new ones.
89 const Target::FileList& public_list = target->public_headers();
90 if (default_public)
91 DCHECK(public_list.empty()); // List only used when default is not public.
92 for (size_t i = 0; i < public_list.size(); i++)
93 files_to_public[public_list[i]] = true;
94
95 // Add the merged list to the master list of all files.
96 for (std::map<SourceFile, bool>::const_iterator i = files_to_public.begin();
97 i != files_to_public.end(); ++i)
98 file_map_[i->first].push_back(TargetInfo(target, i->second));
99 }
100
101 bool HeaderChecker::IsFileInOuputDir(const SourceFile& file) const {
102 const std::string& build_dir = build_settings_->build_dir().value();
103 return file.value().compare(0, build_dir.size(), build_dir) == 0;
104 }
105
106 // This current assumes all include paths are relative to the source root
107 // which is generally the case for Chromium.
108 //
109 // A future enhancement would be to search the include path for the target
110 // containing the source file containing this include and find the file to
111 // handle the cases where people do weird things with the paths.
112 SourceFile HeaderChecker::SourceFileForInclude(
113 const base::StringPiece& input) const {
114 std::string str("//");
115 input.AppendToString(&str);
116 return SourceFile(str);
117 }
118
119 bool HeaderChecker::CheckFile(const Target* from_target,
120 const SourceFile& file,
121 Err* err) const {
122 ScopedTrace trace(TraceItem::TRACE_CHECK_HEADER, file.value());
123
124 // Sometimes you have generated source files included as sources in another
125 // target. These won't exist at checking time. Since we require all generated
126 // files to be somewhere in the output tree, we can just check the name to
127 // see if they should be skipped.
128 if (IsFileInOuputDir(file))
129 return true;
130
131 base::FilePath path = build_settings_->GetFullPath(file);
132 std::string contents;
133 if (!base::ReadFileToString(path, &contents)) {
134 *err = Err(from_target->defined_from(), "Source file not found.",
135 "This target includes as a source:\n " + file.value() +
136 "\nwhich was not found.");
137 return false;
138 }
139
140 CIncludeIterator iter(contents);
141 base::StringPiece current_include;
142 while (iter.GetNextIncludeString(&current_include)) {
143 SourceFile include = SourceFileForInclude(current_include);
144 if (!CheckInclude(from_target, file, include, err))
145 return false;
146 }
147
148 return true;
149 }
150
151 // If the file exists, it must be in a dependency of the given target, and it
152 // must be public in that dependency.
153 bool HeaderChecker::CheckInclude(const Target* from_target,
154 const SourceFile& source_file,
155 const SourceFile& include_file,
156 Err* err) const {
157 // Assume if the file isn't declared in our sources that we don't need to
158 // check it. It would be nice if we could give an error if this happens, but
159 // our include finder is too primitive and returns all includes, even if
160 // they're in a #if not executed in the current build. In that case, it's
161 // not unusual for the buildfiles to not specify that header at all.
162 FileMap::const_iterator found = file_map_.find(include_file);
163 if (found == file_map_.end())
164 return true;
165
166 const TargetVector& targets = found->second;
167
168 // For all targets containing this file, we require that at least one be
169 // a dependency of the current target, and all targets that are dependencies
170 // must have the file listed in the public section.
171 bool found_dependency = false;
172 for (size_t i = 0; i < targets.size(); i++) {
173 // We always allow source files in a target to include headers also in that
174 // target.
175 if (targets[i].target == from_target)
176 return true;
177
178 if (IsDependencyOf(targets[i].target, from_target)) {
179 // The include is in a target that's a proper dependency. Now verify
180 // that the include is a public file.
181 if (!targets[i].is_public) {
182 // Depending on a private header.
183 std::string msg = "The file " + source_file.value() +
184 "\nincludes " + include_file.value() +
185 "\nwhich is private to the target " +
186 targets[i].target->label().GetUserVisibleName(false);
187
188 // TODO(brettw) blame the including file.
189 *err = Err(NULL, "Including a private header.", msg);
190 return false;
191 }
192 found_dependency = true;
193 }
194 }
195
196 if (!found_dependency) {
197 std::string msg =
198 source_file.value() + " includes the header\n" +
199 include_file.value() + " which is not in any dependency of\n" +
200 from_target->label().GetUserVisibleName(false);
201 msg += "\n\nThe include file is in the target(s):\n";
202 for (size_t i = 0; i < targets.size(); i++)
203 msg += " " + targets[i].target->label().GetUserVisibleName(false) + "\n";
204
205 msg += "\nMake sure one of these is a direct or indirect dependency\n"
206 "of " + from_target->label().GetUserVisibleName(false);
207
208 // TODO(brettw) blame the including file.
209 // Probably this means making and leaking an input file for it, and also
210 // tracking the locations for each include.
211 *err = Err(NULL, "Include not allowed.", msg);
212 return false;
213 }
214
215 return true;
216 }
217
218 bool HeaderChecker::IsDependencyOf(const Target* search_for,
219 const Target* search_from) const {
220 std::set<const Target*> checked;
221 return IsDependencyOf(search_for, search_from, &checked);
222 }
223
224 bool HeaderChecker::IsDependencyOf(const Target* search_for,
225 const Target* search_from,
226 std::set<const Target*>* checked) const {
227 if (checked->find(search_for) != checked->end())
228 return false; // Already checked this subtree.
229
230 const LabelTargetVector& deps = search_from->deps();
231 for (size_t i = 0; i < deps.size(); i++) {
232 if (deps[i].ptr == search_for)
233 return true; // Found it.
234
235 // Recursive search.
236 checked->insert(deps[i].ptr);
237 if (IsDependencyOf(search_for, deps[i].ptr, checked))
238 return true;
239 }
240
241 return false;
242 }
OLDNEW
« no previous file with comments | « tools/gn/header_checker.h ('k') | tools/gn/header_checker_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698