[clang] [InstallAPI] Pick up input headers by directory traversal (PR #94508)
Cyndy Ishida via cfe-commits
cfe-commits at lists.llvm.org
Thu Jun 6 12:29:58 PDT 2024
https://github.com/cyndyishida updated https://github.com/llvm/llvm-project/pull/94508
>From c547d990aca29ecfe6f51d37c5c3f8712dfc5e44 Mon Sep 17 00:00:00 2001
From: Cyndy Ishida <cyndy_ishida at apple.com>
Date: Fri, 10 May 2024 09:19:22 -0700
Subject: [PATCH 1/2] [InstallAPI] Pick up input headers by directory traversal
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.
---
.../clang/Basic/DiagnosticInstallAPIKinds.td | 2 +
.../clang/InstallAPI/DirectoryScanner.h | 81 +++++
clang/include/clang/InstallAPI/HeaderFile.h | 8 +
clang/include/clang/InstallAPI/Library.h | 65 ++++
clang/include/clang/InstallAPI/MachO.h | 1 +
clang/lib/InstallAPI/CMakeLists.txt | 2 +
clang/lib/InstallAPI/DirectoryScanner.cpp | 300 ++++++++++++++++++
clang/lib/InstallAPI/Library.cpp | 40 +++
clang/test/InstallAPI/asm.test | 2 +-
clang/test/InstallAPI/basic.test | 4 +-
clang/test/InstallAPI/binary-attributes.test | 6 +-
clang/test/InstallAPI/cpp.test | 4 +-
clang/test/InstallAPI/diagnostics-dsym.test | 4 +-
.../InstallAPI/directory-scanning-dylib.test | 57 ++++
.../directory-scanning-frameworks.test | 89 ++++++
clang/test/InstallAPI/functions.test | 2 +-
clang/test/InstallAPI/variables.test | 2 +-
clang/tools/clang-installapi/Options.cpp | 51 ++-
clang/tools/clang-installapi/Options.h | 3 +
19 files changed, 699 insertions(+), 24 deletions(-)
create mode 100644 clang/include/clang/InstallAPI/DirectoryScanner.h
create mode 100644 clang/include/clang/InstallAPI/Library.h
create mode 100644 clang/lib/InstallAPI/DirectoryScanner.cpp
create mode 100644 clang/lib/InstallAPI/Library.cpp
create mode 100644 clang/test/InstallAPI/directory-scanning-dylib.test
create mode 100644 clang/test/InstallAPI/directory-scanning-frameworks.test
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: -I%t/usr/include -dynamiclib \
// RUN: -install_name @rpath/lib/libasm.dylib \
// RUN: %t/inputs.json -o %t/output.tbd 2>&1 | FileCheck %s --allow-empty
// RUN: llvm-readtapi -compare %t/output.tbd %t/expected.tbd 2>&1 | FileCheck %s --allow-empty
diff --git a/clang/test/InstallAPI/basic.test b/clang/test/InstallAPI/basic.test
index 096911039d114..d86948fd22289 100644
--- a/clang/test/InstallAPI/basic.test
+++ b/clang/test/InstallAPI/basic.test
@@ -3,13 +3,13 @@
/// Check basic arguments are captured.
// RUN: clang-installapi -x objective-c -target arm64-apple-ios13.0.0 \
// RUN: -fapplication-extension -current_version 1 -compatibility_version 1 \
-// RUN: -install_name /usr/lib/basic.dylib \
+// RUN: -install_name /usr/lib/basic.dylib -dynamiclib \
// RUN: %t/basic_inputs.json -o %t/basic.tbd 2>&1 | FileCheck %s --allow-empty
// RUN: llvm-readtapi -compare %t/basic.tbd %t/expected.tbd 2>&1 | FileCheck %s --allow-empty
/// Check multiple targets are captured.
// RUN: clang-installapi -x objective-c -target arm64-apple-ios14.1 -target arm64e-apple-ios14.1 \
-// RUN: -fapplication-extension -install_name /usr/lib/basic.dylib \
+// RUN: -fapplication-extension -install_name /usr/lib/basic.dylib -dynamiclib \
// RUN: %t/basic_inputs.json -o %t/multi-targets.tbd 2>&1 | FileCheck %s --allow-empty
// RUN: llvm-readtapi -compare %t/multi-targets.tbd %t/expected-multi.tbd 2>&1 | FileCheck %s --allow-empty
diff --git a/clang/test/InstallAPI/binary-attributes.test b/clang/test/InstallAPI/binary-attributes.test
index fd9ff12998a34..4c56c01c30aaa 100644
--- a/clang/test/InstallAPI/binary-attributes.test
+++ b/clang/test/InstallAPI/binary-attributes.test
@@ -5,12 +5,14 @@
; RUN: yaml2obj %S/Inputs/Simple/Simple.yaml -o %t/Simple
; RUN: not clang-installapi -target x86_64h-apple-macos10.12 \
-; RUN: -install_name Simple -current_version 3 -compatibility_version 2 \
+; RUN: -install_name /System/Library/Frameworks/Simple.framework/Versions/A/Simple \
+; RUN: -current_version 3 -compatibility_version 2 \
; RUN: -o tmp.tbd --verify-against=%t/Simple 2>&1 | FileCheck -check-prefix=ARCHITECTURE %s
; ARCHITECTURE: error: architectures do not match: 'x86_64h' (provided) vs 'x86_64' (found)
; RUN: not clang-installapi -target x86_64-apple-macos10.12 \
-; RUN: -install_name Simple -current_version 3 -compatibility_version 2 \
+; RUN: -install_name Simple -dynamiclib \
+; RUN: -current_version 3 -compatibility_version 2 \
; RUN: -o tmp.tbd --verify-against=%t/Simple 2>&1 | FileCheck -check-prefix=INSTALL_NAME %s
; INSTALL_NAME: error: install_name does not match: 'Simple' (provided) vs '/System/Library/Frameworks/Simple.framework/Versions/A/Simple' (found)
diff --git a/clang/test/InstallAPI/cpp.test b/clang/test/InstallAPI/cpp.test
index 4817899095302..e29fb0c7fdb68 100644
--- a/clang/test/InstallAPI/cpp.test
+++ b/clang/test/InstallAPI/cpp.test
@@ -4,7 +4,7 @@
// Invoke C++ with no-rtti.
// RUN: clang-installapi -target arm64-apple-macos13.1 \
-// RUN: -I%t/usr/include -I%t/usr/local/include -x c++ \
+// RUN: -I%t/usr/include -I%t/usr/local/include -x c++ -dynamiclib \
// RUN: -install_name @rpath/lib/libcpp.dylib -fno-rtti \
// RUN: %t/inputs.json -o %t/no-rtti.tbd 2>&1 | FileCheck %s --allow-empty
@@ -14,7 +14,7 @@
// Invoke C++ with rtti.
// RUN: clang-installapi -target arm64-apple-macos13.1 \
// RUN: -I%t/usr/include -I%t/usr/local/include -x c++ \
-// RUN: -install_name @rpath/lib/libcpp.dylib -frtti \
+// RUN: -install_name @rpath/lib/libcpp.dylib -frtti -dynamiclib \
// RUN: %t/inputs.json -o %t/rtti.tbd 2>&1 | FileCheck %s --allow-empty
// RUN: llvm-readtapi -compare %t/rtti.tbd \
// RUN: %t/expected-rtti.tbd 2>&1 | FileCheck %s --allow-empty
diff --git a/clang/test/InstallAPI/diagnostics-dsym.test b/clang/test/InstallAPI/diagnostics-dsym.test
index c9cbeffef7bac..42fa67a1f9b1e 100644
--- a/clang/test/InstallAPI/diagnostics-dsym.test
+++ b/clang/test/InstallAPI/diagnostics-dsym.test
@@ -7,14 +7,14 @@
// Build a simple dylib with debug info.
; RUN: %clang --target=arm64-apple-macos11 -g -dynamiclib %t/foo.c \
; RUN: -current_version 1 -compatibility_version 1 -L%t/usr/lib \
-; RUN: -save-temps \
+; RUN: -save-temps -dynamiclib \
; RUN: -o %t/foo.dylib -install_name %t/foo.dylib
; RUN: dsymutil %t/foo.dylib -o %t/foo.dSYM
; RUN: not clang-installapi -x c++ --target=arm64-apple-macos11 \
; RUN: -install_name %t/foo.dylib \
; RUN: -current_version 1 -compatibility_version 1 \
-; RUN: -o %t/output.tbd \
+; RUN: -o %t/output.tbd -dynamiclib \
; RUN: --verify-against=%t/foo.dylib --dsym=%t/foo.dSYM \
; RUN: --verify-mode=Pedantic 2>&1 | FileCheck %s
diff --git a/clang/test/InstallAPI/directory-scanning-dylib.test b/clang/test/InstallAPI/directory-scanning-dylib.test
new file mode 100644
index 0000000000000..ace8745877872
--- /dev/null
+++ b/clang/test/InstallAPI/directory-scanning-dylib.test
@@ -0,0 +1,57 @@
+; RUN: rm -rf %t
+; RUN: split-file %s %t
+; RUN: mkdir -p %t/DstRoot/
+; RUN: cp -r %S/Inputs/LibFoo/ %t/DstRoot/
+
+; RUN: clang-installapi \
+; RUN: -target arm64-apple-macos12 -install_name @rpath/libfoo.dylib \
+; RUN: -current_version 1 -compatibility_version 1 \
+; RUN: -I%t/DstRoot/usr/include -dynamiclib \
+; RUN: -exclude-public-header %t/DstRoot/usr/include/public.h \
+; RUN: %t/DstRoot -o %t/output.tbd 2>&1 | FileCheck %s --allow-empty \
+; RUN: --implicit-check-not=error --implicit-check-not=warning
+; RUN: llvm-readtapi --compare %t/output.tbd %t/expected.tbd
+
+# Test expected error by empty directory.
+; RUN: mkdir -p %t/EmptyRoot
+; RUN: not clang-installapi \
+; RUN: -target arm64-apple-macos12 -install_name @rpath/libfoo.dylib \
+; RUN: -current_version 1 -compatibility_version 1 \
+; RUN: %t/DstRoot/usr/include -dynamiclib \
+; RUN: %t/EmptyRoot -o %t/output.tbd 2>&1 | FileCheck %s --check-prefix=EMPTY
+
+; EMPTY: could not read directory {{.*}} cannot find any public (usr/include) or private (usr/local/include) header directory
+
+;--- expected.tbd
+{
+ "main_library": {
+ "exported_symbols": [
+ {
+ "text": {
+ "global": [
+ "_foo"
+ ]
+ }
+ }
+ ],
+ "flags": [
+ {
+ "attributes": [
+ "not_app_extension_safe"
+ ]
+ }
+ ],
+ "install_names": [
+ {
+ "name": "@rpath/libfoo.dylib"
+ }
+ ],
+ "target_info": [
+ {
+ "min_deployment": "12",
+ "target": "arm64-macos"
+ }
+ ]
+ },
+ "tapi_tbd_version": 5
+}
diff --git a/clang/test/InstallAPI/directory-scanning-frameworks.test b/clang/test/InstallAPI/directory-scanning-frameworks.test
new file mode 100644
index 0000000000000..78f9fe0a92997
--- /dev/null
+++ b/clang/test/InstallAPI/directory-scanning-frameworks.test
@@ -0,0 +1,89 @@
+; RUN: rm -rf %t
+; RUN: split-file %s %t
+; RUN: mkdir -p %t/Frameworks
+; RUN: cp -r %S/Inputs/Simple/Simple.framework %t/Frameworks/
+; RUN: cp -r %S/Inputs/Foundation/Foundation.framework %t/Frameworks/
+
+; RUN: clang-installapi -target x86_64-apple-macosx10.12 \
+; RUN: -install_name /System/Library/Frameworks/Simple.framework/Versions/A/Simple \
+; RUN: -current_version 1.2.3 -compatibility_version 1 -o %t/Simple.tbd \
+; RUN: -exclude-public-header **/Simple.h \
+; RUN: -F %t/Frameworks --verify-mode=ErrorsOnly \
+; RUN: %t/Frameworks/Simple.framework 2>&1 | FileCheck -allow-empty %s \
+; RUN: --implicit-check-not=error --implicit-check-not=warning
+; RUN: llvm-readtapi -compare %t/expected.tbd %t/Simple.tbd
+
+# Test expected error by collecting too many frameworks.
+; RUN: mkdir -p %t/FakeSDKRoot/System/Library/
+; RUN: ln -s %t/Frameworks %t/FakeSDKRoot/System/Library/Frameworks
+; RUN: not clang-installapi -target x86_64-apple-macosx10.12 \
+; RUN: -install_name /System/Library/Frameworks/Simple.framework/Versions/A/Simple \
+; RUN: -current_version 1.2.3 -compatibility_version 1 -o %t/Simple.tbd \
+; RUN: -F %t/Frameworks --verify-mode=ErrorsOnly \
+; RUN: %t/FakeSDKRoot 2>&1 | FileCheck %s --check-prefix=TOO_MANY
+
+; TOO_MANY: error: more than one framework/dynamic library found
+
+;--- expected.tbd
+{
+ "main_library": {
+ "current_versions": [
+ {
+ "version": "1.2.3"
+ }
+ ],
+ "exported_symbols": [
+ {
+ "data": {
+ "global": [
+ "_otherFrameworkAPI",
+ "_otherFrameworkSPI",
+ "_privateGlobalVariable"
+ ],
+ "objc_class": [
+ "Basic6",
+ "Basic1",
+ "Basic3",
+ "Basic4_2",
+ "Basic5",
+ "Basic9",
+ "Basic8",
+ "Basic2",
+ "Basic4",
+ "A",
+ "ExternalManagedObject"
+ ],
+ "objc_ivar": [
+ "Basic4.ivar2",
+ "Basic4_2.ivar1",
+ "Basic6.ivar1",
+ "Basic4.ivar1",
+ "Basic4_2.ivar2"
+ ],
+ "weak": [
+ "_weakPrivateGlobalVariable"
+ ]
+ }
+ }
+ ],
+ "flags": [
+ {
+ "attributes": [
+ "not_app_extension_safe"
+ ]
+ }
+ ],
+ "install_names": [
+ {
+ "name": "/System/Library/Frameworks/Simple.framework/Versions/A/Simple"
+ }
+ ],
+ "target_info": [
+ {
+ "min_deployment": "10.12",
+ "target": "x86_64-macos"
+ }
+ ]
+ },
+ "tapi_tbd_version": 5
+}
diff --git a/clang/test/InstallAPI/functions.test b/clang/test/InstallAPI/functions.test
index 5b5fd1308842e..a50a6a53e1001 100644
--- a/clang/test/InstallAPI/functions.test
+++ b/clang/test/InstallAPI/functions.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 -I%t/usr/local/include \
+// RUN: -I%t/usr/include -I%t/usr/local/include -dynamiclib \
// RUN: -install_name @rpath/lib/libfunctions.dylib --filetype=tbd-v4 \
// RUN: %t/inputs.json -o %t/outputs.tbd 2>&1 | FileCheck %s --allow-empty
// RUN: llvm-readtapi -compare %t/outputs.tbd %t/expected.tbd 2>&1 | FileCheck %s --allow-empty
diff --git a/clang/test/InstallAPI/variables.test b/clang/test/InstallAPI/variables.test
index 6272867911f18..159158a6b91ec 100644
--- a/clang/test/InstallAPI/variables.test
+++ b/clang/test/InstallAPI/variables.test
@@ -4,7 +4,7 @@
/// Check multiple targets are captured.
// RUN: clang-installapi -target arm64-apple-macos13.1 -target arm64e-apple-macos13.1 \
-// RUN: -fapplication-extension -install_name /usr/lib/vars.dylib \
+// RUN: -fapplication-extension -install_name /usr/lib/vars.dylib -dynamiclib \
// RUN: %t/vars_inputs.json -o %t/vars.tbd 2>&1 | FileCheck %s --allow-empty
// RUN: llvm-readtapi -compare %t/vars.tbd %t/expected.tbd 2>&1 | FileCheck %s --allow-empty
diff --git a/clang/tools/clang-installapi/Options.cpp b/clang/tools/clang-installapi/Options.cpp
index 95d28b7b040d7..1ca1d583d5ccd 100644
--- a/clang/tools/clang-installapi/Options.cpp
+++ b/clang/tools/clang-installapi/Options.cpp
@@ -9,6 +9,7 @@
#include "Options.h"
#include "clang/Basic/DiagnosticIDs.h"
#include "clang/Driver/Driver.h"
+#include "clang/InstallAPI/DirectoryScanner.h"
#include "clang/InstallAPI/FileList.h"
#include "clang/InstallAPI/HeaderFile.h"
#include "clang/InstallAPI/InstallAPIDiagnostic.h"
@@ -126,8 +127,14 @@ getArgListFromJSON(const StringRef Input, llvm::opt::OptTable *Table,
bool Options::processDriverOptions(InputArgList &Args) {
// Handle inputs.
- llvm::append_range(DriverOpts.FileLists,
- Args.getAllArgValues(drv::OPT_INPUT));
+ for (const StringRef Path : Args.getAllArgValues(drv::OPT_INPUT)) {
+ // Assume any input that is not a directory is a filelist.
+ // InstallAPI does not accept multiple directories, so retain the last one.
+ if (FM->getOptionalDirectoryRef(Path))
+ DriverOpts.InputDirectory = Path.str();
+ else
+ DriverOpts.FileLists.emplace_back(Path.str());
+ }
// Handle output.
SmallString<PATH_MAX> OutputPath;
@@ -760,15 +767,6 @@ Options::Options(DiagnosticsEngine &Diag, FileManager *FM,
}
}
-static const Regex Rule("(.+)/(.+)\\.framework/");
-static StringRef getFrameworkNameFromInstallName(StringRef InstallName) {
- SmallVector<StringRef, 3> Match;
- Rule.match(InstallName, &Match);
- if (Match.empty())
- return "";
- return Match.back();
-}
-
static Expected<std::unique_ptr<InterfaceFile>>
getInterfaceFile(const StringRef Filename) {
ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
@@ -897,9 +895,36 @@ InstallAPIContext Options::createContext() {
// Attempt to find umbrella headers by capturing framework name.
StringRef FrameworkName;
if (!LinkerOpts.IsDylib)
- FrameworkName = getFrameworkNameFromInstallName(LinkerOpts.InstallName);
+ FrameworkName =
+ Library::getFrameworkNameFromInstallName(LinkerOpts.InstallName);
+
+ /// Process inputs headers.
+ // 1. For headers discovered by directory scanning, sort them.
+ // 2. For headers discovered by filelist, respect ordering.
+ // 3. Append extra headers and mark any excluded headers.
+ // 4. Finally, surface up umbrella headers to top of the list.
+ if (!DriverOpts.InputDirectory.empty()) {
+ DirectoryScanner Scanner(*FM, LinkerOpts.IsDylib
+ ? ScanMode::ScanDylibs
+ : ScanMode::ScanFrameworks);
+ SmallString<PATH_MAX> NormalizedPath(DriverOpts.InputDirectory);
+ FM->getVirtualFileSystem().makeAbsolute(NormalizedPath);
+ sys::path::remove_dots(NormalizedPath, /*remove_dot_dot=*/true);
+ if (llvm::Error Err = Scanner.scan(NormalizedPath)) {
+ Diags->Report(diag::err_directory_scanning)
+ << DriverOpts.InputDirectory << std::move(Err);
+ return Ctx;
+ }
+ std::vector<Library> InputLibraries = Scanner.takeLibraries();
+ if (InputLibraries.size() > 1) {
+ Diags->Report(diag::err_more_than_one_library);
+ return Ctx;
+ }
+ llvm::append_range(Ctx.InputHeaders,
+ DirectoryScanner::getHeaders(InputLibraries));
+ llvm::stable_sort(Ctx.InputHeaders);
+ }
- // Process inputs.
for (const StringRef ListPath : DriverOpts.FileLists) {
auto Buffer = FM->getBufferForFile(ListPath);
if (auto Err = Buffer.getError()) {
diff --git a/clang/tools/clang-installapi/Options.h b/clang/tools/clang-installapi/Options.h
index b37f91efbda72..d62f2efd3141a 100644
--- a/clang/tools/clang-installapi/Options.h
+++ b/clang/tools/clang-installapi/Options.h
@@ -30,6 +30,9 @@ struct DriverOptions {
/// \brief Path to input file lists (JSON).
llvm::MachO::PathSeq FileLists;
+ /// \brief Path to input directory.
+ std::string InputDirectory;
+
/// \brief Path to public umbrella header.
std::string PublicUmbrellaHeader;
>From d859ae6176359a69f0e8d97fbb9dc20d6c6afd58 Mon Sep 17 00:00:00 2001
From: Cyndy Ishida <cyndy_ishida at apple.com>
Date: Thu, 6 Jun 2024 12:29:41 -0700
Subject: [PATCH 2/2] Change up cli syntax to be more cross platform friendly
in tests
---
clang/test/InstallAPI/directory-scanning-dylib.test | 2 +-
clang/test/InstallAPI/directory-scanning-frameworks.test | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/test/InstallAPI/directory-scanning-dylib.test b/clang/test/InstallAPI/directory-scanning-dylib.test
index ace8745877872..b81b29c5da9b5 100644
--- a/clang/test/InstallAPI/directory-scanning-dylib.test
+++ b/clang/test/InstallAPI/directory-scanning-dylib.test
@@ -1,7 +1,7 @@
; RUN: rm -rf %t
; RUN: split-file %s %t
; RUN: mkdir -p %t/DstRoot/
-; RUN: cp -r %S/Inputs/LibFoo/ %t/DstRoot/
+; RUN: cp -r %S/Inputs/LibFoo/* %t/DstRoot/
; RUN: clang-installapi \
; RUN: -target arm64-apple-macos12 -install_name @rpath/libfoo.dylib \
diff --git a/clang/test/InstallAPI/directory-scanning-frameworks.test b/clang/test/InstallAPI/directory-scanning-frameworks.test
index 78f9fe0a92997..fa127853b618a 100644
--- a/clang/test/InstallAPI/directory-scanning-frameworks.test
+++ b/clang/test/InstallAPI/directory-scanning-frameworks.test
@@ -1,8 +1,8 @@
; RUN: rm -rf %t
; RUN: split-file %s %t
; RUN: mkdir -p %t/Frameworks
-; RUN: cp -r %S/Inputs/Simple/Simple.framework %t/Frameworks/
-; RUN: cp -r %S/Inputs/Foundation/Foundation.framework %t/Frameworks/
+; RUN: cp -r %S/Inputs/Simple/* %t/Frameworks/
+; RUN: cp -r %S/Inputs/Foundation/* %t/Frameworks/
; RUN: clang-installapi -target x86_64-apple-macosx10.12 \
; RUN: -install_name /System/Library/Frameworks/Simple.framework/Versions/A/Simple \
More information about the cfe-commits
mailing list