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