Revert 264226 "Reduce dependency of TiclInvalidationService on P..."
[chromium-blink-merge.git] / tools / gn / header_checker.cc
blob3b92c8522c32cb3b11184b304907cb791a3546ac
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.
5 #include "tools/gn/header_checker.h"
7 #include <algorithm>
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"
22 namespace {
24 // This class makes InputFiles on the stack as it reads files to check. When
25 // we throw an error, the Err indicates a locatin which has a pointer to
26 // an InputFile that must persist as long as the Err does.
28 // To make this work, this function creates a clone of the InputFile managed
29 // by the InputFileManager so the error can refer to something that
30 // persists. This means that the current file contents will live as long as
31 // the program, but this is OK since we're erroring out anyway.
32 LocationRange CreatePersistentRange(const InputFile& input_file,
33 const LocationRange& range) {
34 InputFile* clone_input_file;
35 std::vector<Token>* tokens; // Don't care about this.
36 scoped_ptr<ParseNode>* parse_root; // Don't care about this.
38 g_scheduler->input_file_manager()->AddDynamicInput(
39 input_file.name(), &clone_input_file, &tokens, &parse_root);
40 clone_input_file->SetContents(input_file.contents());
42 return LocationRange(
43 Location(clone_input_file, range.begin().line_number(),
44 range.begin().char_offset()),
45 Location(clone_input_file, range.end().line_number(),
46 range.end().char_offset()));
49 } // namespace
51 HeaderChecker::HeaderChecker(const BuildSettings* build_settings,
52 const std::vector<const Target*>& targets)
53 : main_loop_(base::MessageLoop::current()),
54 build_settings_(build_settings) {
55 for (size_t i = 0; i < targets.size(); i++)
56 AddTargetToFileMap(targets[i]);
59 HeaderChecker::~HeaderChecker() {
62 bool HeaderChecker::Run(std::vector<Err>* errors) {
63 ScopedTrace trace(TraceItem::TRACE_CHECK_HEADERS, "Check headers");
65 if (file_map_.empty())
66 return true;
68 scoped_refptr<base::SequencedWorkerPool> pool(
69 new base::SequencedWorkerPool(16, "HeaderChecker"));
70 for (FileMap::const_iterator file_i = file_map_.begin();
71 file_i != file_map_.end(); ++file_i) {
72 const TargetVector& vect = file_i->second;
74 // Only check C-like source files (RC files also have includes).
75 SourceFileType type = GetSourceFileType(file_i->first);
76 if (type != SOURCE_CC && type != SOURCE_H && type != SOURCE_C &&
77 type != SOURCE_M && type != SOURCE_MM && type != SOURCE_RC)
78 continue;
80 for (size_t vect_i = 0; vect_i < vect.size(); ++vect_i) {
81 pool->PostWorkerTaskWithShutdownBehavior(
82 FROM_HERE,
83 base::Bind(&HeaderChecker::DoWork, this,
84 vect[vect_i].target, file_i->first),
85 base::SequencedWorkerPool::BLOCK_SHUTDOWN);
89 // After this call we're single-threaded again.
90 pool->Shutdown();
92 if (errors_.empty())
93 return true;
94 *errors = errors_;
95 return false;
98 void HeaderChecker::DoWork(const Target* target, const SourceFile& file) {
99 Err err;
100 if (!CheckFile(target, file, &err)) {
101 base::AutoLock lock(lock_);
102 errors_.push_back(err);
106 void HeaderChecker::AddTargetToFileMap(const Target* target) {
107 // Files in the sources have this public bit by default.
108 bool default_public = target->all_headers_public();
110 // First collect the normal files, they get the default visibility.
111 std::map<SourceFile, bool> files_to_public;
112 const Target::FileList& sources = target->sources();
113 for (size_t i = 0; i < sources.size(); i++)
114 files_to_public[sources[i]] = default_public;
116 // Add in the public files, forcing them to public. This may overwrite some
117 // entries, and it may add new ones.
118 const Target::FileList& public_list = target->public_headers();
119 if (default_public)
120 DCHECK(public_list.empty()); // List only used when default is not public.
121 for (size_t i = 0; i < public_list.size(); i++)
122 files_to_public[public_list[i]] = true;
124 // Add the merged list to the master list of all files.
125 for (std::map<SourceFile, bool>::const_iterator i = files_to_public.begin();
126 i != files_to_public.end(); ++i)
127 file_map_[i->first].push_back(TargetInfo(target, i->second));
130 bool HeaderChecker::IsFileInOuputDir(const SourceFile& file) const {
131 const std::string& build_dir = build_settings_->build_dir().value();
132 return file.value().compare(0, build_dir.size(), build_dir) == 0;
135 // This current assumes all include paths are relative to the source root
136 // which is generally the case for Chromium.
138 // A future enhancement would be to search the include path for the target
139 // containing the source file containing this include and find the file to
140 // handle the cases where people do weird things with the paths.
141 SourceFile HeaderChecker::SourceFileForInclude(
142 const base::StringPiece& input) const {
143 std::string str("//");
144 input.AppendToString(&str);
145 return SourceFile(str);
148 bool HeaderChecker::CheckFile(const Target* from_target,
149 const SourceFile& file,
150 Err* err) const {
151 ScopedTrace trace(TraceItem::TRACE_CHECK_HEADER, file.value());
153 // Sometimes you have generated source files included as sources in another
154 // target. These won't exist at checking time. Since we require all generated
155 // files to be somewhere in the output tree, we can just check the name to
156 // see if they should be skipped.
157 if (IsFileInOuputDir(file))
158 return true;
160 base::FilePath path = build_settings_->GetFullPath(file);
161 std::string contents;
162 if (!base::ReadFileToString(path, &contents)) {
163 *err = Err(from_target->defined_from(), "Source file not found.",
164 "This target includes as a source:\n " + file.value() +
165 "\nwhich was not found.");
166 return false;
169 InputFile input_file(file);
170 input_file.SetContents(contents);
172 CIncludeIterator iter(&input_file);
173 base::StringPiece current_include;
174 LocationRange range;
175 while (iter.GetNextIncludeString(&current_include, &range)) {
176 SourceFile include = SourceFileForInclude(current_include);
177 if (!CheckInclude(from_target, input_file, include, range, err))
178 return false;
181 return true;
184 // If the file exists, it must be in a dependency of the given target, and it
185 // must be public in that dependency.
186 bool HeaderChecker::CheckInclude(const Target* from_target,
187 const InputFile& source_file,
188 const SourceFile& include_file,
189 const LocationRange& range,
190 Err* err) const {
191 // Assume if the file isn't declared in our sources that we don't need to
192 // check it. It would be nice if we could give an error if this happens, but
193 // our include finder is too primitive and returns all includes, even if
194 // they're in a #if not executed in the current build. In that case, it's
195 // not unusual for the buildfiles to not specify that header at all.
196 FileMap::const_iterator found = file_map_.find(include_file);
197 if (found == file_map_.end())
198 return true;
200 const TargetVector& targets = found->second;
202 // For all targets containing this file, we require that at least one be
203 // a dependency of the current target, and all targets that are dependencies
204 // must have the file listed in the public section.
205 bool found_dependency = false;
206 for (size_t i = 0; i < targets.size(); i++) {
207 // We always allow source files in a target to include headers also in that
208 // target.
209 if (targets[i].target == from_target)
210 return true;
212 if (IsDependencyOf(targets[i].target, from_target)) {
213 // The include is in a target that's a proper dependency. Verify that
214 // the including target has visibility.
215 if (!targets[i].target->visibility().CanSeeMe(from_target->label())) {
216 std::string msg = "The included file is in " +
217 targets[i].target->label().GetUserVisibleName(false) +
218 "\nwhich is not visible from " +
219 from_target->label().GetUserVisibleName(false) +
220 "\n(see \"gn help visibility\").";
222 // Danger: must call CreatePersistentRange to put in Err.
223 *err = Err(CreatePersistentRange(source_file, range),
224 "Including a header from non-visible target.", msg);
225 return false;
228 // The file must also be public in the target.
229 if (!targets[i].is_public) {
230 // Danger: must call CreatePersistentRange to put in Err.
231 *err = Err(CreatePersistentRange(source_file, range),
232 "Including a private header.",
233 "This file is private to the target " +
234 targets[i].target->label().GetUserVisibleName(false));
235 return false;
237 found_dependency = true;
241 if (!found_dependency) {
242 std::string msg = "It is not in any dependency of " +
243 from_target->label().GetUserVisibleName(false);
244 msg += "\nThe include file is in the target(s):\n";
245 for (size_t i = 0; i < targets.size(); i++)
246 msg += " " + targets[i].target->label().GetUserVisibleName(false) + "\n";
247 if (targets.size() > 1)
248 msg += "at least one of ";
249 msg += "which should somehow be reachable from " +
250 from_target->label().GetUserVisibleName(false);
252 // Danger: must call CreatePersistentRange to put in Err.
253 *err = Err(CreatePersistentRange(source_file, range),
254 "Include not allowed.", msg);
255 return false;
258 return true;
261 bool HeaderChecker::IsDependencyOf(const Target* search_for,
262 const Target* search_from) const {
263 std::set<const Target*> checked;
264 return IsDependencyOf(search_for, search_from, &checked);
267 bool HeaderChecker::IsDependencyOf(const Target* search_for,
268 const Target* search_from,
269 std::set<const Target*>* checked) const {
270 if (checked->find(search_for) != checked->end())
271 return false; // Already checked this subtree.
273 const LabelTargetVector& deps = search_from->deps();
274 for (size_t i = 0; i < deps.size(); i++) {
275 if (deps[i].ptr == search_for)
276 return true; // Found it.
278 // Recursive search.
279 checked->insert(deps[i].ptr);
280 if (IsDependencyOf(search_for, deps[i].ptr, checked))
281 return true;
284 return false;