clang 23.0.0git
InitHeaderSearch.cpp
Go to the documentation of this file.
1//===--- InitHeaderSearch.cpp - Initialize header search paths ------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements the InitHeaderSearch class.
10//
11//===----------------------------------------------------------------------===//
12
15#include "clang/Config/config.h" // C_INCLUDE_DIRS
16#include "clang/Lex/HeaderMap.h"
19#include "llvm/ADT/SmallPtrSet.h"
20#include "llvm/ADT/StringExtras.h"
21#include "llvm/ADT/Twine.h"
22#include "llvm/Support/ErrorHandling.h"
23#include "llvm/Support/Path.h"
24#include "llvm/Support/raw_ostream.h"
25#include "llvm/TargetParser/Triple.h"
26#include <optional>
27
28using namespace clang;
29using namespace clang::frontend;
30
31namespace {
32/// Holds information about a single DirectoryLookup object.
33struct DirectoryLookupInfo {
34 IncludeDirGroup Group;
35 DirectoryLookup Lookup;
36 std::optional<unsigned> UserEntryIdx;
37
38 DirectoryLookupInfo(IncludeDirGroup Group, DirectoryLookup Lookup,
39 std::optional<unsigned> UserEntryIdx)
40 : Group(Group), Lookup(Lookup), UserEntryIdx(UserEntryIdx) {}
41};
42
43/// This class makes it easier to set the search paths of a HeaderSearch object.
44/// InitHeaderSearch stores several search path lists internally, which can be
45/// sent to a HeaderSearch object in one swoop.
46class InitHeaderSearch {
47 std::vector<DirectoryLookupInfo> IncludePath;
48 std::vector<std::pair<std::string, bool> > SystemHeaderPrefixes;
49 HeaderSearch &Headers;
50 bool Verbose;
51 std::string IncludeSysroot;
52 bool HasSysroot;
53
54public:
55 InitHeaderSearch(HeaderSearch &HS, bool verbose, StringRef sysroot)
56 : Headers(HS), Verbose(verbose), IncludeSysroot(std::string(sysroot)),
57 HasSysroot(!(sysroot.empty() || sysroot == "/")) {}
58
59 /// Add the specified path to the specified group list, prefixing the sysroot
60 /// if used.
61 /// Returns true if the path exists, false if it was ignored.
62 bool AddPath(const Twine &Path, IncludeDirGroup Group, bool isFramework,
63 std::optional<unsigned> UserEntryIdx = std::nullopt);
64
65 /// Add the specified path to the specified group list, without performing any
66 /// sysroot remapping.
67 /// Returns true if the path exists, false if it was ignored.
68 bool AddUnmappedPath(const Twine &Path, IncludeDirGroup Group,
69 bool isFramework,
70 std::optional<unsigned> UserEntryIdx = std::nullopt);
71
72 /// Add the specified prefix to the system header prefix list.
73 void AddSystemHeaderPrefix(StringRef Prefix, bool IsSystemHeader) {
74 SystemHeaderPrefixes.emplace_back(std::string(Prefix), IsSystemHeader);
75 }
76
77 /// Add paths that should always be searched.
78 void AddDefaultCIncludePaths(const llvm::Triple &triple,
79 const HeaderSearchOptions &HSOpts);
80
81 /// Returns true iff AddDefaultIncludePaths should do anything. If this
82 /// returns false, include paths should instead be handled in the driver.
83 bool ShouldAddDefaultIncludePaths(const llvm::Triple &triple);
84
85 /// Adds the default system include paths so that e.g. stdio.h is found.
86 void AddDefaultIncludePaths(const LangOptions &Lang,
87 const llvm::Triple &triple,
88 const HeaderSearchOptions &HSOpts);
89
90 /// Merges all search path lists into one list and send it to HeaderSearch.
91 void Realize(const LangOptions &Lang);
92};
93
94} // end anonymous namespace.
95
96static bool CanPrefixSysroot(StringRef Path) {
97#if defined(_WIN32)
98 return !Path.empty() && llvm::sys::path::is_separator(Path[0]);
99#else
100 return llvm::sys::path::is_absolute(Path);
101#endif
102}
103
104bool InitHeaderSearch::AddPath(const Twine &Path, IncludeDirGroup Group,
105 bool isFramework,
106 std::optional<unsigned> UserEntryIdx) {
107 // Add the path with sysroot prepended, if desired and this is a system header
108 // group.
109 if (HasSysroot) {
110 SmallString<256> MappedPathStorage;
111 StringRef MappedPathStr = Path.toStringRef(MappedPathStorage);
112 if (CanPrefixSysroot(MappedPathStr)) {
113 return AddUnmappedPath(IncludeSysroot + Path, Group, isFramework,
114 UserEntryIdx);
115 }
116 }
117
118 return AddUnmappedPath(Path, Group, isFramework, UserEntryIdx);
119}
120
121bool InitHeaderSearch::AddUnmappedPath(const Twine &Path, IncludeDirGroup Group,
122 bool isFramework,
123 std::optional<unsigned> UserEntryIdx) {
124 assert(!Path.isTriviallyEmpty() && "can't handle empty path here");
125
126 FileManager &FM = Headers.getFileMgr();
127 SmallString<256> MappedPathStorage;
128 StringRef MappedPathStr = Path.toStringRef(MappedPathStorage);
129
130 // If use system headers while cross-compiling, emit the warning.
131 if (HasSysroot && (MappedPathStr.starts_with("/usr/include") ||
132 MappedPathStr.starts_with("/usr/local/include"))) {
133 Headers.getDiags().Report(diag::warn_poison_system_directories)
134 << MappedPathStr;
135 }
136
137 // Compute the DirectoryLookup type.
139 if (Group == Quoted || Group == Angled) {
141 } else if (Group == ExternCSystem) {
143 } else {
145 }
146
147 // If the directory exists, add it.
148 if (auto DE = FM.getOptionalDirectoryRef(MappedPathStr)) {
149 IncludePath.emplace_back(Group, DirectoryLookup(*DE, Type, isFramework),
150 UserEntryIdx);
151 return true;
152 }
153
154 // Check to see if this is an apple-style headermap (which are not allowed to
155 // be frameworks).
156 if (!isFramework) {
157 if (auto FE = FM.getOptionalFileRef(MappedPathStr)) {
158 if (const HeaderMap *HM = Headers.CreateHeaderMap(*FE)) {
159 // It is a headermap, add it to the search path.
160 IncludePath.emplace_back(Group, DirectoryLookup(HM, Type),
161 UserEntryIdx);
162 return true;
163 }
164 }
165 }
166
167 if (Verbose)
168 llvm::errs() << "ignoring nonexistent directory \""
169 << MappedPathStr << "\"\n";
170 return false;
171}
172
173void InitHeaderSearch::AddDefaultCIncludePaths(const llvm::Triple &triple,
174 const HeaderSearchOptions &HSOpts) {
175 if (!ShouldAddDefaultIncludePaths(triple))
176 llvm_unreachable("Include management is handled in the driver.");
177
178 if (HSOpts.UseStandardSystemIncludes) {
179 // FIXME: temporary hack: hard-coded paths.
180 AddPath("/usr/local/include", System, false);
181 }
182
183 // Builtin includes use #include_next directives and should be positioned
184 // just prior C include dirs.
185 if (HSOpts.UseBuiltinIncludes) {
186 // Ignore the sys root, we *always* look for clang headers relative to
187 // supplied path.
188 SmallString<128> P = StringRef(HSOpts.ResourceDir);
189 llvm::sys::path::append(P, "include");
190 AddUnmappedPath(P, ExternCSystem, false);
191 }
192
193 // All remaining additions are for system include directories, early exit if
194 // we aren't using them.
195 if (!HSOpts.UseStandardSystemIncludes)
196 return;
197
198 // Add dirs specified via 'configure --with-c-include-dirs'.
199 StringRef CIncludeDirs(C_INCLUDE_DIRS);
200 if (CIncludeDirs != "") {
201 SmallVector<StringRef, 5> dirs;
202 CIncludeDirs.split(dirs, ":");
203 for (StringRef dir : dirs)
204 AddPath(dir, ExternCSystem, false);
205 return;
206 }
207
208 AddPath("/usr/include", ExternCSystem, false);
209}
210
211bool InitHeaderSearch::ShouldAddDefaultIncludePaths(
212 const llvm::Triple &triple) {
213 switch (triple.getOS()) {
214 case llvm::Triple::AIX:
215 case llvm::Triple::DragonFly:
216 case llvm::Triple::ELFIAMCU:
217 case llvm::Triple::Emscripten:
218 case llvm::Triple::FreeBSD:
219 case llvm::Triple::Fuchsia:
220 case llvm::Triple::Haiku:
221 case llvm::Triple::Hurd:
222 case llvm::Triple::Linux:
223 case llvm::Triple::LiteOS:
224 case llvm::Triple::Managarm:
225 case llvm::Triple::NetBSD:
226 case llvm::Triple::OpenBSD:
227 case llvm::Triple::PS4:
228 case llvm::Triple::PS5:
229 case llvm::Triple::RTEMS:
230 case llvm::Triple::Solaris:
231 case llvm::Triple::UEFI:
232 case llvm::Triple::WASI:
233 case llvm::Triple::WASIp1:
234 case llvm::Triple::WASIp2:
235 case llvm::Triple::WASIp3:
236 case llvm::Triple::Win32:
237 case llvm::Triple::ZOS:
238 return false;
239
240 case llvm::Triple::UnknownOS:
241 if (triple.isWasm() || triple.isAppleMachO())
242 return false;
243 break;
244
245 default:
246 break;
247 }
248
249 if (triple.isOSDarwin())
250 return false;
251
252 // On hexagon, include paths are managed by the driver.
253 if (triple.getArch() == llvm::Triple::hexagon)
254 return false;
255
256 return true; // Everything else uses AddDefaultIncludePaths().
257}
258
259void InitHeaderSearch::AddDefaultIncludePaths(
260 const LangOptions &Lang, const llvm::Triple &triple,
261 const HeaderSearchOptions &HSOpts) {
262 // NB: This code path is going away. All of the logic is moving into the
263 // driver which has the information necessary to do target-specific
264 // selections of default include paths. Each target which moves there will be
265 // exempted from this logic in ShouldAddDefaultIncludePaths() until we can
266 // delete the entire pile of code.
267 if (!ShouldAddDefaultIncludePaths(triple))
268 return;
269
270 if (Lang.CPlusPlus && !Lang.AsmPreprocessor &&
272 if (HSOpts.UseLibcxx) {
273 AddPath("/usr/include/c++/v1", CXXSystem, false);
274 }
275 }
276
277 AddDefaultCIncludePaths(triple, HSOpts);
278}
279
280/// If there are duplicate directory entries in the specified search list,
281/// remove the later (dead) ones. Returns the number of non-system headers
282/// removed, which is used to update NumAngled.
283static unsigned RemoveDuplicates(std::vector<DirectoryLookupInfo> &SearchList,
284 unsigned First, bool Verbose) {
288 unsigned NonSystemRemoved = 0;
289 for (unsigned i = First; i != SearchList.size(); ++i) {
290 unsigned DirToRemove = i;
291
292 const DirectoryLookup &CurEntry = SearchList[i].Lookup;
293
294 if (CurEntry.isNormalDir()) {
295 // If this isn't the first time we've seen this dir, remove it.
296 if (SeenDirs.insert(CurEntry.getDir()).second)
297 continue;
298 } else if (CurEntry.isFramework()) {
299 // If this isn't the first time we've seen this framework dir, remove it.
300 if (SeenFrameworkDirs.insert(CurEntry.getFrameworkDir()).second)
301 continue;
302 } else {
303 assert(CurEntry.isHeaderMap() && "Not a headermap or normal dir?");
304 // If this isn't the first time we've seen this headermap, remove it.
305 if (SeenHeaderMaps.insert(CurEntry.getHeaderMap()).second)
306 continue;
307 }
308
309 // If we have a normal #include dir/framework/headermap that is shadowed
310 // later in the chain by a system include location, we actually want to
311 // ignore the user's request and drop the user dir... keeping the system
312 // dir. This is weird, but required to emulate GCC's search path correctly.
313 //
314 // Since dupes of system dirs are rare, just rescan to find the original
315 // that we're nuking instead of using a DenseMap.
316 if (CurEntry.getDirCharacteristic() != SrcMgr::C_User) {
317 // Find the dir that this is the same of.
318 unsigned FirstDir;
319 for (FirstDir = First;; ++FirstDir) {
320 assert(FirstDir != i && "Didn't find dupe?");
321
322 const DirectoryLookup &SearchEntry = SearchList[FirstDir].Lookup;
323
324 // If these are different lookup types, then they can't be the dupe.
325 if (SearchEntry.getLookupType() != CurEntry.getLookupType())
326 continue;
327
328 bool isSame;
329 if (CurEntry.isNormalDir())
330 isSame = SearchEntry.getDir() == CurEntry.getDir();
331 else if (CurEntry.isFramework())
332 isSame = SearchEntry.getFrameworkDir() == CurEntry.getFrameworkDir();
333 else {
334 assert(CurEntry.isHeaderMap() && "Not a headermap or normal dir?");
335 isSame = SearchEntry.getHeaderMap() == CurEntry.getHeaderMap();
336 }
337
338 if (isSame)
339 break;
340 }
341
342 // If the first dir in the search path is a non-system dir, zap it
343 // instead of the system one.
344 if (SearchList[FirstDir].Lookup.getDirCharacteristic() == SrcMgr::C_User)
345 DirToRemove = FirstDir;
346 }
347
348 if (Verbose) {
349 llvm::errs() << "ignoring duplicate directory \""
350 << CurEntry.getName() << "\"\n";
351 if (DirToRemove != i)
352 llvm::errs() << " as it is a non-system directory that duplicates "
353 << "a system directory\n";
354 }
355 if (DirToRemove != i)
356 ++NonSystemRemoved;
357
358 // This is reached if the current entry is a duplicate. Remove the
359 // DirToRemove (usually the current dir).
360 SearchList.erase(SearchList.begin()+DirToRemove);
361 --i;
362 }
363 return NonSystemRemoved;
364}
365
366/// Extract DirectoryLookups from DirectoryLookupInfos.
367static std::vector<DirectoryLookup>
368extractLookups(const std::vector<DirectoryLookupInfo> &Infos) {
369 std::vector<DirectoryLookup> Lookups;
370 Lookups.reserve(Infos.size());
371 llvm::transform(Infos, std::back_inserter(Lookups),
372 [](const DirectoryLookupInfo &Info) { return Info.Lookup; });
373 return Lookups;
374}
375
376/// Collect the mapping between indices of DirectoryLookups and UserEntries.
377static llvm::DenseMap<unsigned, unsigned>
378mapToUserEntries(const std::vector<DirectoryLookupInfo> &Infos) {
379 llvm::DenseMap<unsigned, unsigned> LookupsToUserEntries;
380 for (unsigned I = 0, E = Infos.size(); I < E; ++I) {
381 // Check whether this DirectoryLookup maps to a HeaderSearch::UserEntry.
382 if (Infos[I].UserEntryIdx)
383 LookupsToUserEntries.insert({I, *Infos[I].UserEntryIdx});
384 }
385 return LookupsToUserEntries;
386}
387
388void InitHeaderSearch::Realize(const LangOptions &Lang) {
389 // Concatenate ANGLE+SYSTEM+AFTER chains together into SearchList.
390 std::vector<DirectoryLookupInfo> SearchList;
391 SearchList.reserve(IncludePath.size());
392
393 // Quoted arguments go first.
394 for (auto &Include : IncludePath)
395 if (Include.Group == Quoted)
396 SearchList.push_back(Include);
397
398 // Deduplicate and remember index.
399 RemoveDuplicates(SearchList, 0, Verbose);
400 unsigned NumQuoted = SearchList.size();
401
402 for (auto &Include : IncludePath)
403 if (Include.Group == Angled)
404 SearchList.push_back(Include);
405
406 RemoveDuplicates(SearchList, NumQuoted, Verbose);
407 unsigned NumAngled = SearchList.size();
408
409 for (auto &Include : IncludePath)
410 if (Include.Group == System || Include.Group == ExternCSystem ||
411 (!Lang.ObjC && !Lang.CPlusPlus && Include.Group == CSystem) ||
412 (/*FIXME !Lang.ObjC && */ Lang.CPlusPlus &&
413 Include.Group == CXXSystem) ||
414 (Lang.ObjC && !Lang.CPlusPlus && Include.Group == ObjCSystem) ||
415 (Lang.ObjC && Lang.CPlusPlus && Include.Group == ObjCXXSystem))
416 SearchList.push_back(Include);
417
418 for (auto &Include : IncludePath)
419 if (Include.Group == After)
420 SearchList.push_back(Include);
421
422 // Remove duplicates across both the Angled and System directories. GCC does
423 // this and failing to remove duplicates across these two groups breaks
424 // #include_next.
425 unsigned NonSystemRemoved = RemoveDuplicates(SearchList, NumQuoted, Verbose);
426 NumAngled -= NonSystemRemoved;
427
428 Headers.SetSearchPaths(extractLookups(SearchList), NumQuoted, NumAngled,
429 mapToUserEntries(SearchList));
430
431 Headers.SetSystemHeaderPrefixes(SystemHeaderPrefixes);
432
433 // If verbose, print the list of directories that will be searched.
434 if (Verbose) {
435 llvm::errs() << "#include \"...\" search starts here:\n";
436 for (unsigned i = 0, e = SearchList.size(); i != e; ++i) {
437 if (i == NumQuoted)
438 llvm::errs() << "#include <...> search starts here:\n";
439 StringRef Name = SearchList[i].Lookup.getName();
440 const char *Suffix;
441 if (SearchList[i].Lookup.isNormalDir())
442 Suffix = "";
443 else if (SearchList[i].Lookup.isFramework())
444 Suffix = " (framework directory)";
445 else {
446 assert(SearchList[i].Lookup.isHeaderMap() && "Unknown DirectoryLookup");
447 Suffix = " (headermap)";
448 }
449 llvm::errs() << " " << Name << Suffix << "\n";
450 }
451 llvm::errs() << "End of search list.\n";
452 }
453}
454
456 const HeaderSearchOptions &HSOpts,
457 const LangOptions &Lang,
458 const llvm::Triple &Triple) {
459 InitHeaderSearch Init(HS, HSOpts.Verbose, HSOpts.Sysroot);
460
461 // Add the user defined entries.
462 for (unsigned i = 0, e = HSOpts.UserEntries.size(); i != e; ++i) {
463 const HeaderSearchOptions::Entry &E = HSOpts.UserEntries[i];
464 if (E.IgnoreSysRoot) {
465 Init.AddUnmappedPath(E.Path, E.Group, E.IsFramework, i);
466 } else {
467 Init.AddPath(E.Path, E.Group, E.IsFramework, i);
468 }
469 }
470
471 Init.AddDefaultIncludePaths(Lang, Triple, HSOpts);
472
473 for (unsigned i = 0, e = HSOpts.SystemHeaderPrefixes.size(); i != e; ++i)
474 Init.AddSystemHeaderPrefix(HSOpts.SystemHeaderPrefixes[i].Prefix,
475 HSOpts.SystemHeaderPrefixes[i].IsSystemHeader);
476
477 if (HSOpts.UseBuiltinIncludes) {
478 // Set up the builtin include directory in the module map.
479 SmallString<128> P = StringRef(HSOpts.ResourceDir);
480 llvm::sys::path::append(P, "include");
481 if (auto Dir = HS.getFileMgr().getOptionalDirectoryRef(P))
483 }
484
485 Init.Realize(Lang);
486}
Defines the clang::FileManager interface and associated types.
static unsigned RemoveDuplicates(std::vector< DirectoryLookupInfo > &SearchList, unsigned First, bool Verbose)
If there are duplicate directory entries in the specified search list, remove the later (dead) ones.
static llvm::DenseMap< unsigned, unsigned > mapToUserEntries(const std::vector< DirectoryLookupInfo > &Infos)
Collect the mapping between indices of DirectoryLookups and UserEntries.
static bool CanPrefixSysroot(StringRef Path)
static std::vector< DirectoryLookup > extractLookups(const std::vector< DirectoryLookupInfo > &Infos)
Extract DirectoryLookups from DirectoryLookupInfos.
Defines the clang::LangOptions interface.
DiagnosticBuilder Report(SourceLocation Loc, unsigned DiagID)
Issue the message to the client.
DirectoryLookup - This class represents one entry in the search list that specifies the search order ...
SrcMgr::CharacteristicKind getDirCharacteristic() const
DirCharacteristic - The type of directory this is, one of the DirType enum values.
const DirectoryEntry * getFrameworkDir() const
getFrameworkDir - Return the directory that this framework refers to.
bool isFramework() const
isFramework - True if this is a framework directory.
bool isHeaderMap() const
isHeaderMap - Return true if this is a header map, not a normal directory.
StringRef getName() const
getName - Return the directory or filename corresponding to this lookup object.
LookupType_t getLookupType() const
getLookupType - Return the kind of directory lookup that this is: either a normal directory,...
const DirectoryEntry * getDir() const
getDir - Return the directory that this entry refers to.
bool isNormalDir() const
isNormalDir - Return true if this is a normal directory, not a header map.
const HeaderMap * getHeaderMap() const
getHeaderMap - Return the directory that this entry refers to.
OptionalFileEntryRef getOptionalFileRef(StringRef Filename, bool OpenFile=false, bool CacheFailure=true)
Get a FileEntryRef if it exists, without doing anything on error.
OptionalDirectoryEntryRef getOptionalDirectoryRef(StringRef DirName, bool CacheFailure=true)
Get a DirectoryEntryRef if it exists, without doing anything on error.
HeaderSearchOptions - Helper class for storing options related to the initialization of the HeaderSea...
unsigned Verbose
Whether header search information should be output as for -v.
std::vector< SystemHeaderPrefix > SystemHeaderPrefixes
User-specified system header prefixes.
std::string Sysroot
If non-empty, the directory to use as a "virtual system root" for include paths.
unsigned UseLibcxx
Use libc++ instead of the default libstdc++.
unsigned UseBuiltinIncludes
Include the compiler builtin includes.
unsigned UseStandardCXXIncludes
Include the system standard C++ library include search directories.
std::vector< Entry > UserEntries
User specified include entries.
std::string ResourceDir
The directory which holds the compiler resource files (builtin includes, etc.).
unsigned UseStandardSystemIncludes
Include the system standard include search directories.
Encapsulates the information needed to find the file referenced by a #include or #include_next,...
FileManager & getFileMgr() const
DiagnosticsEngine & getDiags() const
void SetSystemHeaderPrefixes(ArrayRef< std::pair< std::string, bool > > P)
Set the list of system header prefixes.
void SetSearchPaths(std::vector< DirectoryLookup > dirs, unsigned angledDirIdx, unsigned systemDirIdx, llvm::DenseMap< unsigned, unsigned > searchDirToHSEntry)
Interface for setting the file search paths.
const HeaderMap * CreateHeaderMap(FileEntryRef FE)
This method returns a HeaderMap for the specified FileEntry, uniquing them through the 'HeaderMaps' d...
ModuleMap & getModuleMap()
Retrieve the module map.
Keeps track of the various options that can be enabled, which controls the dialect of C or C++ that i...
void setBuiltinIncludeDir(DirectoryEntryRef Dir)
Set the directory that contains Clang-supplied include files, such as our stdarg.h or tgmath....
Definition ModuleMap.h:405
CharacteristicKind
Indicates whether a file or directory holds normal user code, system code, or system code which is im...
@ HeaderSearch
Remove unused header search paths including header maps.
IncludeDirGroup
IncludeDirGroup - Identifies the group an include Entry belongs to, representing its relative positiv...
@ CXXSystem
Like System, but only used for C++.
@ Angled
Paths for '#include <>' added by '-I'.
@ CSystem
Like System, but only used for C.
@ System
Like Angled, but marks system directories.
@ Quoted
'#include ""' paths, added by 'gcc -iquote'.
@ ExternCSystem
Like System, but headers are implicitly wrapped in extern "C".
@ ObjCSystem
Like System, but only used for ObjC.
@ ObjCXXSystem
Like System, but only used for ObjC++.
@ After
Like System, but searched after the system directories.
The JSON file list parser is used to communicate input to InstallAPI.
void ApplyHeaderSearchOptions(HeaderSearch &HS, const HeaderSearchOptions &HSOpts, const LangOptions &Lang, const llvm::Triple &triple)
Apply the header search options to get given HeaderSearch object.
@ Type
The name was classified as a type.
Definition Sema.h:564
unsigned IgnoreSysRoot
IgnoreSysRoot - This is false if an absolute path should be treated relative to the sysroot,...