OLD | NEW |
---|---|
(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(¤t_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 } | |
OLD | NEW |