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)); |
} |
} |
} |