Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 28 additions & 15 deletions clang/lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3191,6 +3191,22 @@ static bool ParseHeaderSearchArgs(HeaderSearchOptions &Opts, ArgList &Args,
bool IsIndexHeaderMap = false;
bool IsSysrootSpecified =
Args.hasArg(OPT__sysroot_EQ) || Args.hasArg(OPT_isysroot);

// Expand a leading `=` to the sysroot if one was passed (and it's not a
// framework flag).
auto PrefixHeaderPath = [IsSysrootSpecified,
Copy link

@fanghuaqi fanghuaqi Sep 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @etcwilde @jansvoboda11 , I have a question about why IsSysrootSpecified is required, since the sysroot could use default sysroot defined in toolchain. For gcc, it is not required.

If dir begins with ‘=’ or $SYSROOT, then the ‘=’ or $SYSROOT is replaced by the sysroot prefix; see --sysroot and -isysroot.

So it will replace sysroot defined in toolchain, as tested below:

  • gcc -isystem=/include/libncrt: xxxxx/gcc/bin/../riscv64-unknown-elf/include/libncrt, this is expected
  • clang -isystem=/include/libncrt : ignoring nonexistent directory "=/include/libncrt"

Could this feature be added in llvm to match what is done in gcc's behavior?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey! Do you have any feedback regarding the above question? Thanks a ton!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that with this patch, whenever is sysroot specified we match the GCC behavior (assuming the = spelling). Are you specifically asking to add a fallback to <clang>/../<target>/include?

Copy link

@fanghuaqi fanghuaqi Oct 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that with this patch, whenever is sysroot specified we match the GCC behavior (assuming the = spelling). Are you specifically asking to add a fallback to <clang>/../<target>/include?

Thanks @jansvoboda11 for your reply.

I found my issue reported is caused by this

// If we have a --sysroot, and don't have an explicit -isysroot flag, add an
// -isysroot to the CC1 invocation.
StringRef sysroot = C.getSysRoot();
if (sysroot != "") {
if (!Args.hasArg(options::OPT_isysroot)) {
CmdArgs.push_back("-isysroot");
CmdArgs.push_back(C.getArgs().MakeArgString(sysroot));
}
}

The sysroot returned is empty, but actually it can be computed like this getToolChain().computeSysRoot(), for my case, it is done in

std::string BareMetal::computeSysRoot() const {

And locally I modified the code clang/lib/Driver/ToolChains/Clang.cpp in my side like this, then it fixed my issue.

   // If we have a --sysroot, and don't have an explicit -isysroot flag, add an
   // -isysroot to the CC1 invocation.
-  StringRef sysroot = C.getSysRoot();
-  if (sysroot != "") {
-    if (!Args.hasArg(options::OPT_isysroot)) {
-      CmdArgs.push_back("-isysroot");
-      CmdArgs.push_back(C.getArgs().MakeArgString(sysroot));
-    }
+  std::string sysroot = std::string(C.getSysRoot());
+  if (sysroot.empty()) {
+    sysroot = getToolChain().computeSysRoot();
+  }
+  if (!Args.hasArg(options::OPT_isysroot)) {
+    CmdArgs.push_back("-isysroot");
+    CmdArgs.push_back(C.getArgs().MakeArgString(sysroot));
   }

Regarding Are you specifically asking to add a fallback to /..//include?

I think it will be good, since the = is not handled correctly, it directly returned invalid path =/include/libncrt not a sysroot path + /include/libncrt

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks for explaining. Do you mind creating a PR? That seems like a reasonable path forward.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I will try to create a PR later.

&Opts](const llvm::opt::Arg *A,
bool IsFramework = false) -> std::string {
assert(A->getNumValues() && "Unexpected empty search path flag!");
if (IsSysrootSpecified && !IsFramework && A->getValue()[0] == '=') {
SmallString<32> Buffer;
llvm::sys::path::append(Buffer, Opts.Sysroot,
llvm::StringRef(A->getValue()).substr(1));
return std::string(Buffer);
}
return A->getValue();
};

for (const auto *A : Args.filtered(OPT_I, OPT_F, OPT_index_header_map)) {
if (A->getOption().matches(OPT_index_header_map)) {
// -index-header-map applies to the next -I or -F.
Expand All @@ -3202,16 +3218,7 @@ static bool ParseHeaderSearchArgs(HeaderSearchOptions &Opts, ArgList &Args,
IsIndexHeaderMap ? frontend::IndexHeaderMap : frontend::Angled;

bool IsFramework = A->getOption().matches(OPT_F);
std::string Path = A->getValue();

if (IsSysrootSpecified && !IsFramework && A->getValue()[0] == '=') {
SmallString<32> Buffer;
llvm::sys::path::append(Buffer, Opts.Sysroot,
llvm::StringRef(A->getValue()).substr(1));
Path = std::string(Buffer);
}

Opts.AddPath(Path, Group, IsFramework,
Opts.AddPath(PrefixHeaderPath(A, IsFramework), Group, IsFramework,
/*IgnoreSysroot*/ true);
IsIndexHeaderMap = false;
}
Expand All @@ -3229,12 +3236,18 @@ static bool ParseHeaderSearchArgs(HeaderSearchOptions &Opts, ArgList &Args,
}

for (const auto *A : Args.filtered(OPT_idirafter))
Opts.AddPath(A->getValue(), frontend::After, false, true);
Opts.AddPath(PrefixHeaderPath(A), frontend::After, false, true);
for (const auto *A : Args.filtered(OPT_iquote))
Opts.AddPath(A->getValue(), frontend::Quoted, false, true);
for (const auto *A : Args.filtered(OPT_isystem, OPT_iwithsysroot))
Opts.AddPath(A->getValue(), frontend::System, false,
!A->getOption().matches(OPT_iwithsysroot));
Opts.AddPath(PrefixHeaderPath(A), frontend::Quoted, false, true);

for (const auto *A : Args.filtered(OPT_isystem, OPT_iwithsysroot)) {
if (A->getOption().matches(OPT_iwithsysroot)) {
Opts.AddPath(A->getValue(), frontend::System, false,
/*IgnoreSysRoot=*/false);
continue;
}
Opts.AddPath(PrefixHeaderPath(A), frontend::System, false, true);
}
for (const auto *A : Args.filtered(OPT_iframework))
Opts.AddPath(A->getValue(), frontend::System, true, true);
for (const auto *A : Args.filtered(OPT_iframeworkwithsysroot))
Expand Down
25 changes: 25 additions & 0 deletions clang/test/Preprocessor/sysroot-prefix.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
// RUN: %clang_cc1 -v -isysroot /var/empty -I =null -E %s -o /dev/null 2>&1 | FileCheck -check-prefix CHECK-ISYSROOT_SYSROOT_NULL %s
// RUN: %clang_cc1 -v -isysroot /var/empty -isysroot /var/empty/root -I =null -E %s -o /dev/null 2>&1 | FileCheck -check-prefix CHECK-ISYSROOT_ISYSROOT_SYSROOT_NULL %s
// RUN: %clang_cc1 -v -isysroot /var/empty/root -isysroot /var/empty -I =null -E %s -o /dev/null 2>&1 | FileCheck -check-prefix CHECK-ISYSROOT_ISYSROOT_SWAPPED_SYSROOT_NULL %s
// RUN: %clang_cc1 -v -isystem=/usr/include -isysroot /var/empty -E %s -o /dev/null 2>&1 | FileCheck -check-prefix CHECK-ISYSROOT_ISYSTEM_SYSROOT %s
// RUN: %clang_cc1 -v -isystem=/usr/include -E %s -o /dev/null 2>&1 | FileCheck -check-prefix CHECK-ISYSROOT_ISYSTEM_NO_SYSROOT %s
// RUN: %clang_cc1 -v -iquote=/usr/include -isysroot /var/empty -E %s -o /dev/null 2>&1 | FileCheck -check-prefix CHECK-ISYSROOT_IQUOTE_SYSROOT %s
// RUN: %clang_cc1 -v -iquote=/usr/include -E %s -o /dev/null 2>&1 | FileCheck -check-prefix CHECK-ISYSROOT_IQUOTE_NO_SYSROOT %s
// RUN: %clang_cc1 -v -idirafter=/usr/include -isysroot /var/empty -E %s -o /dev/null 2>&1 | FileCheck -check-prefix CHECK-ISYSROOT_IDIRAFTER_SYSROOT %s
// RUN: %clang_cc1 -v -idirafter=/usr/include -E %s -o /dev/null 2>&1 | FileCheck -check-prefix CHECK-ISYSROOT_IDIRAFTER_NO_SYSROOT %s
// RUN: %clang_cc1 -v -isysroot /var/empty -isystem=/usr/include -iwithsysroot /opt/include -isystem=/usr/local/include -E %s -o /dev/null 2>&1 | FileCheck -check-prefix CHECK-INTERLEAVE_I_PATHS %s

// RUN: not %clang_cc1 -v -isysroot /var/empty -E %s -o /dev/null -I 2>&1 | FileCheck -check-prefix CHECK-EMPTY_I_PATH %s
// RUN: %clang_cc1 -v -isysroot /var/empty/usr -E %s -o /dev/null -I= 2>&1 | FileCheck -check-prefix CHECK-SYSROOT_I_PATH %s

// CHECK-ISYSROOT_NO_SYSROOT: ignoring nonexistent directory "/var/empty/include"
// CHECK-ISYSROOT_NO_SYSROOT-NOT: ignoring nonexistent directory "/var/empty/var/empty/include"
Expand All @@ -23,3 +33,18 @@
// CHECK-ISYSROOT_ISYSROOT_SWAPPED_SYSROOT_NULL: ignoring nonexistent directory "/var/empty{{.}}null"
// CHECK-ISYSROOT_ISYSROOT_SWAPPED_SYSROOT_NULL-NOT: ignoring nonexistent directory "=null"

// CHECK-ISYSROOT_ISYSTEM_SYSROOT: ignoring nonexistent directory "/var/empty/usr/include"
// CHECK-ISYSROOT_ISYSTEM_NO_SYSROOT: ignoring nonexistent directory "=/usr/include"

// CHECK-ISYSROOT_IQUOTE_SYSROOT: ignoring nonexistent directory "/var/empty/usr/include"
// CHECK-ISYSROOT_IQUOTE_NO_SYSROOT: ignoring nonexistent directory "=/usr/include"

// CHECK-ISYSROOT_IDIRAFTER_SYSROOT: ignoring nonexistent directory "/var/empty/usr/include"
// CHECK-ISYSROOT_IDIRAFTER_NO_SYSROOT: ignoring nonexistent directory "=/usr/include"

// CHECK-INTERLEAVE_I_PATHS: ignoring nonexistent directory "/var/empty/usr/include"
// CHECK-INTERLEAVE_I_PATHS: ignoring nonexistent directory "/var/empty/opt/include"
// CHECK-INTERLEAVE_I_PATHS: ignoring nonexistent directory "/var/empty/usr/local/include"

// CHECK-EMPTY_I_PATH: argument to '-I' is missing
// CHECK-SYSROOT_I_PATH: ignoring nonexistent directory "/var/empty/usr{{/|\\}}"