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