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

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: 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
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 pending_files_(0) {
scottmg 2014/04/05 00:40:07 is this necessary? isn't there just a pool.Join th
brettw 2014/04/07 05:08:19 Yes! It's actually the shutdown call I already mak
27 for (size_t i = 0; i < targets.size(); i++)
28 AddTargetToFileMap(targets[i]);
29 }
30
31 HeaderChecker::~HeaderChecker() {
32 }
33
34 bool HeaderChecker::Run(std::vector<Err>* errors) {
35 ScopedTrace trace(TraceItem::TRACE_CHECK_HEADERS, "Check headers");
36
37 if (file_map_.empty())
38 return true; // Code below will livelock if there is no work.
39
40 scoped_refptr<base::SequencedWorkerPool> pool(
41 new base::SequencedWorkerPool(16, "HeaderChecker"));
42 for (FileMap::const_iterator file_i = file_map_.begin();
43 file_i != file_map_.end(); ++file_i) {
44 const TargetVector& vect = file_i->second;
45
46 // Only check C-like source files.
47 SourceFileType type = GetSourceFileType(file_i->first);
48 if (type != SOURCE_CC && type != SOURCE_H && type != SOURCE_C &&
49 type != SOURCE_M && type != SOURCE_MM)
scottmg 2014/04/05 00:40:07 SOURCE_RC too, strictly speaking
50 continue;
51
52 for (size_t vect_i = 0; vect_i < vect.size(); ++vect_i) {
53 base::AtomicRefCountInc(&pending_files_);
54 pool->PostWorkerTaskWithShutdownBehavior(
55 FROM_HERE,
56 base::Bind(&HeaderChecker::DoWork, this,
57 vect[vect_i].target, file_i->first),
58 base::SequencedWorkerPool::BLOCK_SHUTDOWN);
59 }
60 }
61
62 main_thread_runner_.Run();
63
64 // After this call we're single-threaded again.
65 pool->Shutdown();
66
67 if (errors_.empty())
68 return true;
69 *errors = errors_;
70 return false;
71 }
72
73 void HeaderChecker::DoWork(const Target* target, const SourceFile& file) {
74 Err err;
75 if (!CheckFile(target, file, &err)) {
76 base::AutoLock lock(lock_);
77 errors_.push_back(err);
78 }
79
80 if (!base::AtomicRefCountDec(&pending_files_)) {
81 main_loop_->PostTask(FROM_HERE,
82 base::Bind(&HeaderChecker::OnComplete, this));
83 }
84 }
85
86 void HeaderChecker::OnComplete() {
87 main_thread_runner_.Quit();
88 }
89
90 void HeaderChecker::AddTargetToFileMap(const Target* target) {
91 // Files in the sources have this public bit by default.
92 bool default_public = target->all_headers_public();
93
94 // First collect the normal files, they get the default visibility.
95 std::map<SourceFile, bool> files_to_public;
96 const Target::FileList& sources = target->sources();
97 for (size_t i = 0; i < sources.size(); i++)
98 files_to_public[sources[i]] = default_public;
99
100 // Add in the public files, forcing them to public. This may overwrite some
101 // entries, and it may add new ones.
102 const Target::FileList& public_list = target->public_headers();
103 if (default_public)
104 DCHECK(public_list.empty()); // List only used when default is not public.
105 for (size_t i = 0; i < public_list.size(); i++)
106 files_to_public[public_list[i]] = true;
107
108 // Add the merged list to the master list of all files.
109 for (std::map<SourceFile, bool>::const_iterator i = files_to_public.begin();
110 i != files_to_public.end(); ++i)
111 file_map_[i->first].push_back(TargetInfo(target, i->second));
112 }
113
114 bool HeaderChecker::IsFileInOuputDir(const SourceFile& file) const {
115 const std::string& build_dir = build_settings_->build_dir().value();
116 return file.value().compare(0, build_dir.size(), build_dir) == 0;
117 }
118
119 // This current assumes all include paths are relative to the source root
120 // which is generally the case for Chromium.
121 //
122 // A future enhancement would be to search the include path for the target
123 // containing the source file containing this include and find the file to
124 // handle the cases where people do weird things with the paths.
125 SourceFile HeaderChecker::SourceFileForInclude(
126 const base::StringPiece& input) const {
127 std::string str("//");
128 input.AppendToString(&str);
129 return SourceFile(str);
130 }
131
132 bool HeaderChecker::CheckFile(const Target* from_target,
133 const SourceFile& file,
134 Err* err) const {
135 ScopedTrace trace(TraceItem::TRACE_CHECK_HEADER, file.value());
136
137 // Sometimes you have generated source files included as sources in another
138 // target. These won't exist at checking time. Since we require all generated
139 // files to be somewhere in the output tree, we can just check the name to
140 // see if they should be skipped.
141 if (IsFileInOuputDir(file))
142 return true;
143
144 base::FilePath path = build_settings_->GetFullPath(file);
145 std::string contents;
146 if (!base::ReadFileToString(path, &contents)) {
147 *err = Err(from_target->defined_from(), "Source file not found.",
148 "This target includes as a source:\n " + file.value() +
149 "\nwhich was not found.");
150 return false;
151 }
152
153 CIncludeIterator iter(contents);
154 base::StringPiece current_include;
155 while (iter.GetNextIncludeString(&current_include)) {
156 SourceFile include = SourceFileForInclude(current_include);
157 if (!CheckInclude(from_target, file, include, err))
158 return false;
159 }
160
161 return true;
162 }
163
164 // If the file exists, it must be in a dependency of the given target, and it
165 // must be public in that dependency.
166 bool HeaderChecker::CheckInclude(const Target* from_target,
167 const SourceFile& source_file,
168 const SourceFile& include_file,
169 Err* err) const {
170 // Assume if the file isn't declared in our sources that we don't need to
171 // check it. It would be nice if we could give an error if this happens, but
172 // our include finder is too primitive and returns all includes, even if
173 // they're in a #if not executed in the current build. In that case, it's
174 // not unusual foe the buildfiles to not specify that header at all.
scottmg 2014/04/05 00:40:07 foe->for
175 FileMap::const_iterator found = file_map_.find(include_file);
176 if (found == file_map_.end())
177 return true;
178
179 const TargetVector& targets = found->second;
180
181 // For all targets containing this file, we require that at least one be
182 // a dependency of the current target, and all targets that are dependencies
183 // must have the file listed in the public section.
184 bool found_dependency = false;
185 for (size_t i = 0; i < targets.size(); i++) {
186 // We always allow source files in a target to include headers also in that
187 // target.
188 if (targets[i].target == from_target)
189 return true;
190
191 if (IsDependencyOf(targets[i].target, from_target)) {
192 // The include is in a target that's a proper dependency. Now verify
193 // that the include is a public file.
194 if (!targets[i].is_public) {
195 // Depending on a private header.
196 std::string msg = "The file " + source_file.value() +
197 "\nincludes " + include_file.value() +
198 "\nwhich is private to the target " +
199 targets[i].target->label().GetUserVisibleName(false);
200
201 // TODO(brettw) blame the including file.
202 *err = Err(NULL, "Including a private header.", msg);
203 return false;
204 }
205 found_dependency = true;
206 }
207 }
208
209 if (!found_dependency) {
210 std::string msg =
211 source_file.value() + " includes the header\n" +
212 include_file.value() + " which is not in any dependency of\n" +
213 from_target->label().GetUserVisibleName(false);
214 msg += "\n\nThe include file is in the target(s):\n";
215 for (size_t i = 0; i < targets.size(); i++)
216 msg += " " + targets[i].target->label().GetUserVisibleName(false) + "\n";
217
218 msg += "\nMake sure one of these is a direct or indirect dependency\n"
219 "of " + from_target->label().GetUserVisibleName(false);
220
221 // TODO(brettw) blame the including file.
222 // Probably this means making and leaking an input file for it, and also
223 // tracking the locations for each include.
224 *err = Err(NULL, "Include not allowed.", msg);
225 return false;
226 }
227
228 return true;
229 }
230
231 bool HeaderChecker::IsDependencyOf(const Target* search_for,
232 const Target* search_from) const {
233 std::set<const Target*> checked;
234 return IsDependencyOf(search_for, search_from, &checked);
235 }
236
237 bool HeaderChecker::IsDependencyOf(const Target* search_for,
238 const Target* search_from,
239 std::set<const Target*>* checked) const {
240 if (checked->find(search_for) != checked->end())
241 return false; // Already checked this subtree.
242
243 const LabelTargetVector& deps = search_from->deps();
244 for (size_t i = 0; i < deps.size(); i++) {
245 if (deps[i].ptr == search_for)
246 return true; // Found it.
247
248 // Recursive search.
249 checked->insert(deps[i].ptr);
250 if (IsDependencyOf(search_for, deps[i].ptr, checked))
251 return true;
252 }
253
254 return false;
255 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698