[clang] [InstallAPI] Pick up input headers by directory traversal (PR #94508)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Jun 5 10:43:35 PDT 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Cyndy Ishida (cyndyishida)
<details>
<summary>Changes</summary>
Match TAPI behavior and allow input headers to be resolved via a passed directory, which is expected to be a library sitting in a build directory.
---
Patch is 34.57 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/94508.diff
19 Files Affected:
- (modified) clang/include/clang/Basic/DiagnosticInstallAPIKinds.td (+2)
- (added) clang/include/clang/InstallAPI/DirectoryScanner.h (+81)
- (modified) clang/include/clang/InstallAPI/HeaderFile.h (+8)
- (added) clang/include/clang/InstallAPI/Library.h (+65)
- (modified) clang/include/clang/InstallAPI/MachO.h (+1)
- (modified) clang/lib/InstallAPI/CMakeLists.txt (+2)
- (added) clang/lib/InstallAPI/DirectoryScanner.cpp (+300)
- (added) clang/lib/InstallAPI/Library.cpp (+40)
- (modified) clang/test/InstallAPI/asm.test (+1-1)
- (modified) clang/test/InstallAPI/basic.test (+2-2)
- (modified) clang/test/InstallAPI/binary-attributes.test (+4-2)
- (modified) clang/test/InstallAPI/cpp.test (+2-2)
- (modified) clang/test/InstallAPI/diagnostics-dsym.test (+2-2)
- (added) clang/test/InstallAPI/directory-scanning-dylib.test (+57)
- (added) clang/test/InstallAPI/directory-scanning-frameworks.test (+89)
- (modified) clang/test/InstallAPI/functions.test (+1-1)
- (modified) clang/test/InstallAPI/variables.test (+1-1)
- (modified) clang/tools/clang-installapi/Options.cpp (+38-13)
- (modified) clang/tools/clang-installapi/Options.h (+3)
``````````diff
diff --git a/clang/include/clang/Basic/DiagnosticInstallAPIKinds.td b/clang/include/clang/Basic/DiagnosticInstallAPIKinds.td
index cdf27247602f2..e10fa71011f30 100644
--- a/clang/include/clang/Basic/DiagnosticInstallAPIKinds.td
+++ b/clang/include/clang/Basic/DiagnosticInstallAPIKinds.td
@@ -26,6 +26,8 @@ def err_unsupported_environment : Error<"environment '%0' is not supported: '%1'
def err_unsupported_os : Error<"os '%0' is not supported: '%1'">;
def err_cannot_read_input_list : Error<"could not read %0 input list '%1': %2">;
def err_invalid_label: Error<"label '%0' is reserved: use a different label name for -X<label>">;
+def err_directory_scanning: Error<"could not read directory '%0': %1">;
+def err_more_than_one_library: Error<"more than one framework/dynamic library found">;
} // end of command line category.
let CategoryName = "Verification" in {
diff --git a/clang/include/clang/InstallAPI/DirectoryScanner.h b/clang/include/clang/InstallAPI/DirectoryScanner.h
new file mode 100644
index 0000000000000..803328982ec87
--- /dev/null
+++ b/clang/include/clang/InstallAPI/DirectoryScanner.h
@@ -0,0 +1,81 @@
+//===- InstallAPI/DirectoryScanner.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 DirectoryScanner for collecting library files on the file system.
+///
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_CLANG_INSTALLAPI_DIRECTORYSCANNER_H
+#define LLVM_CLANG_INSTALLAPI_DIRECTORYSCANNER_H
+
+#include "clang/Basic/FileManager.h"
+#include "clang/InstallAPI/Library.h"
+
+namespace clang::installapi {
+
+enum ScanMode {
+ /// Scanning Framework directory.
+ ScanFrameworks,
+ /// Scanning Dylib directory.
+ ScanDylibs,
+};
+
+class DirectoryScanner {
+public:
+ DirectoryScanner(FileManager &FM, ScanMode Mode = ScanMode::ScanFrameworks)
+ : FM(FM), Mode(Mode) {}
+
+ /// Scan for all input files throughout directory.
+ ///
+ /// \param Directory Path of input directory.
+ llvm::Error scan(StringRef Directory);
+
+ /// Take over ownership of stored libraries.
+ std::vector<Library> takeLibraries() { return std::move(Libraries); };
+
+ /// Get all the header files in libraries.
+ ///
+ /// \param Libraries Reference of collection of libraries.
+ static HeaderSeq getHeaders(ArrayRef<Library> Libraries);
+
+private:
+ /// Collect files for dylibs in usr/(local)/lib within directory.
+ llvm::Error scanForUnwrappedLibraries(StringRef Directory);
+
+ /// Collect files for any frameworks within directory.
+ llvm::Error scanForFrameworks(StringRef Directory);
+
+ /// Get a library from the libraries collection.
+ Library &getOrCreateLibrary(StringRef Path, std::vector<Library> &Libs) const;
+
+ /// Collect multiple frameworks from directory.
+ llvm::Error scanMultipleFrameworks(StringRef Directory,
+ std::vector<Library> &Libs) const;
+ /// Collect files from nested frameworks.
+ llvm::Error scanSubFrameworksDirectory(StringRef Directory,
+ std::vector<Library> &Libs) const;
+
+ /// Collect files from framework path.
+ llvm::Error scanFrameworkDirectory(StringRef Path, Library &Framework) const;
+
+ /// Collect header files from path.
+ llvm::Error scanHeaders(StringRef Path, Library &Lib, HeaderType Type,
+ StringRef BasePath,
+ StringRef ParentPath = StringRef()) const;
+
+ /// Collect files from Version directories inside Framework directories.
+ llvm::Error scanFrameworkVersionsDirectory(StringRef Path,
+ Library &Lib) const;
+ FileManager &FM;
+ ScanMode Mode;
+ StringRef RootPath;
+ std::vector<Library> Libraries;
+};
+
+} // namespace clang::installapi
+
+#endif // LLVM_CLANG_INSTALLAPI_DIRECTORYSCANNER_H
diff --git a/clang/include/clang/InstallAPI/HeaderFile.h b/clang/include/clang/InstallAPI/HeaderFile.h
index c67503d4ad49e..12a87c01ad1c4 100644
--- a/clang/include/clang/InstallAPI/HeaderFile.h
+++ b/clang/include/clang/InstallAPI/HeaderFile.h
@@ -97,6 +97,14 @@ class HeaderFile {
Other.Excluded, Other.Extra,
Other.Umbrella);
}
+
+ bool operator<(const HeaderFile &Other) const {
+ if (isExtra() && Other.isExtra())
+ return std::tie(Type, Umbrella) < std::tie(Other.Type, Other.Umbrella);
+
+ return std::tie(Type, Umbrella, Extra, FullPath) <
+ std::tie(Other.Type, Other.Umbrella, Other.Extra, Other.FullPath);
+ }
};
/// Glob that represents a pattern of header files to retreive.
diff --git a/clang/include/clang/InstallAPI/Library.h b/clang/include/clang/InstallAPI/Library.h
new file mode 100644
index 0000000000000..8373d424dd364
--- /dev/null
+++ b/clang/include/clang/InstallAPI/Library.h
@@ -0,0 +1,65 @@
+//===- InstallAPI/Library.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
+//
+//===----------------------------------------------------------------------===//
+///
+/// Defines the content of a library, such as public and private
+/// header files, and whether it is a framework.
+///
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_CLANG_INSTALLAPI_LIBRARY_H
+#define LLVM_CLANG_INSTALLAPI_LIBRARY_H
+
+#include "clang/InstallAPI/HeaderFile.h"
+#include "clang/InstallAPI/MachO.h"
+
+namespace clang::installapi {
+
+class Library {
+public:
+ Library(StringRef Directory) : BaseDirectory(Directory) {}
+
+ /// Capture the name of the framework by the install name.
+ ///
+ /// \param InstallName The install name of the library encoded in a dynamic
+ /// library.
+ static StringRef getFrameworkNameFromInstallName(StringRef InstallName);
+
+ /// Get name of library by the discovered file path.
+ StringRef getName() const;
+
+ /// Get discovered path of library.
+ StringRef getPath() const { return BaseDirectory; }
+
+ /// Add a header file that belongs to the library.
+ ///
+ /// \param FullPath Path to header file.
+ /// \param Type Access level of header.
+ /// \param IncludePath The way the header should be included.
+ void addHeaderFile(StringRef FullPath, HeaderType Type,
+ StringRef IncludePath = StringRef()) {
+ Headers.emplace_back(FullPath, Type, IncludePath);
+ }
+
+ /// Determine if library is empty.
+ bool empty() {
+ return SubFrameworks.empty() && Headers.empty() &&
+ FrameworkVersions.empty();
+ }
+
+private:
+ std::string BaseDirectory;
+ HeaderSeq Headers;
+ std::vector<Library> SubFrameworks;
+ std::vector<Library> FrameworkVersions;
+ bool IsUnwrappedDylib{false};
+
+ friend class DirectoryScanner;
+};
+
+} // namespace clang::installapi
+
+#endif // LLVM_CLANG_INSTALLAPI_LIBRARY_H
diff --git a/clang/include/clang/InstallAPI/MachO.h b/clang/include/clang/InstallAPI/MachO.h
index 1ea544412f4cd..6036a7e5397cb 100644
--- a/clang/include/clang/InstallAPI/MachO.h
+++ b/clang/include/clang/InstallAPI/MachO.h
@@ -31,6 +31,7 @@ using RecordLinkage = llvm::MachO::RecordLinkage;
using Record = llvm::MachO::Record;
using EncodeKind = llvm::MachO::EncodeKind;
using GlobalRecord = llvm::MachO::GlobalRecord;
+using InterfaceFile = llvm::MachO::InterfaceFile;
using ObjCContainerRecord = llvm::MachO::ObjCContainerRecord;
using ObjCInterfaceRecord = llvm::MachO::ObjCInterfaceRecord;
using ObjCCategoryRecord = llvm::MachO::ObjCCategoryRecord;
diff --git a/clang/lib/InstallAPI/CMakeLists.txt b/clang/lib/InstallAPI/CMakeLists.txt
index b36493942300b..b63173bc1be3e 100644
--- a/clang/lib/InstallAPI/CMakeLists.txt
+++ b/clang/lib/InstallAPI/CMakeLists.txt
@@ -8,10 +8,12 @@ set(LLVM_LINK_COMPONENTS
add_clang_library(clangInstallAPI
DiagnosticBuilderWrappers.cpp
+ DirectoryScanner.cpp
DylibVerifier.cpp
FileList.cpp
Frontend.cpp
HeaderFile.cpp
+ Library.cpp
Visitor.cpp
LINK_LIBS
diff --git a/clang/lib/InstallAPI/DirectoryScanner.cpp b/clang/lib/InstallAPI/DirectoryScanner.cpp
new file mode 100644
index 0000000000000..ae4ba52de6ba9
--- /dev/null
+++ b/clang/lib/InstallAPI/DirectoryScanner.cpp
@@ -0,0 +1,300 @@
+//===- DirectoryScanner.cpp -----------------------------------------------===//
+//
+// 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/DirectoryScanner.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/StringSwitch.h"
+#include "llvm/TextAPI/DylibReader.h"
+
+using namespace llvm;
+using namespace llvm::MachO;
+
+namespace clang::installapi {
+
+HeaderSeq DirectoryScanner::getHeaders(ArrayRef<Library> Libraries) {
+ HeaderSeq Headers;
+ for (const Library &Lib : Libraries)
+ llvm::append_range(Headers, Lib.Headers);
+ return Headers;
+}
+
+llvm::Error DirectoryScanner::scan(StringRef Directory) {
+ if (Mode == ScanMode::ScanFrameworks)
+ return scanForFrameworks(Directory);
+
+ return scanForUnwrappedLibraries(Directory);
+}
+
+llvm::Error DirectoryScanner::scanForUnwrappedLibraries(StringRef Directory) {
+ // Check some known sub-directory locations.
+ auto GetDirectory = [&](const char *Sub) -> OptionalDirectoryEntryRef {
+ SmallString<PATH_MAX> Path(Directory);
+ sys::path::append(Path, Sub);
+ return FM.getOptionalDirectoryRef(Path);
+ };
+
+ auto DirPublic = GetDirectory("usr/include");
+ auto DirPrivate = GetDirectory("usr/local/include");
+ if (!DirPublic && !DirPrivate) {
+ std::error_code ec = std::make_error_code(std::errc::not_a_directory);
+ return createStringError(ec,
+ "cannot find any public (usr/include) or private "
+ "(usr/local/include) header directory");
+ }
+
+ Library &Lib = getOrCreateLibrary(Directory, Libraries);
+ Lib.IsUnwrappedDylib = true;
+
+ if (DirPublic)
+ if (Error Err = scanHeaders(DirPublic->getName(), Lib, HeaderType::Public,
+ Directory))
+ return Err;
+
+ if (DirPrivate)
+ if (Error Err = scanHeaders(DirPrivate->getName(), Lib, HeaderType::Private,
+ Directory))
+ return Err;
+
+ return Error::success();
+}
+
+static bool isFramework(StringRef Path) {
+ while (Path.back() == '/')
+ Path = Path.slice(0, Path.size() - 1);
+
+ return llvm::StringSwitch<bool>(llvm::sys::path::extension(Path))
+ .Case(".framework", true)
+ .Default(false);
+}
+
+Library &
+DirectoryScanner::getOrCreateLibrary(StringRef Path,
+ std::vector<Library> &Libs) const {
+ if (Path.consume_front(RootPath) && Path.empty())
+ Path = "/";
+
+ auto LibIt =
+ find_if(Libs, [Path](const Library &L) { return L.getPath() == Path; });
+ if (LibIt != Libs.end())
+ return *LibIt;
+
+ Libs.emplace_back(Path);
+ return Libs.back();
+}
+
+Error DirectoryScanner::scanHeaders(StringRef Path, Library &Lib,
+ HeaderType Type, StringRef BasePath,
+ StringRef ParentPath) const {
+ std::error_code ec;
+ auto &FS = FM.getVirtualFileSystem();
+ PathSeq SubDirectories;
+ for (vfs::directory_iterator i = FS.dir_begin(Path, ec), ie; i != ie;
+ i.increment(ec)) {
+ StringRef HeaderPath = i->path();
+ if (ec)
+ return createStringError(ec, "unable to read: " + HeaderPath);
+
+ if (sys::fs::is_symlink_file(HeaderPath))
+ continue;
+
+ // Ignore tmp files from unifdef.
+ const StringRef Filename = sys::path::filename(HeaderPath);
+ if (Filename.starts_with("."))
+ continue;
+
+ // If it is a directory, remember the subdirectory.
+ if (FM.getOptionalDirectoryRef(HeaderPath))
+ SubDirectories.push_back(HeaderPath.str());
+
+ if (!isHeaderFile(HeaderPath))
+ continue;
+
+ // Skip files that do not exist. This usually happens for broken symlinks.
+ if (FS.status(HeaderPath) == std::errc::no_such_file_or_directory)
+ continue;
+
+ auto IncludeName = createIncludeHeaderName(HeaderPath);
+ Lib.addHeaderFile(HeaderPath, Type,
+ IncludeName.has_value() ? IncludeName.value() : "");
+ }
+
+ // Go through the subdirectories.
+ // Sort the sub-directory first since different file systems might have
+ // different traverse order.
+ llvm::sort(SubDirectories);
+ if (ParentPath.empty())
+ ParentPath = Path;
+ for (const StringRef Dir : SubDirectories)
+ return scanHeaders(Dir, Lib, Type, BasePath, ParentPath);
+
+ return Error::success();
+}
+
+llvm::Error
+DirectoryScanner::scanMultipleFrameworks(StringRef Directory,
+ std::vector<Library> &Libs) const {
+ std::error_code ec;
+ auto &FS = FM.getVirtualFileSystem();
+ for (vfs::directory_iterator i = FS.dir_begin(Directory, ec), ie; i != ie;
+ i.increment(ec)) {
+ StringRef Curr = i->path();
+
+ // Skip files that do not exist. This usually happens for broken symlinks.
+ if (ec == std::errc::no_such_file_or_directory) {
+ ec.clear();
+ continue;
+ }
+ if (ec)
+ return createStringError(ec, Curr);
+
+ if (sys::fs::is_symlink_file(Curr))
+ continue;
+
+ if (isFramework(Curr)) {
+ if (FM.getOptionalDirectoryRef(Curr))
+ continue;
+ Library &Framework = getOrCreateLibrary(Curr, Libs);
+ if (Error Err = scanFrameworkDirectory(Curr, Framework))
+ return Err;
+ }
+ }
+
+ return Error::success();
+}
+
+llvm::Error
+DirectoryScanner::scanSubFrameworksDirectory(StringRef Directory,
+ std::vector<Library> &Libs) const {
+ if (FM.getOptionalDirectoryRef(Directory))
+ return scanMultipleFrameworks(Directory, Libs);
+
+ std::error_code ec = std::make_error_code(std::errc::not_a_directory);
+ return createStringError(ec, Directory);
+}
+
+/// FIXME: How to handle versions? For now scan them separately as independent
+/// frameworks.
+llvm::Error
+DirectoryScanner::scanFrameworkVersionsDirectory(StringRef Path,
+ Library &Lib) const {
+ std::error_code ec;
+ auto &FS = FM.getVirtualFileSystem();
+ for (vfs::directory_iterator i = FS.dir_begin(Path, ec), ie; i != ie;
+ i.increment(ec)) {
+ const StringRef Curr = i->path();
+
+ // Skip files that do not exist. This usually happens for broken symlinks.
+ if (ec == std::errc::no_such_file_or_directory) {
+ ec.clear();
+ continue;
+ }
+ if (ec)
+ return createStringError(ec, Curr);
+
+ if (sys::fs::is_symlink_file(Curr))
+ continue;
+
+ // Each version should be a framework directory.
+ if (!FM.getOptionalDirectoryRef(Curr))
+ continue;
+
+ Library &VersionedFramework =
+ getOrCreateLibrary(Curr, Lib.FrameworkVersions);
+ if (Error Err = scanFrameworkDirectory(Curr, VersionedFramework))
+ return Err;
+ }
+
+ return Error::success();
+}
+
+llvm::Error DirectoryScanner::scanFrameworkDirectory(StringRef Path,
+ Library &Framework) const {
+ // If the framework is inside Kernel or IOKit, scan headers in the different
+ // directories separately.
+ Framework.IsUnwrappedDylib =
+ Path.contains("Kernel.framework") || Path.contains("IOKit.framework");
+
+ // Unfortunately we cannot identify symlinks in the VFS. We assume that if
+ // there is a Versions directory, then we have symlinks and directly proceed
+ // to the Versions folder.
+ std::error_code ec;
+ auto &FS = FM.getVirtualFileSystem();
+
+ for (vfs::directory_iterator i = FS.dir_begin(Path, ec), ie; i != ie;
+ i.increment(ec)) {
+ StringRef Curr = i->path();
+ // Skip files that do not exist. This usually happens for broken symlinks.
+ if (ec == std::errc::no_such_file_or_directory) {
+ ec.clear();
+ continue;
+ }
+
+ if (ec)
+ return createStringError(ec, Curr);
+
+ if (sys::fs::is_symlink_file(Curr))
+ continue;
+
+ StringRef FileName = sys::path::filename(Curr);
+ // Scan all "public" headers.
+ if (FileName.contains("Headers")) {
+ if (Error Err = scanHeaders(Curr, Framework, HeaderType::Public, Curr))
+ return Err;
+ continue;
+ }
+ // Scan all "private" headers.
+ if (FileName.contains("PrivateHeaders")) {
+ if (Error Err = scanHeaders(Curr, Framework, HeaderType::Private, Curr))
+ return Err;
+ continue;
+ }
+ // Scan sub frameworks.
+ if (FileName.contains("Frameworks")) {
+ if (Error Err = scanSubFrameworksDirectory(Curr, Framework.SubFrameworks))
+ return Err;
+ continue;
+ }
+ // Check for versioned frameworks.
+ if (FileName.contains("Versions")) {
+ if (Error Err = scanFrameworkVersionsDirectory(Curr, Framework))
+ return Err;
+ continue;
+ }
+ }
+
+ return Error::success();
+}
+
+llvm::Error DirectoryScanner::scanForFrameworks(StringRef Directory) {
+ RootPath = "";
+
+ // Expect a certain directory structure and naming convention to find
+ // frameworks.
+ static const char *SubDirectories[] = {"System/Library/Frameworks/",
+ "System/Library/PrivateFrameworks/"};
+
+ // Check if the directory is already a framework.
+ if (isFramework(Directory)) {
+ Library &Framework = getOrCreateLibrary(Directory, Libraries);
+ if (Error Err = scanFrameworkDirectory(Directory, Framework))
+ return Err;
+ return Error::success();
+ }
+
+ // Check known sub-directory locations.
+ for (const auto *SubDir : SubDirectories) {
+ SmallString<PATH_MAX> Path(Directory);
+ sys::path::append(Path, SubDir);
+
+ if (Error Err = scanMultipleFrameworks(Path, Libraries))
+ return Err;
+ }
+
+ return Error::success();
+}
+} // namespace clang::installapi
diff --git a/clang/lib/InstallAPI/Library.cpp b/clang/lib/InstallAPI/Library.cpp
new file mode 100644
index 0000000000000..bdfa3535273e1
--- /dev/null
+++ b/clang/lib/InstallAPI/Library.cpp
@@ -0,0 +1,40 @@
+//===- Library.cpp --------------------------------------------------------===//
+//
+// 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/Library.h"
+
+using namespace llvm;
+namespace clang::installapi {
+
+const Regex Rule("(.+)/(.+)\\.framework/");
+StringRef Library::getFrameworkNameFromInstallName(StringRef InstallName) {
+ assert(InstallName.contains(".framework") && "expected a framework");
+ SmallVector<StringRef, 3> Match;
+ Rule.match(InstallName, &Match);
+ if (Match.empty())
+ return "";
+ return Match.back();
+}
+
+StringRef Library::getName() const {
+ assert(!IsUnwrappedDylib && "expected a framework");
+ StringRef Path = BaseDirectory;
+
+ // Return the framework name extracted from path.
+ while (!Path.empty()) {
+ if (Path.ends_with(".framework"))
+ return sys::path::filename(Path);
+ Path = sys::path::parent_path(Path);
+ }
+
+ // Otherwise, return the name of the BaseDirectory.
+ Path = BaseDirectory;
+ return sys::path::filename(Path.rtrim("/"));
+}
+
+} // namespace clang::installapi
diff --git a/clang/test/InstallAPI/asm.test b/clang/test/InstallAPI/asm.test
index b6af7f643d72f..9df644a823909 100644
--- a/clang/test/InstallAPI/asm.test
+++ b/clang/test/InstallAPI/asm.test
@@ -3,7 +3,7 @@
// RUN: sed -e "s|DSTROOT|%/t|g" %t/inputs.json.in > %t/inputs.json
// RUN: clang-installapi -target arm64-apple-macos13.1 \
-// RUN: -I%t/usr/include \
+// RUN: -...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/94508
More information about the cfe-commits
mailing list