From a994b56753c424453c4ce4bbeafb0f7211f387b4 Mon Sep 17 00:00:00 2001 From: Cyndy Ishida Date: Tue, 13 Feb 2024 18:22:23 -0800 Subject: [PATCH] [clang][InstallAPI] Add input file support to library This patch adds support for expected InstallAPI inputs. InstallAPI accepts a well defined filelist of headers and how those headers represent a single library. InstallAPI captures header files to determine linkable symbols to then compare against what was compiled in a binary dylib and generate TBD files. --- clang/include/clang/InstallAPI/FileList.h | 80 +++++++ clang/include/clang/InstallAPI/HeaderFile.h | 69 ++++++ clang/lib/ExtractAPI/ExtractAPIConsumer.cpp | 6 +- clang/lib/InstallAPI/CMakeLists.txt | 2 + clang/lib/InstallAPI/FileList.cpp | 215 ++++++++++++++++++ clang/lib/InstallAPI/HeaderFile.cpp | 32 +++ clang/unittests/CMakeLists.txt | 1 + clang/unittests/InstallAPI/CMakeLists.txt | 9 + clang/unittests/InstallAPI/FileListTest.cpp | 173 ++++++++++++++ clang/unittests/InstallAPI/HeaderFileTest.cpp | 89 ++++++++ 10 files changed, 672 insertions(+), 4 deletions(-) create mode 100644 clang/include/clang/InstallAPI/FileList.h create mode 100644 clang/include/clang/InstallAPI/HeaderFile.h create mode 100644 clang/lib/InstallAPI/FileList.cpp create mode 100644 clang/lib/InstallAPI/HeaderFile.cpp create mode 100644 clang/unittests/InstallAPI/CMakeLists.txt create mode 100644 clang/unittests/InstallAPI/FileListTest.cpp create mode 100644 clang/unittests/InstallAPI/HeaderFileTest.cpp diff --git a/clang/include/clang/InstallAPI/FileList.h b/clang/include/clang/InstallAPI/FileList.h new file mode 100644 index 00000000000000..5639388514b11d --- /dev/null +++ b/clang/include/clang/InstallAPI/FileList.h @@ -0,0 +1,80 @@ +//===- InstallAPI/FileList.h ------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// The JSON file list parser is used to communicate input to InstallAPI. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_INSTALLAPI_FILELIST_H +#define LLVM_CLANG_INSTALLAPI_FILELIST_H + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/FileManager.h" +#include "clang/InstallAPI/HeaderFile.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/MemoryBuffer.h" + +namespace clang { +namespace installapi { + +/// Abstract Interface for reading FileList JSON Input. +class FileListReader { + class Implementation; + + Implementation &Impl; + + FileListReader(std::unique_ptr InputBuffer, + llvm::Error &Err); + +public: + static llvm::Expected> + get(std::unique_ptr InputBuffer); + + ~FileListReader(); + + FileListReader(const FileListReader &) = delete; + FileListReader &operator=(const FileListReader &) = delete; + + int getVersion() const; + + struct HeaderInfo { + HeaderType Type; + std::string Path; + std::optional Language; + }; + + /// Visitor used when walking the contents of the file list. + class Visitor { + public: + virtual ~Visitor(); + + virtual void visitHeaderFile(HeaderInfo &header) = 0; + }; + + /// Visit the contents of the header list file, passing each entity to the + /// given visitor. It visits in the same order as they appear in the json + /// file. + void visit(Visitor &visitor); +}; + +class FileListVisitor final : public FileListReader::Visitor { + FileManager &FM; + DiagnosticsEngine &Diag; + HeaderSeq &HeaderFiles; + +public: + FileListVisitor(FileManager &FM, DiagnosticsEngine &Diag, + HeaderSeq &HeaderFiles) + : FM(FM), Diag(Diag), HeaderFiles(HeaderFiles) {} + + void visitHeaderFile(FileListReader::HeaderInfo &Header) override; +}; +} // namespace installapi +} // namespace clang + +#endif // LLVM_CLANG_INSTALLAPI_FILELIST_H diff --git a/clang/include/clang/InstallAPI/HeaderFile.h b/clang/include/clang/InstallAPI/HeaderFile.h new file mode 100644 index 00000000000000..7aea045b50c363 --- /dev/null +++ b/clang/include/clang/InstallAPI/HeaderFile.h @@ -0,0 +1,69 @@ +//===- InstallAPI/HeaderFile.h ----------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// Representations of a library's headers for InstallAPI. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_INSTALLAPI_HEADERFILE_H +#define LLVM_CLANG_INSTALLAPI_HEADERFILE_H + +#include "clang/Basic/LangStandard.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Regex.h" +#include +#include + +namespace clang::installapi { + +const llvm::Regex DarwinFwkHeaderRule("/(.+)\\.framework/(.+)?Headers/(.+)"); + +enum class HeaderType { + /// Represents declarations accessible to all clients. + Public, + /// Represents declarations accessible to a disclosed set of clients. + Private, + /// Represents declarations only accessible as implementation details to the + /// input library. + Project, +}; + +class HeaderFile { + /// Full input path to header. + std::string FullPath; + /// Access level of header. + HeaderType Type; + /// Expected way header will be included by clients. + std::string IncludeName; + /// Supported language mode for header. + std::optional Language; + +public: + HeaderFile(StringRef FullPath, HeaderType Type, + StringRef IncludeName = StringRef(), + std::optional Language = std::nullopt) + : FullPath(FullPath), Type(Type), IncludeName(IncludeName), + Language(Language) {} + + HeaderType getType() const { return Type; } +}; + +/// Assemble expected way header will be included by clients. +/// As in what maps inside the brackets of `#include ` +/// For example, +/// "/System/Library/Frameworks/Foo.framework/Headers/Foo.h" returns +/// "Foo/Foo.h" +/// +/// \param FullPath Path to the header file which includes the library +/// structure. +std::optional createIncludeHeaderName(const StringRef FullPath); +using HeaderSeq = std::vector; + +} // namespace clang::installapi + +#endif // LLVM_CLANG_INSTALLAPI_HEADERFILE_H diff --git a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp index fd62d841197d9f..f73b0adec76553 100644 --- a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp +++ b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp @@ -30,6 +30,7 @@ #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendOptions.h" #include "clang/Frontend/MultiplexConsumer.h" +#include "clang/InstallAPI/HeaderFile.h" #include "clang/Lex/MacroInfo.h" #include "clang/Lex/PPCallbacks.h" #include "clang/Lex/Preprocessor.h" @@ -61,9 +62,6 @@ std::optional getRelativeIncludeName(const CompilerInstance &CI, "CompilerInstance does not have a FileNamager!"); using namespace llvm::sys; - // Matches framework include patterns - const llvm::Regex Rule("/(.+)\\.framework/(.+)?Headers/(.+)"); - const auto &FS = CI.getVirtualFileSystem(); SmallString<128> FilePath(File.begin(), File.end()); @@ -147,7 +145,7 @@ std::optional getRelativeIncludeName(const CompilerInstance &CI, // include name `` if (Entry.IsFramework) { SmallVector Matches; - Rule.match(File, &Matches); + clang::installapi::DarwinFwkHeaderRule.match(File, &Matches); // Returned matches are always in stable order. if (Matches.size() != 4) return std::nullopt; diff --git a/clang/lib/InstallAPI/CMakeLists.txt b/clang/lib/InstallAPI/CMakeLists.txt index 1476b737c5e61c..6c9cb4b559f67d 100644 --- a/clang/lib/InstallAPI/CMakeLists.txt +++ b/clang/lib/InstallAPI/CMakeLists.txt @@ -5,6 +5,8 @@ set(LLVM_LINK_COMPONENTS add_clang_library(clangInstallAPI Context.cpp + FileList.cpp + HeaderFile.cpp LINK_LIBS clangAST diff --git a/clang/lib/InstallAPI/FileList.cpp b/clang/lib/InstallAPI/FileList.cpp new file mode 100644 index 00000000000000..6bf6048778c96c --- /dev/null +++ b/clang/lib/InstallAPI/FileList.cpp @@ -0,0 +1,215 @@ +//===- FileList.cpp ---------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/InstallAPI/FileList.h" +#include "clang/Basic/DiagnosticFrontend.h" +#include "clang/InstallAPI/FileList.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/JSON.h" +#include "llvm/TextAPI/TextAPIError.h" +#include + +// clang-format off +/* +InstallAPI JSON Input Format specification. + +{ + "headers" : [ # Required: Key must exist. + { # Optional: May contain 0 or more header inputs. + "path" : "/usr/include/mach-o/dlfn.h", # Required: Path should point to destination + # location where applicable. + "type" : "public", # Required: Maps to HeaderType for header. + "language": "c++" # Optional: Language mode for header. + } + ], + "version" : "3" # Required: Version 3 supports language mode + & project header input. +} +*/ +// clang-format on + +using namespace llvm; +using namespace llvm::json; +using namespace llvm::MachO; +using namespace clang::installapi; + +class FileListReader::Implementation { +private: + Expected parseString(const Object *Obj, StringRef Key, + StringRef Error); + Expected parsePath(const Object *Obj); + Expected parseType(const Object *Obj); + std::optional parseLanguage(const Object *Obj); + Error parseHeaders(Array &Headers); + +public: + std::unique_ptr InputBuffer; + unsigned Version; + std::vector HeaderList; + + Error parse(StringRef Input); +}; + +Expected +FileListReader::Implementation::parseString(const Object *Obj, StringRef Key, + StringRef Error) { + auto Str = Obj->getString(Key); + if (!Str) + return make_error(Error, inconvertibleErrorCode()); + return *Str; +} + +Expected +FileListReader::Implementation::parseType(const Object *Obj) { + auto TypeStr = + parseString(Obj, "type", "required field 'type' not specified"); + if (!TypeStr) + return TypeStr.takeError(); + + if (*TypeStr == "public") + return HeaderType::Public; + else if (*TypeStr == "private") + return HeaderType::Private; + else if (*TypeStr == "project" && Version >= 2) + return HeaderType::Project; + + return make_error(TextAPIErrorCode::InvalidInputFormat, + "unsupported header type"); +} + +Expected +FileListReader::Implementation::parsePath(const Object *Obj) { + auto Path = parseString(Obj, "path", "required field 'path' not specified"); + if (!Path) + return Path.takeError(); + + return *Path; +} + +std::optional +FileListReader::Implementation::parseLanguage(const Object *Obj) { + auto Language = Obj->getString("language"); + if (!Language) + return std::nullopt; + + return StringSwitch(*Language) + .Case("c", clang::Language::C) + .Case("c++", clang::Language::CXX) + .Case("objective-c", clang::Language::ObjC) + .Case("objective-c++", clang::Language::ObjCXX) + .Default(clang::Language::Unknown); +} + +Error FileListReader::Implementation::parseHeaders(Array &Headers) { + for (const auto &H : Headers) { + auto *Obj = H.getAsObject(); + if (!Obj) + return make_error("expect a JSON object", + inconvertibleErrorCode()); + auto Type = parseType(Obj); + if (!Type) + return Type.takeError(); + auto Path = parsePath(Obj); + if (!Path) + return Path.takeError(); + auto Language = parseLanguage(Obj); + + HeaderList.emplace_back(HeaderInfo{*Type, std::string(*Path), Language}); + } + + return Error::success(); +} + +Error FileListReader::Implementation::parse(StringRef Input) { + auto Val = json::parse(Input); + if (!Val) + return Val.takeError(); + + auto *Root = Val->getAsObject(); + if (!Root) + return make_error("not a JSON object", + inconvertibleErrorCode()); + + auto VersionStr = Root->getString("version"); + if (!VersionStr) + return make_error(TextAPIErrorCode::InvalidInputFormat, + "required field 'version' not specified"); + if (VersionStr->getAsInteger(10, Version)) + return make_error(TextAPIErrorCode::InvalidInputFormat, + "invalid version number"); + + if (Version < 1 || Version > 3) + return make_error(TextAPIErrorCode::InvalidInputFormat, + "unsupported version"); + + // Not specifying any header files should be atypical, but valid. + auto Headers = Root->getArray("headers"); + if (!Headers) + return Error::success(); + + Error Err = parseHeaders(*Headers); + if (Err) + return Err; + + return Error::success(); +} + +FileListReader::FileListReader(std::unique_ptr InputBuffer, + Error &Error) + : Impl(*new FileListReader::Implementation()) { + ErrorAsOutParameter ErrorAsOutParam(&Error); + Impl.InputBuffer = std::move(InputBuffer); + + Error = Impl.parse(Impl.InputBuffer->getBuffer()); +} + +Expected> +FileListReader::get(std::unique_ptr InputBuffer) { + Error Error = Error::success(); + std::unique_ptr Reader( + new FileListReader(std::move(InputBuffer), Error)); + if (Error) + return std::move(Error); + + return Reader; +} + +FileListReader::~FileListReader() { delete &Impl; } + +int FileListReader::getVersion() const { return Impl.Version; } + +void FileListReader::visit(Visitor &Visitor) { + for (auto &File : Impl.HeaderList) + Visitor.visitHeaderFile(File); +} + +FileListReader::Visitor::~Visitor() {} + +void FileListReader::Visitor::visitHeaderFile(HeaderInfo &Header) {} + +void FileListVisitor::visitHeaderFile(FileListReader::HeaderInfo &Header) { + llvm::vfs::Status Result; + if (FM.getNoncachedStatValue(Header.Path, Result) || !Result.exists()) { + Diag.Report(diag::err_fe_error_opening) << Header.Path; + return; + } + + // Track full paths for project headers, as they are looked up via + // quote includes. + if (Header.Type == HeaderType::Project) { + HeaderFiles.emplace_back(Header.Path, Header.Type, + /*IncludeName*/ "", Header.Language); + return; + } + + auto IncludeName = createIncludeHeaderName(Header.Path); + HeaderFiles.emplace_back(Header.Path, Header.Type, + IncludeName.has_value() ? IncludeName.value() : "", + Header.Language); +} diff --git a/clang/lib/InstallAPI/HeaderFile.cpp b/clang/lib/InstallAPI/HeaderFile.cpp new file mode 100644 index 00000000000000..78a85688ab53b4 --- /dev/null +++ b/clang/lib/InstallAPI/HeaderFile.cpp @@ -0,0 +1,32 @@ +//===- HeaderFile.cpp ------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/InstallAPI/HeaderFile.h" + +using namespace llvm; +namespace clang::installapi { +std::optional createIncludeHeaderName(const StringRef FullPath) { + // Headers in usr(/local)*/include. + std::string Pattern = "/include/"; + auto PathPrefix = FullPath.find(Pattern); + if (PathPrefix != StringRef::npos) { + PathPrefix += Pattern.size(); + return FullPath.drop_front(PathPrefix).str(); + } + + // Framework Headers. + SmallVector Matches; + DarwinFwkHeaderRule.match(FullPath, &Matches); + // Returned matches are always in stable order. + if (Matches.size() != 4) + return std::nullopt; + + return Matches[1].drop_front(Matches[1].rfind('/') + 1).str() + "/" + + Matches[3].str(); +} +} // namespace clang::installapi diff --git a/clang/unittests/CMakeLists.txt b/clang/unittests/CMakeLists.txt index f4e4f585bdd800..37ca3107b54774 100644 --- a/clang/unittests/CMakeLists.txt +++ b/clang/unittests/CMakeLists.txt @@ -51,5 +51,6 @@ endif() add_subdirectory(DirectoryWatcher) add_subdirectory(Rename) add_subdirectory(Index) +add_subdirectory(InstallAPI) add_subdirectory(Serialization) add_subdirectory(Support) diff --git a/clang/unittests/InstallAPI/CMakeLists.txt b/clang/unittests/InstallAPI/CMakeLists.txt new file mode 100644 index 00000000000000..b70b7c136e64a6 --- /dev/null +++ b/clang/unittests/InstallAPI/CMakeLists.txt @@ -0,0 +1,9 @@ +add_clang_unittest(InstallAPITests + HeaderFileTest.cpp + FileListTest.cpp + ) + +clang_target_link_libraries(InstallAPITests + PRIVATE + clangInstallAPI + ) diff --git a/clang/unittests/InstallAPI/FileListTest.cpp b/clang/unittests/InstallAPI/FileListTest.cpp new file mode 100644 index 00000000000000..7e0830e44102ab --- /dev/null +++ b/clang/unittests/InstallAPI/FileListTest.cpp @@ -0,0 +1,173 @@ +//===- unittests/InstallAPI/FileList.cpp - File List Tests ---------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/InstallAPI/FileList.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/MemoryBuffer.h" +#include "gtest/gtest.h" +#include + +using namespace llvm; +using namespace clang::installapi; + +namespace { +class TestVisitor : public FileListReader::Visitor { +public: + std::map Headers; + + void visitHeaderFile(FileListReader::HeaderInfo &Header) override { + StringRef Key = StringRef(Header.Path).rsplit("/").second; + Headers[Key] = Header; + } +}; +} // namespace + +static bool operator==(const FileListReader::HeaderInfo &LHS, + const FileListReader::HeaderInfo &RHS) { + return std::tie(LHS.Type, LHS.Path, LHS.Language) == + std::tie(RHS.Type, RHS.Path, RHS.Language); +} + +TEST(FileListReader, Version3) { + static const char Input[] = R"({ + "version" : "3", + "headers" : [ + { + "type" : "public", + "path" : "/tmp/dst/usr/include/foo.h", + "language" : "objective-c" + }, + { + "type" : "private", + "path" : "/tmp/dst/usr/local/include/bar.h", + "language" : "objective-c++" + }, + { + "type" : "project", + "path" : "/tmp/src/baz.h" + } + ] + })"; + auto InputBuf = MemoryBuffer::getMemBuffer(Input); + auto Reader = FileListReader::get(std::move(InputBuf)); + ASSERT_TRUE(!!Reader); + TestVisitor Visitor; + (*Reader)->visit(Visitor); + EXPECT_EQ(3U, Visitor.Headers.size()); + + FileListReader::HeaderInfo Foo{ + HeaderType::Public, "/tmp/dst/usr/include/foo.h", clang::Language::ObjC}; + EXPECT_TRUE(Foo == Visitor.Headers["foo.h"]); + + FileListReader::HeaderInfo Bar{HeaderType::Private, + "/tmp/dst/usr/local/include/bar.h", + clang::Language::ObjCXX}; + EXPECT_TRUE(Bar == Visitor.Headers["bar.h"]); + + FileListReader::HeaderInfo Baz{HeaderType::Project, "/tmp/src/baz.h", + std::nullopt}; + EXPECT_TRUE(Baz == Visitor.Headers["baz.h"]); +} + +TEST(FileList, Version1) { + static const char Input[] = R"({ + "version" : "1", + "headers" : [ + { + "type" : "public", + "path" : "/usr/include/foo.h" + }, + { + "type" : "private", + "path" : "/usr/local/include/bar.h" + } + ] + })"; + auto InputBuf = MemoryBuffer::getMemBuffer(Input); + auto Reader = FileListReader::get(std::move(InputBuf)); + ASSERT_TRUE(!!Reader); + + TestVisitor Visitor; + (*Reader)->visit(Visitor); + EXPECT_EQ(2U, Visitor.Headers.size()); + + FileListReader::HeaderInfo Foo{HeaderType::Public, "/usr/include/foo.h", + std::nullopt}; + EXPECT_TRUE(Foo == Visitor.Headers["foo.h"]); + + FileListReader::HeaderInfo Bar{HeaderType::Private, + "/usr/local/include/bar.h", std::nullopt}; + EXPECT_TRUE(Bar == Visitor.Headers["bar.h"]); +} + +TEST(FileList, MissingVersion) { + static const char Input[] = R"({ + "headers" : [ + { + "type" : "public", + "path" : "/usr/include/foo.h" + }, + { + "type" : "private", + "path" : "/usr/local/include/bar.h" + } + ] + })"; + auto InputBuf = MemoryBuffer::getMemBuffer(Input); + auto Reader = FileListReader::get(std::move(InputBuf)); + EXPECT_FALSE(!!Reader); + consumeError(Reader.takeError()); +} + +TEST(FileList, InvalidTypes) { + static const char Input[] = R"({ + "version" : "1", + "headers" : [ + { + "type" : "project", + "path" : "/usr/include/foo.h" + } + ] + })"; + auto InputBuf = MemoryBuffer::getMemBuffer(Input); + auto Reader = FileListReader::get(std::move(InputBuf)); + EXPECT_FALSE(!!Reader); + EXPECT_STREQ("invalid input format: unsupported header type\n", + toString(Reader.takeError()).c_str()); +} + +TEST(FileListReader, Version2) { + static const auto Input = R"({ + "version" : "2", + "headers" : [ + { + "type" : "public", + "path" : "/usr/include/foo.h" + }, + { + "type" : "project", + "path" : "src/bar.h" + } + ] + })"; + auto InputBuf = MemoryBuffer::getMemBuffer(Input); + auto Reader = FileListReader::get(std::move(InputBuf)); + ASSERT_TRUE(!!Reader); + + TestVisitor Visitor; + (*Reader)->visit(Visitor); + EXPECT_EQ(2U, Visitor.Headers.size()); + + FileListReader::HeaderInfo Foo{HeaderType::Public, "/usr/include/foo.h", + std::nullopt}; + EXPECT_TRUE(Foo == Visitor.Headers["foo.h"]); + + FileListReader::HeaderInfo Bar{HeaderType::Project, "src/bar.h", + std::nullopt}; + EXPECT_TRUE(Bar == Visitor.Headers["bar.h"]); +} diff --git a/clang/unittests/InstallAPI/HeaderFileTest.cpp b/clang/unittests/InstallAPI/HeaderFileTest.cpp new file mode 100644 index 00000000000000..7c0b7c5db36892 --- /dev/null +++ b/clang/unittests/InstallAPI/HeaderFileTest.cpp @@ -0,0 +1,89 @@ +//===- unittests/InstallAPI/HeaderFile.cpp - HeaderFile Test --------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/InstallAPI/HeaderFile.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace clang::installapi; + +TEST(HeaderFile, FrameworkIncludes) { + const char *Path = "/System/Library/Frameworks/Foo.framework/Headers/Foo.h"; + std::optional IncludeName = createIncludeHeaderName(Path); + EXPECT_TRUE(IncludeName.has_value()); + EXPECT_STREQ(IncludeName.value().c_str(), "Foo/Foo.h"); + + Path = "/System/Library/Frameworks/Foo.framework/Frameworks/Bar.framework/" + "Headers/SimpleBar.h"; + IncludeName = createIncludeHeaderName(Path); + EXPECT_TRUE(IncludeName.has_value()); + EXPECT_STREQ(IncludeName.value().c_str(), "Bar/SimpleBar.h"); + + Path = "/tmp/Foo.framework/Versions/A/Headers/SimpleFoo.h"; + IncludeName = createIncludeHeaderName(Path); + EXPECT_TRUE(IncludeName.has_value()); + EXPECT_STREQ(IncludeName.value().c_str(), "Foo/SimpleFoo.h"); + + Path = "/System/Library/PrivateFrameworks/Foo.framework/Headers/Foo.h"; + IncludeName = createIncludeHeaderName(Path); + EXPECT_TRUE(IncludeName.has_value()); + EXPECT_STREQ(IncludeName.value().c_str(), "Foo/Foo.h"); + + Path = "/AppleInternal/Developer/Library/Frameworks/" + "HelloFramework.framework/Headers/HelloFramework.h"; + IncludeName = createIncludeHeaderName(Path); + EXPECT_TRUE(IncludeName.has_value()); + EXPECT_STREQ(IncludeName.value().c_str(), "HelloFramework/HelloFramework.h"); + + Path = "/tmp/BuildProducts/Foo.framework/Versions/A/" + "PrivateHeaders/Foo+Private.h"; + IncludeName = createIncludeHeaderName(Path); + EXPECT_TRUE(IncludeName.has_value()); + EXPECT_STREQ(IncludeName.value().c_str(), "Foo/Foo+Private.h"); + + Path = "/Applications/Xcode.app/Contents/Developer/SDKS/MacOS.sdk/System/" + "Library/Frameworks/Foo.framework/PrivateHeaders/Foo_Private.h"; + IncludeName = createIncludeHeaderName(Path); + EXPECT_TRUE(IncludeName.has_value()); + EXPECT_STREQ(IncludeName.value().c_str(), "Foo/Foo_Private.h"); + + Path = + "/System/Library/PrivateFrameworks/Foo.framework/PrivateHeaders/Foo.hpp"; + IncludeName = createIncludeHeaderName(Path); + EXPECT_TRUE(IncludeName.has_value()); + EXPECT_STREQ(IncludeName.value().c_str(), "Foo/Foo.hpp"); + + Path = "/Applications/Xcode.app/Contents/Developer/SDKS/MacOS.sdk/System/" + "Library/Frameworks/Foo.framework/Headers/BarDir/Bar.h"; + IncludeName = createIncludeHeaderName(Path); + EXPECT_TRUE(IncludeName.has_value()); + EXPECT_STREQ(IncludeName.value().c_str(), "Foo/BarDir/Bar.h"); +} + +TEST(HeaderFile, DylibIncludes) { + const char *Path = "/usr/include/foo.h"; + std::optional IncludeName = createIncludeHeaderName(Path); + EXPECT_TRUE(IncludeName.has_value()); + EXPECT_STREQ(IncludeName.value().c_str(), "foo.h"); + + Path = "/tmp/BuildProducts/usr/include/a/A.h"; + IncludeName = createIncludeHeaderName(Path); + EXPECT_TRUE(IncludeName.has_value()); + EXPECT_STREQ(IncludeName.value().c_str(), "a/A.h"); + + Path = "/Applications/Xcode.app/Contents/Developer/SDKS/MacOS.sdk/" + "usr/include/simd/types.h"; + IncludeName = createIncludeHeaderName(Path); + EXPECT_TRUE(IncludeName.has_value()); + EXPECT_STREQ(IncludeName.value().c_str(), "simd/types.h"); + + Path = "/usr/local/include/hidden/A.h"; + IncludeName = createIncludeHeaderName(Path); + EXPECT_TRUE(IncludeName.has_value()); + EXPECT_STREQ(IncludeName.value().c_str(), "hidden/A.h"); +}