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/swift_values.h"
21 #include "gn/target.h"
22 #include "gn/trace.h"
23 #include "util/worker_pool.h"
24
25 namespace {
26
27 struct PublicGeneratedPair {
PublicGeneratedPair__anona8f4ff940111::PublicGeneratedPair28 PublicGeneratedPair() : is_public(false), is_generated(false) {}
29 bool is_public;
30 bool is_generated;
31 };
32
33 // This class makes InputFiles on the stack as it reads files to check. When
34 // we throw an error, the Err indicates a locatin which has a pointer to
35 // an InputFile that must persist as long as the Err does.
36 //
37 // To make this work, this function creates a clone of the InputFile managed
38 // by the InputFileManager so the error can refer to something that
39 // persists. This means that the current file contents will live as long as
40 // the program, but this is OK since we're erroring out anyway.
CreatePersistentRange(const InputFile & input_file,const LocationRange & range)41 LocationRange CreatePersistentRange(const InputFile& input_file,
42 const LocationRange& range) {
43 InputFile* clone_input_file;
44 std::vector<Token>* tokens; // Don't care about this.
45 std::unique_ptr<ParseNode>* parse_root; // Don't care about this.
46
47 g_scheduler->input_file_manager()->AddDynamicInput(
48 input_file.name(), &clone_input_file, &tokens, &parse_root);
49 clone_input_file->SetContents(input_file.contents());
50
51 return LocationRange(Location(clone_input_file, range.begin().line_number(),
52 range.begin().column_number()),
53 Location(clone_input_file, range.end().line_number(),
54 range.end().column_number()));
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 const SourceFile::Type type = file.first.GetType();
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 // If target generates a swiftmodule, then
227 // - it may use a bridge header which has default visibility
228 // - it may generate public header which must be considered public
229 if (target->builds_swift_module()) {
230 const SourceFile& bridge_header = target->swift_values().bridge_header();
231 if (!bridge_header.is_null()) {
232 files_to_public[bridge_header].is_public = default_public;
233 }
234
235 std::vector<SourceFile> outputs;
236 target->swift_values().GetOutputsAsSourceFiles(target, &outputs);
237
238 for (const SourceFile& output : outputs) {
239 if (output.GetType() == SourceFile::SOURCE_H) {
240 PublicGeneratedPair* pair = &files_to_public[output];
241 pair->is_public = true;
242 pair->is_generated = true;
243 }
244 }
245 }
246
247 // Add in outputs from actions. These are treated as public (since if other
248 // targets can't use them, then there wouldn't be any point in outputting).
249 std::vector<SourceFile> outputs;
250 target->action_values().GetOutputsAsSourceFiles(target, &outputs);
251 for (const auto& output : outputs) {
252 PublicGeneratedPair* pair = &files_to_public[output];
253 pair->is_public = true;
254 pair->is_generated = true;
255 }
256
257 // Add the merged list to the master list of all files.
258 for (const auto& cur : files_to_public) {
259 (*dest)[cur.first].push_back(
260 TargetInfo(target, cur.second.is_public, cur.second.is_generated));
261 }
262 }
263
IsFileInOuputDir(const SourceFile & file) const264 bool HeaderChecker::IsFileInOuputDir(const SourceFile& file) const {
265 const std::string& build_dir = build_settings_->build_dir().value();
266 return file.value().compare(0, build_dir.size(), build_dir) == 0;
267 }
268
SourceFileForInclude(const IncludeStringWithLocation & include,const std::vector<SourceDir> & include_dirs,const InputFile & source_file,Err * err) const269 SourceFile HeaderChecker::SourceFileForInclude(
270 const IncludeStringWithLocation& include,
271 const std::vector<SourceDir>& include_dirs,
272 const InputFile& source_file,
273 Err* err) const {
274 using base::FilePath;
275
276 Value relative_file_value(nullptr, std::string(include.contents));
277
278 auto find_predicate = [relative_file_value, err,
279 this](const SourceDir& dir) -> bool {
280 SourceFile include_file = dir.ResolveRelativeFile(relative_file_value, err);
281 return file_map_.find(include_file) != file_map_.end();
282 };
283 if (!include.system_style_include) {
284 const SourceDir& file_dir = source_file.dir();
285 if (find_predicate(file_dir)) {
286 return file_dir.ResolveRelativeFile(relative_file_value, err);
287 }
288 }
289
290 auto it =
291 std::find_if(include_dirs.begin(), include_dirs.end(), find_predicate);
292
293 if (it != include_dirs.end())
294 return it->ResolveRelativeFile(relative_file_value, err);
295
296 return SourceFile();
297 }
298
CheckFile(const Target * from_target,const SourceFile & file,std::vector<Err> * errors) const299 bool HeaderChecker::CheckFile(const Target* from_target,
300 const SourceFile& file,
301 std::vector<Err>* errors) const {
302 ScopedTrace trace(TraceItem::TRACE_CHECK_HEADER, file.value());
303
304 // Sometimes you have generated source files included as sources in another
305 // target. These won't exist at checking time. Since we require all generated
306 // files to be somewhere in the output tree, we can just check the name to
307 // see if they should be skipped.
308 if (!check_generated_ && IsFileInOuputDir(file))
309 return true;
310
311 base::FilePath path = build_settings_->GetFullPath(file);
312 std::string contents;
313 if (!base::ReadFileToString(path, &contents)) {
314 // A missing (not yet) generated file is an acceptable problem
315 // considering this code does not understand conditional includes.
316 if (IsFileInOuputDir(file))
317 return true;
318
319 errors->emplace_back(from_target->defined_from(), "Source file not found.",
320 "The target:\n " +
321 from_target->label().GetUserVisibleName(false) +
322 "\nhas a source file:\n " + file.value() +
323 "\nwhich was not found.");
324 return false;
325 }
326
327 InputFile input_file(file);
328 input_file.SetContents(contents);
329
330 std::vector<SourceDir> include_dirs;
331 for (ConfigValuesIterator iter(from_target); !iter.done(); iter.Next()) {
332 const std::vector<SourceDir>& target_include_dirs =
333 iter.cur().include_dirs();
334 include_dirs.insert(include_dirs.end(), target_include_dirs.begin(),
335 target_include_dirs.end());
336 }
337
338 size_t error_count_before = errors->size();
339 CIncludeIterator iter(&input_file);
340
341 IncludeStringWithLocation include;
342
343 std::set<std::pair<const Target*, const Target*>> no_dependency_cache;
344
345 while (iter.GetNextIncludeString(&include)) {
346 if (include.system_style_include && !check_system_)
347 continue;
348
349 Err err;
350 SourceFile included_file =
351 SourceFileForInclude(include, include_dirs, input_file, &err);
352 if (!included_file.is_null()) {
353 CheckInclude(from_target, input_file, included_file, include.location,
354 &no_dependency_cache, errors);
355 }
356 }
357
358 return errors->size() == error_count_before;
359 }
360
361 // If the file exists:
362 // - The header must be in the public section of a target, or it must
363 // be in the sources with no public list (everything is implicitly public).
364 // - The dependency path to the included target must follow only public_deps.
365 // - If there are multiple targets with the header in it, only one need be
366 // 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) const367 void HeaderChecker::CheckInclude(
368 const Target* from_target,
369 const InputFile& source_file,
370 const SourceFile& include_file,
371 const LocationRange& range,
372 std::set<std::pair<const Target*, const Target*>>* no_dependency_cache,
373 std::vector<Err>* errors) const {
374 // Assume if the file isn't declared in our sources that we don't need to
375 // check it. It would be nice if we could give an error if this happens, but
376 // our include finder is too primitive and returns all includes, even if
377 // they're in a #if not executed in the current build. In that case, it's
378 // not unusual for the buildfiles to not specify that header at all.
379 FileMap::const_iterator found = file_map_.find(include_file);
380 if (found == file_map_.end())
381 return;
382
383 const TargetVector& targets = found->second;
384 Chain chain; // Prevent reallocating in the loop.
385
386 // If the file is unknown in the current toolchain (rather than being private
387 // or in a target not visible to the current target), ignore it. This is a
388 // bit of a hack to account for the fact that the include finder doesn't
389 // understand the preprocessor.
390 //
391 // When not cross-compiling, if a platform specific header is conditionally
392 // included in the build, and preprocessor conditions around #includes of
393 // that match the build conditions, everything will be OK because the file
394 // won't be known to GN even though the #include finder identified the file.
395 //
396 // Cross-compiling breaks this. When compiling Android on Linux, for example,
397 // we might see both Linux and Android definitions of a target and know
398 // about the union of all headers in the build. Since the #include finder
399 // ignores preprocessor, we will find the Linux headers in the Android
400 // build and note that a dependency from the Android target to the Linux
401 // one is missing (these might even be the same target in different
402 // toolchains!).
403 bool present_in_current_toolchain = false;
404 for (const auto& target : targets) {
405 if (from_target->label().ToolchainsEqual(target.target->label())) {
406 present_in_current_toolchain = true;
407 break;
408 }
409 }
410 if (!present_in_current_toolchain)
411 return;
412
413 // For all targets containing this file, we require that at least one be
414 // a direct or public dependency of the current target, and either (1) the
415 // header is public within the target, or (2) there is a friend definition
416 // whitelisting the includor.
417 //
418 // If there is more than one target containing this header, we may encounter
419 // some error cases before finding a good one. This error stores the previous
420 // one encountered, which we may or may not throw away.
421 Err last_error;
422
423 bool found_dependency = false;
424 for (const auto& target : targets) {
425 // We always allow source files in a target to include headers also in that
426 // target.
427 const Target* to_target = target.target;
428 if (to_target == from_target)
429 return;
430
431 bool is_permitted_chain = false;
432
433 bool cached_no_dependency =
434 no_dependency_cache->find(std::make_pair(to_target, from_target)) !=
435 no_dependency_cache->end();
436
437 bool add_to_cache = !cached_no_dependency;
438
439 if (!cached_no_dependency &&
440 IsDependencyOf(to_target, from_target, &chain, &is_permitted_chain)) {
441 add_to_cache = false;
442
443 DCHECK(chain.size() >= 2);
444 DCHECK(chain[0].target == to_target);
445 DCHECK(chain[chain.size() - 1].target == from_target);
446
447 found_dependency = true;
448
449 bool effectively_public =
450 target.is_public || FriendMatches(to_target, from_target);
451
452 if (effectively_public && is_permitted_chain) {
453 // This one is OK, we're done.
454 last_error = Err();
455 break;
456 }
457
458 // Diagnose the error.
459 if (!effectively_public) {
460 // Danger: must call CreatePersistentRange to put in Err.
461 last_error = Err(CreatePersistentRange(source_file, range),
462 "Including a private header.",
463 "This file is private to the target " +
464 target.target->label().GetUserVisibleName(false));
465 } else if (!is_permitted_chain) {
466 last_error = Err(CreatePersistentRange(source_file, range),
467 "Can't include this header from here.",
468 GetDependencyChainPublicError(chain));
469 } else {
470 NOTREACHED();
471 }
472 } else if (to_target->allow_circular_includes_from().find(
473 from_target->label()) !=
474 to_target->allow_circular_includes_from().end()) {
475 // Not a dependency, but this include is whitelisted from the destination.
476 found_dependency = true;
477 last_error = Err();
478 break;
479 }
480
481 if (add_to_cache) {
482 no_dependency_cache->emplace(to_target, from_target);
483 }
484 }
485
486 if (!found_dependency || last_error.has_error()) {
487 if (!found_dependency) {
488 DCHECK(!last_error.has_error());
489 Err err = MakeUnreachableError(source_file, range, from_target, targets);
490 errors->push_back(std::move(err));
491 } else {
492 // Found at least one dependency chain above, but it had an error.
493 errors->push_back(std::move(last_error));
494 }
495 return;
496 }
497
498 // One thing we didn't check for is targets that expose their dependents
499 // headers in their own public headers.
500 //
501 // Say we have A -> B -> C. If C has public_configs, everybody getting headers
502 // from C should get the configs also or things could be out-of-sync. Above,
503 // we check for A including C's headers directly, but A could also include a
504 // header from B that in turn includes a header from C.
505 //
506 // There are two ways to solve this:
507 // - If a public header in B includes C, force B to publicly depend on C.
508 // This is possible to check, but might be super annoying because most
509 // targets (especially large leaf-node targets) don't declare
510 // public/private headers and you'll get lots of false positives.
511 //
512 // - Save the includes found in each file and actually compute the graph of
513 // includes to detect when A implicitly includes C's header. This will not
514 // have the annoying false positive problem, but is complex to write.
515 }
516
IsDependencyOf(const Target * search_for,const Target * search_from,Chain * chain,bool * is_permitted) const517 bool HeaderChecker::IsDependencyOf(const Target* search_for,
518 const Target* search_from,
519 Chain* chain,
520 bool* is_permitted) const {
521 if (search_for == search_from) {
522 // A target is always visible from itself.
523 *is_permitted = true;
524 return false;
525 }
526
527 // Find the shortest public dependency chain.
528 if (IsDependencyOf(search_for, search_from, true, chain)) {
529 *is_permitted = true;
530 return true;
531 }
532
533 // If not, try to find any dependency chain at all.
534 if (IsDependencyOf(search_for, search_from, false, chain)) {
535 *is_permitted = false;
536 return true;
537 }
538
539 *is_permitted = false;
540 return false;
541 }
542
IsDependencyOf(const Target * search_for,const Target * search_from,bool require_permitted,Chain * chain) const543 bool HeaderChecker::IsDependencyOf(const Target* search_for,
544 const Target* search_from,
545 bool require_permitted,
546 Chain* chain) const {
547 // This method conducts a breadth-first search through the dependency graph
548 // to find a shortest chain from search_from to search_for.
549 //
550 // work_queue maintains a queue of targets which need to be considered as
551 // part of this chain, in the order they were first traversed.
552 //
553 // Each time a new transitive dependency of search_from is discovered for
554 // the first time, it is added to work_queue and a "breadcrumb" is added,
555 // indicating which target it was reached from when first discovered.
556 //
557 // Once this search finds search_for, the breadcrumbs are used to reconstruct
558 // a shortest dependency chain (in reverse order) from search_from to
559 // search_for.
560
561 std::map<const Target*, ChainLink> breadcrumbs;
562 base::queue<ChainLink> work_queue;
563 work_queue.push(ChainLink(search_from, true));
564
565 bool first_time = true;
566 while (!work_queue.empty()) {
567 ChainLink cur_link = work_queue.front();
568 const Target* target = cur_link.target;
569 work_queue.pop();
570
571 if (target == search_for) {
572 // Found it! Reconstruct the chain.
573 chain->clear();
574 while (target != search_from) {
575 chain->push_back(cur_link);
576 cur_link = breadcrumbs[target];
577 target = cur_link.target;
578 }
579 chain->push_back(ChainLink(search_from, true));
580 return true;
581 }
582
583 // Always consider public dependencies as possibilities.
584 for (const auto& dep : target->public_deps()) {
585 if (breadcrumbs.insert(std::make_pair(dep.ptr, cur_link)).second)
586 work_queue.push(ChainLink(dep.ptr, true));
587 }
588
589 if (first_time || !require_permitted) {
590 // Consider all dependencies since all target paths are allowed, so add
591 // in private ones. Also do this the first time through the loop, since
592 // a target can include headers from its direct deps regardless of
593 // public/private-ness.
594 first_time = false;
595 for (const auto& dep : target->private_deps()) {
596 if (breadcrumbs.insert(std::make_pair(dep.ptr, cur_link)).second)
597 work_queue.push(ChainLink(dep.ptr, false));
598 }
599 }
600 }
601
602 return false;
603 }
604
MakeUnreachableError(const InputFile & source_file,const LocationRange & range,const Target * from_target,const TargetVector & targets)605 Err HeaderChecker::MakeUnreachableError(const InputFile& source_file,
606 const LocationRange& range,
607 const Target* from_target,
608 const TargetVector& targets) {
609 // Normally the toolchains will all match, but when cross-compiling, we can
610 // get targets with more than one toolchain in the list of possibilities.
611 std::vector<const Target*> targets_with_matching_toolchains;
612 std::vector<const Target*> targets_with_other_toolchains;
613 for (const TargetInfo& candidate : targets) {
614 if (candidate.target->toolchain() == from_target->toolchain())
615 targets_with_matching_toolchains.push_back(candidate.target);
616 else
617 targets_with_other_toolchains.push_back(candidate.target);
618 }
619
620 // It's common when cross-compiling to have a target with the same file in
621 // more than one toolchain. We could output all of them, but this is
622 // generally confusing to people (most end-users won't understand toolchains
623 // well).
624 //
625 // So delete any candidates in other toolchains that also appear in the same
626 // toolchain as the from_target.
627 for (int other_index = 0;
628 other_index < static_cast<int>(targets_with_other_toolchains.size());
629 other_index++) {
630 for (const Target* cur_matching : targets_with_matching_toolchains) {
631 if (TargetLabelsMatchExceptToolchain(
632 cur_matching, targets_with_other_toolchains[other_index])) {
633 // Found a duplicate, erase it.
634 targets_with_other_toolchains.erase(
635 targets_with_other_toolchains.begin() + other_index);
636 other_index--;
637 break;
638 }
639 }
640 }
641
642 // Only display toolchains on labels if they don't all match.
643 bool include_toolchain = !targets_with_other_toolchains.empty();
644
645 std::string msg = "It is not in any dependency of\n " +
646 from_target->label().GetUserVisibleName(include_toolchain);
647 msg += "\nThe include file is in the target(s):\n";
648 for (auto* target : targets_with_matching_toolchains)
649 msg += " " + target->label().GetUserVisibleName(include_toolchain) + "\n";
650 for (auto* target : targets_with_other_toolchains)
651 msg += " " + target->label().GetUserVisibleName(include_toolchain) + "\n";
652 if (targets_with_other_toolchains.size() +
653 targets_with_matching_toolchains.size() >
654 1)
655 msg += "at least one of ";
656 msg += "which should somehow be reachable.";
657
658 // Danger: must call CreatePersistentRange to put in Err.
659 return Err(CreatePersistentRange(source_file, range), "Include not allowed.",
660 msg);
661 }
662