• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "gn/header_checker.h"
6 
7 #include <algorithm>
8 
9 #include "base/containers/queue.h"
10 #include "base/files/file_util.h"
11 #include "base/strings/string_util.h"
12 #include "gn/build_settings.h"
13 #include "gn/builder.h"
14 #include "gn/c_include_iterator.h"
15 #include "gn/config.h"
16 #include "gn/config_values_extractors.h"
17 #include "gn/err.h"
18 #include "gn/filesystem_utils.h"
19 #include "gn/scheduler.h"
20 #include "gn/target.h"
21 #include "gn/trace.h"
22 #include "util/worker_pool.h"
23 
24 namespace {
25 
26 struct PublicGeneratedPair {
PublicGeneratedPair__anon42ac35f70111::PublicGeneratedPair27   PublicGeneratedPair() : is_public(false), is_generated(false) {}
28   bool is_public;
29   bool is_generated;
30 };
31 
32 // This class makes InputFiles on the stack as it reads files to check. When
33 // we throw an error, the Err indicates a locatin which has a pointer to
34 // an InputFile that must persist as long as the Err does.
35 //
36 // To make this work, this function creates a clone of the InputFile managed
37 // by the InputFileManager so the error can refer to something that
38 // persists. This means that the current file contents will live as long as
39 // the program, but this is OK since we're erroring out anyway.
CreatePersistentRange(const InputFile & input_file,const LocationRange & range)40 LocationRange CreatePersistentRange(const InputFile& input_file,
41                                     const LocationRange& range) {
42   InputFile* clone_input_file;
43   std::vector<Token>* tokens;              // Don't care about this.
44   std::unique_ptr<ParseNode>* parse_root;  // Don't care about this.
45 
46   g_scheduler->input_file_manager()->AddDynamicInput(
47       input_file.name(), &clone_input_file, &tokens, &parse_root);
48   clone_input_file->SetContents(input_file.contents());
49 
50   return LocationRange(
51       Location(clone_input_file, range.begin().line_number(),
52                range.begin().column_number(), -1 /* TODO(scottmg) */),
53       Location(clone_input_file, range.end().line_number(),
54                range.end().column_number(), -1 /* TODO(scottmg) */));
55 }
56 
57 // Given a reverse dependency chain where the target chain[0]'s includes are
58 // being used by chain[end] and not all deps are public, returns the string
59 // describing the error.
GetDependencyChainPublicError(const HeaderChecker::Chain & chain)60 std::string GetDependencyChainPublicError(const HeaderChecker::Chain& chain) {
61   std::string ret =
62       "The target:\n  " +
63       chain[chain.size() - 1].target->label().GetUserVisibleName(false) +
64       "\nis including a file from the target:\n  " +
65       chain[0].target->label().GetUserVisibleName(false) + "\n";
66 
67   // Invalid chains should always be 0 (no chain) or more than two
68   // (intermediate private dependencies). 1 and 2 are impossible because a
69   // target can always include headers from itself and its direct dependents.
70   DCHECK(chain.size() != 1 && chain.size() != 2);
71   if (chain.empty()) {
72     ret += "There is no dependency chain between these targets.";
73   } else {
74     // Indirect dependency chain, print the chain.
75     ret +=
76         "\nIt's usually best to depend directly on the destination target.\n"
77         "In some cases, the destination target is considered a subcomponent\n"
78         "of an intermediate target. In this case, the intermediate target\n"
79         "should depend publicly on the destination to forward the ability\n"
80         "to include headers.\n"
81         "\n"
82         "Dependency chain (there may also be others):\n";
83 
84     for (int i = static_cast<int>(chain.size()) - 1; i >= 0; i--) {
85       ret.append("  " + chain[i].target->label().GetUserVisibleName(false));
86       if (i != 0) {
87         // Identify private dependencies so the user can see where in the
88         // dependency chain things went bad. Don't list this for the first link
89         // in the chain since direct dependencies are OK, and listing that as
90         // "private" may make people feel like they need to fix it.
91         if (i == static_cast<int>(chain.size()) - 1 || chain[i - 1].is_public)
92           ret.append(" -->");
93         else
94           ret.append(" --[private]-->");
95       }
96       ret.append("\n");
97     }
98   }
99   return ret;
100 }
101 
102 // Returns true if the two targets have the same label not counting the
103 // toolchain.
TargetLabelsMatchExceptToolchain(const Target * a,const Target * b)104 bool TargetLabelsMatchExceptToolchain(const Target* a, const Target* b) {
105   return a->label().dir() == b->label().dir() &&
106          a->label().name() == b->label().name();
107 }
108 
109 // Returns true if the target |annotation_on| includes a friend annotation
110 // that allows |is_marked_friend| as a friend.
FriendMatches(const Target * annotation_on,const Target * is_marked_friend)111 bool FriendMatches(const Target* annotation_on,
112                    const Target* is_marked_friend) {
113   return LabelPattern::VectorMatches(annotation_on->friends(),
114                                      is_marked_friend->label());
115 }
116 
117 }  // namespace
118 
HeaderChecker(const BuildSettings * build_settings,const std::vector<const Target * > & targets,bool check_generated,bool check_system)119 HeaderChecker::HeaderChecker(const BuildSettings* build_settings,
120                              const std::vector<const Target*>& targets,
121                              bool check_generated,
122                              bool check_system)
123     : build_settings_(build_settings),
124       check_generated_(check_generated),
125       check_system_(check_system),
126       lock_(),
127       task_count_cv_() {
128   for (auto* target : targets)
129     AddTargetToFileMap(target, &file_map_);
130 }
131 
132 HeaderChecker::~HeaderChecker() = default;
133 
Run(const std::vector<const Target * > & to_check,bool force_check,std::vector<Err> * errors)134 bool HeaderChecker::Run(const std::vector<const Target*>& to_check,
135                         bool force_check,
136                         std::vector<Err>* errors) {
137   FileMap files_to_check;
138   for (auto* check : to_check) {
139     // This function will get called with all target types, but check only
140     // applies to binary targets.
141     if (check->IsBinary())
142       AddTargetToFileMap(check, &files_to_check);
143   }
144   RunCheckOverFiles(files_to_check, force_check);
145 
146   if (errors_.empty())
147     return true;
148   *errors = errors_;
149   return false;
150 }
151 
RunCheckOverFiles(const FileMap & files,bool force_check)152 void HeaderChecker::RunCheckOverFiles(const FileMap& files, bool force_check) {
153   WorkerPool pool;
154 
155   for (const auto& file : files) {
156     // Only check C-like source files (RC files also have includes).
157     SourceFile::Type type = file.first.type();
158     if (type != SourceFile::SOURCE_CPP && type != SourceFile::SOURCE_H &&
159         type != SourceFile::SOURCE_C && type != SourceFile::SOURCE_M &&
160         type != SourceFile::SOURCE_MM && type != SourceFile::SOURCE_RC)
161       continue;
162 
163     if (!check_generated_) {
164       // If any target marks it as generated, don't check it. We have to check
165       // file_map_, which includes all known files; files only includes those
166       // being checked.
167       bool is_generated = false;
168       for (const auto& vect_i : file_map_[file.first])
169         is_generated |= vect_i.is_generated;
170       if (is_generated)
171         continue;
172     }
173 
174     for (const auto& vect_i : file.second) {
175       if (vect_i.target->check_includes()) {
176         task_count_.Increment();
177         pool.PostTask([this, target = vect_i.target, file = file.first]() {
178           DoWork(target, file);
179         });
180       }
181     }
182   }
183 
184   // Wait for all tasks posted by this method to complete.
185   std::unique_lock<std::mutex> auto_lock(lock_);
186   while (!task_count_.IsZero())
187     task_count_cv_.wait(auto_lock);
188 }
189 
DoWork(const Target * target,const SourceFile & file)190 void HeaderChecker::DoWork(const Target* target, const SourceFile& file) {
191   std::vector<Err> errors;
192   if (!CheckFile(target, file, &errors)) {
193     std::lock_guard<std::mutex> lock(lock_);
194     errors_.insert(errors_.end(), errors.begin(), errors.end());
195   }
196 
197   if (!task_count_.Decrement()) {
198     // Signal |task_count_cv_| when |task_count_| becomes zero.
199     std::unique_lock<std::mutex> auto_lock(lock_);
200     task_count_cv_.notify_one();
201   }
202 }
203 
204 // static
AddTargetToFileMap(const Target * target,FileMap * dest)205 void HeaderChecker::AddTargetToFileMap(const Target* target, FileMap* dest) {
206   // Files in the sources have this public bit by default.
207   bool default_public = target->all_headers_public();
208 
209   std::map<SourceFile, PublicGeneratedPair> files_to_public;
210 
211   // First collect the normal files, they get the default visibility. If you
212   // depend on the compiled target, it should be enough to be able to include
213   // the header.
214   for (const auto& source : target->sources()) {
215     files_to_public[source].is_public = default_public;
216   }
217 
218   // Add in the public files, forcing them to public. This may overwrite some
219   // entries, and it may add new ones.
220   if (default_public)  // List only used when default is not public.
221     DCHECK(target->public_headers().empty());
222   for (const auto& source : target->public_headers()) {
223     files_to_public[source].is_public = true;
224   }
225 
226   // Add in outputs from actions. These are treated as public (since if other
227   // targets can't use them, then there wouldn't be any point in outputting).
228   std::vector<SourceFile> outputs;
229   target->action_values().GetOutputsAsSourceFiles(target, &outputs);
230   for (const auto& output : outputs) {
231     PublicGeneratedPair* pair = &files_to_public[output];
232     pair->is_public = true;
233     pair->is_generated = true;
234   }
235 
236   // Add the merged list to the master list of all files.
237   for (const auto& cur : files_to_public) {
238     (*dest)[cur.first].push_back(
239         TargetInfo(target, cur.second.is_public, cur.second.is_generated));
240   }
241 }
242 
IsFileInOuputDir(const SourceFile & file) const243 bool HeaderChecker::IsFileInOuputDir(const SourceFile& file) const {
244   const std::string& build_dir = build_settings_->build_dir().value();
245   return file.value().compare(0, build_dir.size(), build_dir) == 0;
246 }
247 
SourceFileForInclude(const IncludeStringWithLocation & include,const std::vector<SourceDir> & include_dirs,const InputFile & source_file,Err * err) const248 SourceFile HeaderChecker::SourceFileForInclude(
249     const IncludeStringWithLocation& include,
250     const std::vector<SourceDir>& include_dirs,
251     const InputFile& source_file,
252     Err* err) const {
253   using base::FilePath;
254 
255   Value relative_file_value(nullptr, std::string(include.contents));
256 
257   auto find_predicate = [relative_file_value, err, this](const SourceDir& dir) -> bool {
258         SourceFile include_file =
259             dir.ResolveRelativeFile(relative_file_value, err);
260         return file_map_.find(include_file) != file_map_.end();
261       };
262   if (!include.system_style_include) {
263     const SourceDir& file_dir = source_file.dir();
264     if (find_predicate(file_dir)) {
265       return file_dir.ResolveRelativeFile(relative_file_value, err);
266     }
267   }
268 
269   auto it = std::find_if(
270       include_dirs.begin(), include_dirs.end(), find_predicate);
271 
272   if (it != include_dirs.end())
273     return it->ResolveRelativeFile(relative_file_value, err);
274 
275   return SourceFile();
276 }
277 
CheckFile(const Target * from_target,const SourceFile & file,std::vector<Err> * errors) const278 bool HeaderChecker::CheckFile(const Target* from_target,
279                               const SourceFile& file,
280                               std::vector<Err>* errors) const {
281   ScopedTrace trace(TraceItem::TRACE_CHECK_HEADER, file.value());
282 
283   // Sometimes you have generated source files included as sources in another
284   // target. These won't exist at checking time. Since we require all generated
285   // files to be somewhere in the output tree, we can just check the name to
286   // see if they should be skipped.
287   if (!check_generated_ && IsFileInOuputDir(file))
288     return true;
289 
290   base::FilePath path = build_settings_->GetFullPath(file);
291   std::string contents;
292   if (!base::ReadFileToString(path, &contents)) {
293     // A missing (not yet) generated file is an acceptable problem
294     // considering this code does not understand conditional includes.
295     if (IsFileInOuputDir(file))
296       return true;
297 
298     errors->emplace_back(from_target->defined_from(), "Source file not found.",
299                          "The target:\n  " +
300                              from_target->label().GetUserVisibleName(false) +
301                              "\nhas a source file:\n  " + file.value() +
302                              "\nwhich was not found.");
303     return false;
304   }
305 
306   InputFile input_file(file);
307   input_file.SetContents(contents);
308 
309   std::vector<SourceDir> include_dirs;
310   for (ConfigValuesIterator iter(from_target); !iter.done(); iter.Next()) {
311     const std::vector<SourceDir>& target_include_dirs =
312         iter.cur().include_dirs();
313     include_dirs.insert(include_dirs.end(), target_include_dirs.begin(),
314                         target_include_dirs.end());
315   }
316 
317   size_t error_count_before = errors->size();
318   CIncludeIterator iter(&input_file);
319 
320   IncludeStringWithLocation include;
321 
322   std::set<std::pair<const Target*, const Target*>> no_dependency_cache;
323 
324   while (iter.GetNextIncludeString(&include)) {
325     if (include.system_style_include && !check_system_)
326       continue;
327 
328     Err err;
329     SourceFile included_file = SourceFileForInclude(include,
330                                                     include_dirs,
331                                                     input_file,
332                                                     &err);
333     if (!included_file.is_null()) {
334       CheckInclude(from_target, input_file, included_file, include.location,
335                    &no_dependency_cache, errors);
336     }
337   }
338 
339   return errors->size() == error_count_before;
340 }
341 
342 // If the file exists:
343 //  - The header must be in the public section of a target, or it must
344 //    be in the sources with no public list (everything is implicitly public).
345 //  - The dependency path to the included target must follow only public_deps.
346 //  - If there are multiple targets with the header in it, only one need be
347 //    valid for the check to pass.
CheckInclude(const Target * from_target,const InputFile & source_file,const SourceFile & include_file,const LocationRange & range,std::set<std::pair<const Target *,const Target * >> * no_dependency_cache,std::vector<Err> * errors) const348 void HeaderChecker::CheckInclude(
349     const Target* from_target,
350     const InputFile& source_file,
351     const SourceFile& include_file,
352     const LocationRange& range,
353     std::set<std::pair<const Target*, const Target*>>* no_dependency_cache,
354     std::vector<Err>* errors) const {
355   // Assume if the file isn't declared in our sources that we don't need to
356   // check it. It would be nice if we could give an error if this happens, but
357   // our include finder is too primitive and returns all includes, even if
358   // they're in a #if not executed in the current build. In that case, it's
359   // not unusual for the buildfiles to not specify that header at all.
360   FileMap::const_iterator found = file_map_.find(include_file);
361   if (found == file_map_.end())
362     return;
363 
364   const TargetVector& targets = found->second;
365   Chain chain;  // Prevent reallocating in the loop.
366 
367   // If the file is unknown in the current toolchain (rather than being private
368   // or in a target not visible to the current target), ignore it. This is a
369   // bit of a hack to account for the fact that the include finder doesn't
370   // understand the preprocessor.
371   //
372   // When not cross-compiling, if a platform specific header is conditionally
373   // included in the build, and preprocessor conditions around #includes of
374   // that match the build conditions, everything will be OK because the file
375   // won't be known to GN even though the #include finder identified the file.
376   //
377   // Cross-compiling breaks this. When compiling Android on Linux, for example,
378   // we might see both Linux and Android definitions of a target and know
379   // about the union of all headers in the build. Since the #include finder
380   // ignores preprocessor, we will find the Linux headers in the Android
381   // build and note that a dependency from the Android target to the Linux
382   // one is missing (these might even be the same target in different
383   // toolchains!).
384   bool present_in_current_toolchain = false;
385   for (const auto& target : targets) {
386     if (from_target->label().ToolchainsEqual(target.target->label())) {
387       present_in_current_toolchain = true;
388       break;
389     }
390   }
391   if (!present_in_current_toolchain)
392     return;
393 
394   // For all targets containing this file, we require that at least one be
395   // a direct or public dependency of the current target, and either (1) the
396   // header is public within the target, or (2) there is a friend definition
397   // whitelisting the includor.
398   //
399   // If there is more than one target containing this header, we may encounter
400   // some error cases before finding a good one. This error stores the previous
401   // one encountered, which we may or may not throw away.
402   Err last_error;
403 
404   bool found_dependency = false;
405   for (const auto& target : targets) {
406     // We always allow source files in a target to include headers also in that
407     // target.
408     const Target* to_target = target.target;
409     if (to_target == from_target)
410       return;
411 
412     bool is_permitted_chain = false;
413 
414     bool cached_no_dependency =
415         no_dependency_cache->find(std::make_pair(to_target, from_target)) !=
416         no_dependency_cache->end();
417 
418     bool add_to_cache = !cached_no_dependency;
419 
420     if (!cached_no_dependency &&
421         IsDependencyOf(to_target, from_target, &chain, &is_permitted_chain)) {
422       add_to_cache = false;
423 
424       DCHECK(chain.size() >= 2);
425       DCHECK(chain[0].target == to_target);
426       DCHECK(chain[chain.size() - 1].target == from_target);
427 
428       found_dependency = true;
429 
430       bool effectively_public =
431           target.is_public || FriendMatches(to_target, from_target);
432 
433       if (effectively_public && is_permitted_chain) {
434         // This one is OK, we're done.
435         last_error = Err();
436         break;
437       }
438 
439       // Diagnose the error.
440       if (!effectively_public) {
441         // Danger: must call CreatePersistentRange to put in Err.
442         last_error = Err(CreatePersistentRange(source_file, range),
443                          "Including a private header.",
444                          "This file is private to the target " +
445                              target.target->label().GetUserVisibleName(false));
446       } else if (!is_permitted_chain) {
447         last_error = Err(CreatePersistentRange(source_file, range),
448                          "Can't include this header from here.",
449                          GetDependencyChainPublicError(chain));
450       } else {
451         NOTREACHED();
452       }
453     } else if (to_target->allow_circular_includes_from().find(
454                    from_target->label()) !=
455                to_target->allow_circular_includes_from().end()) {
456       // Not a dependency, but this include is whitelisted from the destination.
457       found_dependency = true;
458       last_error = Err();
459       break;
460     }
461 
462     if (add_to_cache) {
463       no_dependency_cache->emplace(to_target, from_target);
464     }
465   }
466 
467   if (!found_dependency || last_error.has_error()) {
468     if (!found_dependency) {
469       DCHECK(!last_error.has_error());
470       Err err = MakeUnreachableError(source_file, range, from_target, targets);
471       errors->push_back(std::move(err));
472     } else {
473       // Found at least one dependency chain above, but it had an error.
474       errors->push_back(std::move(last_error));
475     }
476     return;
477   }
478 
479   // One thing we didn't check for is targets that expose their dependents
480   // headers in their own public headers.
481   //
482   // Say we have A -> B -> C. If C has public_configs, everybody getting headers
483   // from C should get the configs also or things could be out-of-sync. Above,
484   // we check for A including C's headers directly, but A could also include a
485   // header from B that in turn includes a header from C.
486   //
487   // There are two ways to solve this:
488   //  - If a public header in B includes C, force B to publicly depend on C.
489   //    This is possible to check, but might be super annoying because most
490   //    targets (especially large leaf-node targets) don't declare
491   //    public/private headers and you'll get lots of false positives.
492   //
493   //  - Save the includes found in each file and actually compute the graph of
494   //    includes to detect when A implicitly includes C's header. This will not
495   //    have the annoying false positive problem, but is complex to write.
496 }
497 
IsDependencyOf(const Target * search_for,const Target * search_from,Chain * chain,bool * is_permitted) const498 bool HeaderChecker::IsDependencyOf(const Target* search_for,
499                                    const Target* search_from,
500                                    Chain* chain,
501                                    bool* is_permitted) const {
502   if (search_for == search_from) {
503     // A target is always visible from itself.
504     *is_permitted = true;
505     return false;
506   }
507 
508   // Find the shortest public dependency chain.
509   if (IsDependencyOf(search_for, search_from, true, chain)) {
510     *is_permitted = true;
511     return true;
512   }
513 
514   // If not, try to find any dependency chain at all.
515   if (IsDependencyOf(search_for, search_from, false, chain)) {
516     *is_permitted = false;
517     return true;
518   }
519 
520   *is_permitted = false;
521   return false;
522 }
523 
IsDependencyOf(const Target * search_for,const Target * search_from,bool require_permitted,Chain * chain) const524 bool HeaderChecker::IsDependencyOf(const Target* search_for,
525                                    const Target* search_from,
526                                    bool require_permitted,
527                                    Chain* chain) const {
528   // This method conducts a breadth-first search through the dependency graph
529   // to find a shortest chain from search_from to search_for.
530   //
531   // work_queue maintains a queue of targets which need to be considered as
532   // part of this chain, in the order they were first traversed.
533   //
534   // Each time a new transitive dependency of search_from is discovered for
535   // the first time, it is added to work_queue and a "breadcrumb" is added,
536   // indicating which target it was reached from when first discovered.
537   //
538   // Once this search finds search_for, the breadcrumbs are used to reconstruct
539   // a shortest dependency chain (in reverse order) from search_from to
540   // search_for.
541 
542   std::map<const Target*, ChainLink> breadcrumbs;
543   base::queue<ChainLink> work_queue;
544   work_queue.push(ChainLink(search_from, true));
545 
546   bool first_time = true;
547   while (!work_queue.empty()) {
548     ChainLink cur_link = work_queue.front();
549     const Target* target = cur_link.target;
550     work_queue.pop();
551 
552     if (target == search_for) {
553       // Found it! Reconstruct the chain.
554       chain->clear();
555       while (target != search_from) {
556         chain->push_back(cur_link);
557         cur_link = breadcrumbs[target];
558         target = cur_link.target;
559       }
560       chain->push_back(ChainLink(search_from, true));
561       return true;
562     }
563 
564     // Always consider public dependencies as possibilities.
565     for (const auto& dep : target->public_deps()) {
566       if (breadcrumbs.insert(std::make_pair(dep.ptr, cur_link)).second)
567         work_queue.push(ChainLink(dep.ptr, true));
568     }
569 
570     if (first_time || !require_permitted) {
571       // Consider all dependencies since all target paths are allowed, so add
572       // in private ones. Also do this the first time through the loop, since
573       // a target can include headers from its direct deps regardless of
574       // public/private-ness.
575       first_time = false;
576       for (const auto& dep : target->private_deps()) {
577         if (breadcrumbs.insert(std::make_pair(dep.ptr, cur_link)).second)
578           work_queue.push(ChainLink(dep.ptr, false));
579       }
580     }
581   }
582 
583   return false;
584 }
585 
MakeUnreachableError(const InputFile & source_file,const LocationRange & range,const Target * from_target,const TargetVector & targets)586 Err HeaderChecker::MakeUnreachableError(const InputFile& source_file,
587                                         const LocationRange& range,
588                                         const Target* from_target,
589                                         const TargetVector& targets) {
590   // Normally the toolchains will all match, but when cross-compiling, we can
591   // get targets with more than one toolchain in the list of possibilities.
592   std::vector<const Target*> targets_with_matching_toolchains;
593   std::vector<const Target*> targets_with_other_toolchains;
594   for (const TargetInfo& candidate : targets) {
595     if (candidate.target->toolchain() == from_target->toolchain())
596       targets_with_matching_toolchains.push_back(candidate.target);
597     else
598       targets_with_other_toolchains.push_back(candidate.target);
599   }
600 
601   // It's common when cross-compiling to have a target with the same file in
602   // more than one toolchain. We could output all of them, but this is
603   // generally confusing to people (most end-users won't understand toolchains
604   // well).
605   //
606   // So delete any candidates in other toolchains that also appear in the same
607   // toolchain as the from_target.
608   for (int other_index = 0;
609        other_index < static_cast<int>(targets_with_other_toolchains.size());
610        other_index++) {
611     for (const Target* cur_matching : targets_with_matching_toolchains) {
612       if (TargetLabelsMatchExceptToolchain(
613               cur_matching, targets_with_other_toolchains[other_index])) {
614         // Found a duplicate, erase it.
615         targets_with_other_toolchains.erase(
616             targets_with_other_toolchains.begin() + other_index);
617         other_index--;
618         break;
619       }
620     }
621   }
622 
623   // Only display toolchains on labels if they don't all match.
624   bool include_toolchain = !targets_with_other_toolchains.empty();
625 
626   std::string msg = "It is not in any dependency of\n  " +
627                     from_target->label().GetUserVisibleName(include_toolchain);
628   msg += "\nThe include file is in the target(s):\n";
629   for (auto* target : targets_with_matching_toolchains)
630     msg += "  " + target->label().GetUserVisibleName(include_toolchain) + "\n";
631   for (auto* target : targets_with_other_toolchains)
632     msg += "  " + target->label().GetUserVisibleName(include_toolchain) + "\n";
633   if (targets_with_other_toolchains.size() +
634           targets_with_matching_toolchains.size() >
635       1)
636     msg += "at least one of ";
637   msg += "which should somehow be reachable.";
638 
639   // Danger: must call CreatePersistentRange to put in Err.
640   return Err(CreatePersistentRange(source_file, range), "Include not allowed.",
641              msg);
642 }
643