diff --git a/clang/lib/Lex/HeaderSearch.cpp b/clang/lib/Lex/HeaderSearch.cpp --- a/clang/lib/Lex/HeaderSearch.cpp +++ b/clang/lib/Lex/HeaderSearch.cpp @@ -720,7 +720,8 @@ } static bool isFrameworkStylePath(StringRef Path, bool &IsPrivateHeader, - SmallVectorImpl &FrameworkName) { + SmallVectorImpl &FrameworkName, + SmallVectorImpl &IncludeSpelling) { using namespace llvm::sys; path::const_iterator I = path::begin(Path); path::const_iterator E = path::end(Path); @@ -736,15 +737,22 @@ // and some other variations among these lines. int FoundComp = 0; while (I != E) { - if (*I == "Headers") + if (*I == "Headers") { ++FoundComp; - if (I->endswith(".framework")) { - FrameworkName.append(I->begin(), I->end()); - ++FoundComp; - } - if (*I == "PrivateHeaders") { + } else if (*I == "PrivateHeaders") { ++FoundComp; IsPrivateHeader = true; + } else if (I->endswith(".framework")) { + StringRef Name = I->drop_back(10); // Drop .framework + // Need to reset the strings and counter to support nested frameworks. + FrameworkName.clear(); + FrameworkName.append(Name.begin(), Name.end()); + IncludeSpelling.clear(); + IncludeSpelling.append(Name.begin(), Name.end()); + FoundComp = 1; + } else if (FoundComp >= 2) { + IncludeSpelling.push_back('/'); + IncludeSpelling.append(I->begin(), I->end()); } ++I; } @@ -759,20 +767,24 @@ bool FoundByHeaderMap = false) { bool IsIncluderPrivateHeader = false; SmallString<128> FromFramework, ToFramework; - if (!isFrameworkStylePath(Includer, IsIncluderPrivateHeader, FromFramework)) + SmallString<128> FromIncludeSpelling, ToIncludeSpelling; + if (!isFrameworkStylePath(Includer, IsIncluderPrivateHeader, FromFramework, + FromIncludeSpelling)) return; bool IsIncludeePrivateHeader = false; - bool IsIncludeeInFramework = isFrameworkStylePath( - IncludeFE->getName(), IsIncludeePrivateHeader, ToFramework); + bool IsIncludeeInFramework = + isFrameworkStylePath(IncludeFE->getName(), IsIncludeePrivateHeader, + ToFramework, ToIncludeSpelling); if (!isAngled && !FoundByHeaderMap) { SmallString<128> NewInclude("<"); if (IsIncludeeInFramework) { - NewInclude += ToFramework.str().drop_back(10); // drop .framework - NewInclude += "/"; + NewInclude += ToIncludeSpelling; + NewInclude += ">"; + } else { + NewInclude += IncludeFilename; + NewInclude += ">"; } - NewInclude += IncludeFilename; - NewInclude += ">"; Diags.Report(IncludeLoc, diag::warn_quoted_include_in_framework_header) << IncludeFilename << FixItHint::CreateReplacement(IncludeLoc, NewInclude); @@ -1843,10 +1855,11 @@ using namespace llvm::sys; unsigned BestPrefixLength = 0; - // Checks whether Dir and File shares a common prefix, if they do and that's - // the longest prefix we've seen so for it returns true and updates the - // BestPrefixLength accordingly. - auto CheckDir = [&](llvm::StringRef Dir) -> bool { + bool BestPrefixIsFramework = false; + // Checks whether `Dir` is a strict path prefix of `File`. If so and that's + // the longest prefix we've seen so for it, returns true and updates the + // `BestPrefixLength` accordingly. + auto CheckDir = [&](llvm::StringRef Dir, bool IsFramework) -> bool { llvm::SmallString<32> DirPath(Dir.begin(), Dir.end()); if (!WorkingDir.empty() && !path::is_absolute(Dir)) fs::make_absolute(WorkingDir, DirPath); @@ -1870,6 +1883,7 @@ unsigned PrefixLength = NI - path::begin(File); if (PrefixLength > BestPrefixLength) { BestPrefixLength = PrefixLength; + BestPrefixIsFramework = IsFramework; return true; } break; @@ -1880,6 +1894,16 @@ path::is_separator(NI->front()) && path::is_separator(DI->front())) continue; + // Special case Apple .sdk folders since the search path is typically a + // symlink like `iPhoneSimulator14.5.sdk` while the file is instead + // located in `iPhoneSimulator.sdk` (the real folder). + if (NI->endswith(".sdk") && DI->endswith(".sdk")) { + StringRef NBasename = path::stem(*NI); + StringRef DBasename = path::stem(*DI); + if (DBasename.startswith(NBasename)) + continue; + } + if (*NI != *DI) break; } @@ -1887,18 +1911,22 @@ }; for (unsigned I = 0; I != SearchDirs.size(); ++I) { - // FIXME: Support this search within frameworks. - if (!SearchDirs[I].isNormalDir()) - continue; - - StringRef Dir = SearchDirs[I].getDir()->getName(); - if (CheckDir(Dir) && IsSystem) - *IsSystem = BestPrefixLength ? I >= SystemDirIdx : false; + if (SearchDirs[I].isNormalDir()) { + StringRef Dir = SearchDirs[I].getDir()->getName(); + if (CheckDir(Dir, /** IsFramework= */ false) && IsSystem) + *IsSystem = BestPrefixLength ? I >= SystemDirIdx : false; + } else if (SearchDirs[I].isFramework()) { + StringRef Dir = SearchDirs[I].getFrameworkDir()->getName(); + if (CheckDir(Dir, /** IsFramework= */ true) && IsSystem) + *IsSystem = SearchDirs[I].isSystemHeaderDirectory(); + } } // Try to shorten include path using TUs directory, if we couldn't find any // suitable prefix in include search paths. - if (!BestPrefixLength && CheckDir(path::parent_path(MainFile)) && IsSystem) + if (!BestPrefixLength && + CheckDir(path::parent_path(MainFile), /** IsFramework= */ false) && + IsSystem) *IsSystem = false; // Try resolving resulting filename via reverse search in header maps, @@ -1912,8 +1940,39 @@ SearchDirs[I].getHeaderMap()->reverseLookupFilename(Filename); if (!SpelledFilename.empty()) { Filename = SpelledFilename; + BestPrefixIsFramework = false; break; } } + + // If the best prefix is a framework path, we need to compute the proper + // include spelling for the framework header. + bool IsPrivateHeader, MainIsPrivateHeader; + SmallString<128> FrameworkName, IncludeSpelling, MainFrameworkName, + MainIncludeSpelling; + if (BestPrefixIsFramework && + isFrameworkStylePath(Filename, IsPrivateHeader, FrameworkName, + IncludeSpelling)) { + // Prefer using `` only if the main file is in `UIKit`. + if (isFrameworkStylePath(MainFile, MainIsPrivateHeader, MainFrameworkName, + MainIncludeSpelling) && + MainFrameworkName == FrameworkName) { + Filename = IncludeSpelling; + } else { + // Prefer using `` for non-`UIKit` main files. + // + // This assumes the framework has an umbrella header of the same name. + // There's not much more we can do without hitting the filesystem, + // especially if there's no modulemap. + IncludeSpelling.clear(); + if (IsPrivateHeader) + llvm::sys::path::append(IncludeSpelling, FrameworkName, + FrameworkName + "_Private.h"); + else + llvm::sys::path::append(IncludeSpelling, FrameworkName, + FrameworkName + ".h"); + Filename = IncludeSpelling; + } + } return path::convert_to_slash(Filename); }