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

Unified Diff: tools/gn/header_checker.cc

Issue 584683002: Improve GN header checker, make //ui pass. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: merge Created 6 years, 3 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « tools/gn/header_checker.h ('k') | tools/gn/header_checker_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/gn/header_checker.cc
diff --git a/tools/gn/header_checker.cc b/tools/gn/header_checker.cc
index 03f8d2b090ff7a274d1f301b6d64c92398332e21..78c619bbaaa333ad698e135d025f6af8cdd0bb35 100644
--- a/tools/gn/header_checker.cc
+++ b/tools/gn/header_checker.cc
@@ -37,7 +37,7 @@ struct PublicGeneratedPair {
SourceFile RemoveRootGenDirFromFile(const Target* target,
const SourceFile& file) {
const SourceDir& gen = target->settings()->toolchain_gen_dir();
- if (StartsWithASCII(file.value(), gen.value(), true))
+ if (!gen.is_null() && StartsWithASCII(file.value(), gen.value(), true))
return SourceFile("//" + file.value().substr(gen.value().size()));
return file;
}
@@ -71,12 +71,20 @@ LocationRange CreatePersistentRange(const InputFile& input_file,
// being used by chain[end] and not all deps are public, returns the string
// describing the error.
std::string GetDependencyChainPublicError(
- const std::vector<const Target*>& chain) {
- std::string ret = "The target " +
- chain[chain.size() - 1]->label().GetUserVisibleName(false) + "\n" +
- "is including a file in " + chain[0]->label().GetUserVisibleName(false) +
+ const HeaderChecker::Chain& chain) {
+ std::string ret = "The target:\n " +
+ chain[chain.size() - 1].target->label().GetUserVisibleName(false) +
+ "\nis including a file from the target:\n " +
+ chain[0].target->label().GetUserVisibleName(false) +
"\n";
- if (chain.size() > 2) {
+
+ // Invalid chains should always be 0 (no chain) or more than two
+ // (intermediate private dependencies). 1 and 2 are impossible because a
+ // target can always include headers from itself and its direct dependents.
+ DCHECK(chain.size() != 1 && chain.size() != 2);
+ if (chain.empty()) {
+ ret += "There is no dependency chain between these targets.";
+ } else {
// Indirect dependency chain, print the chain.
ret += "\nIt's usually best to depend directly on the destination target.\n"
"In some cases, the destination target is considered a subcomponent\n"
@@ -84,12 +92,20 @@ std::string GetDependencyChainPublicError(
"should depend publicly on the destination to forward the ability\n"
"to include headers.\n"
"\n"
- "Dependency chain (there may be others):\n";
+ "Dependency chain (there may also be others):\n";
for (int i = static_cast<int>(chain.size()) - 1; i >= 0; i--) {
- ret.append(" " + chain[i]->label().GetUserVisibleName(false));
- if (i != 0)
- ret.append(" ->");
+ ret.append(" " + chain[i].target->label().GetUserVisibleName(false));
+ if (i != 0) {
+ // Identify private dependencies so the user can see where in the
+ // dependency chain things went bad. Don't list this for the first link
+ // in the chain since direct dependencies are OK, and listing that as
+ // "private" may make people feel like they need to fix it.
+ if (i == static_cast<int>(chain.size()) - 1 || chain[i - 1].is_public)
+ ret.append(" -->");
+ else
+ ret.append(" --[private]-->");
+ }
ret.append("\n");
}
}
@@ -146,15 +162,17 @@ void HeaderChecker::RunCheckOverFiles(const FileMap& files, bool force_check) {
continue;
// Do a first pass to find if this should be skipped. All targets including
- // this source file must exclude it from checking, or it must be generated.
+ // this source file must exclude it from checking, or any target
+ // must mark it as generated (for cases where one target generates a file,
+ // and another lists it as a source to compile it).
if (!force_check) {
bool check_includes = false;
+ bool is_generated = false;
for (size_t vect_i = 0; vect_i < vect.size(); ++vect_i) {
- check_includes |=
- vect[vect_i].target->check_includes() &&
- !vect[vect_i].is_generated;
+ check_includes |= vect[vect_i].target->check_includes();
+ is_generated |= vect[vect_i].is_generated;
}
- if (!check_includes)
+ if (!check_includes || is_generated)
continue;
}
@@ -184,19 +202,28 @@ void HeaderChecker::AddTargetToFileMap(const Target* target, FileMap* dest) {
// 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, PublicGeneratedPair> files_to_public;
+
+ // First collect the normal files, they get the default visibility. Always
+ // trim the root gen dir if it exists. This will only exist on outputs of an
+ // action, but those are often then wired into the sources of a compiled
+ // target to actually compile generated code. If you depend on the compiled
+ // target, it should be enough to be able to include the header.
const Target::FileList& sources = target->sources();
- for (size_t i = 0; i < sources.size(); i++)
- files_to_public[sources[i]].is_public = default_public;
+ for (size_t i = 0; i < sources.size(); i++) {
+ SourceFile file = RemoveRootGenDirFromFile(target, sources[i]);
+ files_to_public[file].is_public = 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]].is_public = true;
+ for (size_t i = 0; i < public_list.size(); i++) {
+ SourceFile file = RemoveRootGenDirFromFile(target, public_list[i]);
+ files_to_public[file].is_public = true;
+ }
// Add in outputs from actions. These are treated as public (since if other
// targets can't use them, then there wouldn't be any point in outputting).
@@ -256,7 +283,8 @@ bool HeaderChecker::CheckFile(const Target* from_target,
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() +
+ "The target:\n " + from_target->label().GetUserVisibleName(false) +
+ "\nhas a source file:\n " + file.value() +
"\nwhich was not found.");
return false;
}
@@ -300,11 +328,17 @@ bool HeaderChecker::CheckInclude(const Target* from_target,
return true;
const TargetVector& targets = found->second;
- std::vector<const Target*> chain; // Prevent reallocating in the loop.
+ Chain chain; // Prevent reallocating in the loop.
// 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.
+ // a direct or public dependency of the current target, and that the header
+ // is public within the target.
+ //
+ // If there is more than one target containing this header, we may encounter
+ // some error cases before finding a good one. This error stores the previous
+ // one encountered, which we may or may not throw away.
+ Err last_error;
+
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
@@ -313,40 +347,49 @@ bool HeaderChecker::CheckInclude(const Target* from_target,
if (to_target == from_target)
return true;
- bool is_public_dep_chain = false;
- if (IsDependencyOf(to_target, from_target, &chain, &is_public_dep_chain)) {
+ bool is_permitted_chain = false;
+ if (IsDependencyOf(to_target, from_target, &chain, &is_permitted_chain)) {
DCHECK(chain.size() >= 2);
- DCHECK(chain[0] == to_target);
- DCHECK(chain[chain.size() - 1] == from_target);
+ DCHECK(chain[0].target == to_target);
+ DCHECK(chain[chain.size() - 1].target == from_target);
- // The file must be public in the target.
- if (!targets[i].is_public) {
- // Danger: must call CreatePersistentRange to put in Err.
- *err = Err(CreatePersistentRange(source_file, range),
- "Including a private header.",
- "This file is private to the target " +
- targets[i].target->label().GetUserVisibleName(false));
- return false;
- }
+ found_dependency = true;
- // The dependency chain must be public.
- if (!is_public_dep_chain) {
- *err = Err(CreatePersistentRange(source_file, range),
- "Can't include this header from here.",
- GetDependencyChainPublicError(chain));
- return false;
+ if (targets[i].is_public && is_permitted_chain) {
+ // This one is OK, we're done.
+ last_error = Err();
+ break;
}
- found_dependency = true;
+ // Diagnose the error.
+ if (!targets[i].is_public) {
+ // Danger: must call CreatePersistentRange to put in Err.
+ last_error = Err(
+ CreatePersistentRange(source_file, range),
+ "Including a private header.",
+ "This file is private to the target " +
+ targets[i].target->label().GetUserVisibleName(false));
+ } else if (!is_permitted_chain) {
+ last_error = Err(
+ CreatePersistentRange(source_file, range),
+ "Can't include this header from here.",
+ GetDependencyChainPublicError(chain));
+ } else {
+ NOTREACHED();
+ }
} else if (
to_target->allow_circular_includes_from().find(from_target->label()) !=
to_target->allow_circular_includes_from().end()) {
// Not a dependency, but this include is whitelisted from the destination.
found_dependency = true;
+ last_error = Err();
+ break;
}
}
if (!found_dependency) {
+ DCHECK(!last_error.has_error());
+
std::string msg = "It is not in any dependency of " +
from_target->label().GetUserVisibleName(false);
msg += "\nThe include file is in the target(s):\n";
@@ -362,6 +405,11 @@ bool HeaderChecker::CheckInclude(const Target* from_target,
"Include not allowed.", msg);
return false;
}
+ if (last_error.has_error()) {
+ // Found at least one dependency chain above, but it had an error.
+ *err = last_error;
+ return false;
+ }
// One thing we didn't check for is targets that expose their dependents
// headers in their own public headers.
@@ -372,11 +420,10 @@ bool HeaderChecker::CheckInclude(const Target* from_target,
// header from B that in turn includes a header from C.
//
// There are two ways to solve this:
- // - If a public header in B includes C, force B to forward C's direct
- // dependent configs. This is possible to check, but might be super
- // annoying because most targets (especially large leaf-node targets)
- // don't declare public/private headers and you'll get lots of false
- // positives.
+ // - If a public header in B includes C, force B to publicly depend on C.
+ // This is possible to check, but might be super annoying because most
+ // targets (especially large leaf-node targets) don't declare
+ // public/private headers and you'll get lots of false positives.
//
// - Save the includes found in each file and actually compute the graph of
// includes to detect when A implicitly includes C's header. This will not
@@ -387,34 +434,34 @@ bool HeaderChecker::CheckInclude(const Target* from_target,
bool HeaderChecker::IsDependencyOf(const Target* search_for,
const Target* search_from,
- std::vector<const Target*>* chain,
- bool* is_public) const {
+ Chain* chain,
+ bool* is_permitted) const {
if (search_for == search_from) {
// A target is always visible from itself.
- *is_public = true;
+ *is_permitted = true;
return false;
}
// Find the shortest public dependency chain.
if (IsDependencyOf(search_for, search_from, true, chain)) {
- *is_public = true;
+ *is_permitted = true;
return true;
}
// If not, try to find any dependency chain at all.
if (IsDependencyOf(search_for, search_from, false, chain)) {
- *is_public = false;
+ *is_permitted = false;
return true;
}
- *is_public = false;
+ *is_permitted = false;
return false;
}
bool HeaderChecker::IsDependencyOf(const Target* search_for,
const Target* search_from,
- bool require_public,
- std::vector<const Target*>* chain) const {
+ bool require_permitted,
+ Chain* chain) const {
// This method conducts a breadth-first search through the dependency graph
// to find a shortest chain from search_from to search_for.
//
@@ -429,40 +476,47 @@ bool HeaderChecker::IsDependencyOf(const Target* search_for,
// a shortest dependency chain (in reverse order) from search_from to
// search_for.
- std::map<const Target*, const Target*> breadcrumbs;
- std::queue<const Target*> work_queue;
- work_queue.push(search_from);
+ std::map<const Target*, ChainLink> breadcrumbs;
+ std::queue<ChainLink> work_queue;
+ work_queue.push(ChainLink(search_from, true));
+ bool first_time = true;
while (!work_queue.empty()) {
- const Target* target = work_queue.front();
+ ChainLink cur_link = work_queue.front();
+ const Target* target = cur_link.target;
work_queue.pop();
if (target == search_for) {
// Found it! Reconstruct the chain.
chain->clear();
while (target != search_from) {
- chain->push_back(target);
- target = breadcrumbs[target];
+ chain->push_back(cur_link);
+ cur_link = breadcrumbs[target];
+ target = cur_link.target;
}
- chain->push_back(search_from);
+ chain->push_back(ChainLink(search_from, true));
return true;
}
// Always consider public dependencies as possibilities.
const LabelTargetVector& public_deps = target->public_deps();
for (size_t i = 0; i < public_deps.size(); i++) {
- if (breadcrumbs.insert(std::make_pair(public_deps[i].ptr, target)).second)
- work_queue.push(public_deps[i].ptr);
+ if (breadcrumbs.insert(
+ std::make_pair(public_deps[i].ptr, cur_link)).second)
+ work_queue.push(ChainLink(public_deps[i].ptr, true));
}
- if (!require_public) {
+ if (first_time || !require_permitted) {
// Consider all dependencies since all target paths are allowed, so add
- // in private ones.
+ // in private ones. Also do this the first time through the loop, since
+ // a target can include headers from its direct deps regardless of
+ // public/private-ness.
+ first_time = false;
const LabelTargetVector& private_deps = target->private_deps();
for (size_t i = 0; i < private_deps.size(); i++) {
- if (breadcrumbs.insert(std::make_pair(private_deps[i].ptr, target))
- .second)
- work_queue.push(private_deps[i].ptr);
+ if (breadcrumbs.insert(
+ std::make_pair(private_deps[i].ptr, cur_link)).second)
+ work_queue.push(ChainLink(private_deps[i].ptr, false));
}
}
}
« no previous file with comments | « tools/gn/header_checker.h ('k') | tools/gn/header_checker_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698