| Index: tools/gn/header_checker.cc
|
| diff --git a/tools/gn/header_checker.cc b/tools/gn/header_checker.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..54dfb50c8a7e3fe6bbdc889cb6464faa5af5bb77
|
| --- /dev/null
|
| +++ b/tools/gn/header_checker.cc
|
| @@ -0,0 +1,242 @@
|
| +// Copyright 2014 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "tools/gn/header_checker.h"
|
| +
|
| +#include <algorithm>
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/file_util.h"
|
| +#include "base/message_loop/message_loop.h"
|
| +#include "base/threading/sequenced_worker_pool.h"
|
| +#include "tools/gn/build_settings.h"
|
| +#include "tools/gn/builder.h"
|
| +#include "tools/gn/c_include_iterator.h"
|
| +#include "tools/gn/err.h"
|
| +#include "tools/gn/filesystem_utils.h"
|
| +#include "tools/gn/scheduler.h"
|
| +#include "tools/gn/target.h"
|
| +#include "tools/gn/trace.h"
|
| +
|
| +HeaderChecker::HeaderChecker(const BuildSettings* build_settings,
|
| + const std::vector<const Target*>& targets)
|
| + : main_loop_(base::MessageLoop::current()),
|
| + build_settings_(build_settings) {
|
| + for (size_t i = 0; i < targets.size(); i++)
|
| + AddTargetToFileMap(targets[i]);
|
| +}
|
| +
|
| +HeaderChecker::~HeaderChecker() {
|
| +}
|
| +
|
| +bool HeaderChecker::Run(std::vector<Err>* errors) {
|
| + ScopedTrace trace(TraceItem::TRACE_CHECK_HEADERS, "Check headers");
|
| +
|
| + if (file_map_.empty())
|
| + return true;
|
| +
|
| + scoped_refptr<base::SequencedWorkerPool> pool(
|
| + new base::SequencedWorkerPool(16, "HeaderChecker"));
|
| + for (FileMap::const_iterator file_i = file_map_.begin();
|
| + file_i != file_map_.end(); ++file_i) {
|
| + const TargetVector& vect = file_i->second;
|
| +
|
| + // Only check C-like source files (RC files also have includes).
|
| + SourceFileType type = GetSourceFileType(file_i->first);
|
| + if (type != SOURCE_CC && type != SOURCE_H && type != SOURCE_C &&
|
| + type != SOURCE_M && type != SOURCE_MM && type != SOURCE_RC)
|
| + continue;
|
| +
|
| + for (size_t vect_i = 0; vect_i < vect.size(); ++vect_i) {
|
| + pool->PostWorkerTaskWithShutdownBehavior(
|
| + FROM_HERE,
|
| + base::Bind(&HeaderChecker::DoWork, this,
|
| + vect[vect_i].target, file_i->first),
|
| + base::SequencedWorkerPool::BLOCK_SHUTDOWN);
|
| + }
|
| + }
|
| +
|
| + // After this call we're single-threaded again.
|
| + pool->Shutdown();
|
| +
|
| + if (errors_.empty())
|
| + return true;
|
| + *errors = errors_;
|
| + return false;
|
| +}
|
| +
|
| +void HeaderChecker::DoWork(const Target* target, const SourceFile& file) {
|
| + Err err;
|
| + if (!CheckFile(target, file, &err)) {
|
| + base::AutoLock lock(lock_);
|
| + errors_.push_back(err);
|
| + }
|
| +}
|
| +
|
| +void HeaderChecker::AddTargetToFileMap(const Target* target) {
|
| + // Files in the sources have this public bit by default.
|
| + bool default_public = target->all_headers_public();
|
| +
|
| + // First collect the normal files, they get the default visibility.
|
| + std::map<SourceFile, bool> files_to_public;
|
| + const Target::FileList& sources = target->sources();
|
| + for (size_t i = 0; i < sources.size(); i++)
|
| + files_to_public[sources[i]] = default_public;
|
| +
|
| + // Add in the public files, forcing them to public. This may overwrite some
|
| + // entries, and it may add new ones.
|
| + const Target::FileList& public_list = target->public_headers();
|
| + if (default_public)
|
| + DCHECK(public_list.empty()); // List only used when default is not public.
|
| + for (size_t i = 0; i < public_list.size(); i++)
|
| + files_to_public[public_list[i]] = true;
|
| +
|
| + // Add the merged list to the master list of all files.
|
| + for (std::map<SourceFile, bool>::const_iterator i = files_to_public.begin();
|
| + i != files_to_public.end(); ++i)
|
| + file_map_[i->first].push_back(TargetInfo(target, i->second));
|
| +}
|
| +
|
| +bool HeaderChecker::IsFileInOuputDir(const SourceFile& file) const {
|
| + const std::string& build_dir = build_settings_->build_dir().value();
|
| + return file.value().compare(0, build_dir.size(), build_dir) == 0;
|
| +}
|
| +
|
| +// This current assumes all include paths are relative to the source root
|
| +// which is generally the case for Chromium.
|
| +//
|
| +// A future enhancement would be to search the include path for the target
|
| +// containing the source file containing this include and find the file to
|
| +// handle the cases where people do weird things with the paths.
|
| +SourceFile HeaderChecker::SourceFileForInclude(
|
| + const base::StringPiece& input) const {
|
| + std::string str("//");
|
| + input.AppendToString(&str);
|
| + return SourceFile(str);
|
| +}
|
| +
|
| +bool HeaderChecker::CheckFile(const Target* from_target,
|
| + const SourceFile& file,
|
| + Err* err) const {
|
| + ScopedTrace trace(TraceItem::TRACE_CHECK_HEADER, file.value());
|
| +
|
| + // Sometimes you have generated source files included as sources in another
|
| + // target. These won't exist at checking time. Since we require all generated
|
| + // files to be somewhere in the output tree, we can just check the name to
|
| + // see if they should be skipped.
|
| + if (IsFileInOuputDir(file))
|
| + return true;
|
| +
|
| + base::FilePath path = build_settings_->GetFullPath(file);
|
| + std::string contents;
|
| + if (!base::ReadFileToString(path, &contents)) {
|
| + *err = Err(from_target->defined_from(), "Source file not found.",
|
| + "This target includes as a source:\n " + file.value() +
|
| + "\nwhich was not found.");
|
| + return false;
|
| + }
|
| +
|
| + CIncludeIterator iter(contents);
|
| + base::StringPiece current_include;
|
| + while (iter.GetNextIncludeString(¤t_include)) {
|
| + SourceFile include = SourceFileForInclude(current_include);
|
| + if (!CheckInclude(from_target, file, include, err))
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +// If the file exists, it must be in a dependency of the given target, and it
|
| +// must be public in that dependency.
|
| +bool HeaderChecker::CheckInclude(const Target* from_target,
|
| + const SourceFile& source_file,
|
| + const SourceFile& include_file,
|
| + Err* err) const {
|
| + // Assume if the file isn't declared in our sources that we don't need to
|
| + // check it. It would be nice if we could give an error if this happens, but
|
| + // our include finder is too primitive and returns all includes, even if
|
| + // they're in a #if not executed in the current build. In that case, it's
|
| + // not unusual for the buildfiles to not specify that header at all.
|
| + FileMap::const_iterator found = file_map_.find(include_file);
|
| + if (found == file_map_.end())
|
| + return true;
|
| +
|
| + const TargetVector& targets = found->second;
|
| +
|
| + // For all targets containing this file, we require that at least one be
|
| + // a dependency of the current target, and all targets that are dependencies
|
| + // must have the file listed in the public section.
|
| + bool found_dependency = false;
|
| + for (size_t i = 0; i < targets.size(); i++) {
|
| + // We always allow source files in a target to include headers also in that
|
| + // target.
|
| + if (targets[i].target == from_target)
|
| + return true;
|
| +
|
| + if (IsDependencyOf(targets[i].target, from_target)) {
|
| + // The include is in a target that's a proper dependency. Now verify
|
| + // that the include is a public file.
|
| + if (!targets[i].is_public) {
|
| + // Depending on a private header.
|
| + std::string msg = "The file " + source_file.value() +
|
| + "\nincludes " + include_file.value() +
|
| + "\nwhich is private to the target " +
|
| + targets[i].target->label().GetUserVisibleName(false);
|
| +
|
| + // TODO(brettw) blame the including file.
|
| + *err = Err(NULL, "Including a private header.", msg);
|
| + return false;
|
| + }
|
| + found_dependency = true;
|
| + }
|
| + }
|
| +
|
| + if (!found_dependency) {
|
| + std::string msg =
|
| + source_file.value() + " includes the header\n" +
|
| + include_file.value() + " which is not in any dependency of\n" +
|
| + from_target->label().GetUserVisibleName(false);
|
| + msg += "\n\nThe include file is in the target(s):\n";
|
| + for (size_t i = 0; i < targets.size(); i++)
|
| + msg += " " + targets[i].target->label().GetUserVisibleName(false) + "\n";
|
| +
|
| + msg += "\nMake sure one of these is a direct or indirect dependency\n"
|
| + "of " + from_target->label().GetUserVisibleName(false);
|
| +
|
| + // TODO(brettw) blame the including file.
|
| + // Probably this means making and leaking an input file for it, and also
|
| + // tracking the locations for each include.
|
| + *err = Err(NULL, "Include not allowed.", msg);
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +bool HeaderChecker::IsDependencyOf(const Target* search_for,
|
| + const Target* search_from) const {
|
| + std::set<const Target*> checked;
|
| + return IsDependencyOf(search_for, search_from, &checked);
|
| +}
|
| +
|
| +bool HeaderChecker::IsDependencyOf(const Target* search_for,
|
| + const Target* search_from,
|
| + std::set<const Target*>* checked) const {
|
| + if (checked->find(search_for) != checked->end())
|
| + return false; // Already checked this subtree.
|
| +
|
| + const LabelTargetVector& deps = search_from->deps();
|
| + for (size_t i = 0; i < deps.size(); i++) {
|
| + if (deps[i].ptr == search_for)
|
| + return true; // Found it.
|
| +
|
| + // Recursive search.
|
| + checked->insert(deps[i].ptr);
|
| + if (IsDependencyOf(search_for, deps[i].ptr, checked))
|
| + return true;
|
| + }
|
| +
|
| + return false;
|
| +}
|
|
|