Author: Cyndy Ishida Date: 2024-06-14T13:08:27-07:00 New Revision: feed66f3eae5006bb05e6cb34801930fd940daa8
URL: https://github.com/llvm/llvm-project/commit/feed66f3eae5006bb05e6cb34801930fd940daa8 DIFF: https://github.com/llvm/llvm-project/commit/feed66f3eae5006bb05e6cb34801930fd940daa8.diff LOG: [InstallAPI] Pick up input headers by directory traversal (#94508) 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. Added: clang/include/clang/InstallAPI/DirectoryScanner.h clang/include/clang/InstallAPI/Library.h clang/lib/InstallAPI/DirectoryScanner.cpp clang/lib/InstallAPI/Library.cpp clang/test/InstallAPI/directory-scanning-dylib.test clang/test/InstallAPI/directory-scanning-frameworks.test Modified: clang/include/clang/Basic/DiagnosticInstallAPIKinds.td clang/include/clang/InstallAPI/HeaderFile.h clang/include/clang/InstallAPI/MachO.h clang/lib/InstallAPI/CMakeLists.txt clang/test/InstallAPI/asm.test clang/test/InstallAPI/basic.test clang/test/InstallAPI/binary-attributes.test clang/test/InstallAPI/cpp.test clang/test/InstallAPI/diagnostics-dsym.test clang/test/InstallAPI/functions.test clang/test/InstallAPI/variables.test clang/tools/clang-installapi/Options.cpp clang/tools/clang-installapi/Options.h Removed: ################################################################################ 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 diff erent 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..26843101e62ea 100644 --- a/clang/include/clang/InstallAPI/HeaderFile.h +++ b/clang/include/clang/InstallAPI/HeaderFile.h @@ -97,6 +97,19 @@ class HeaderFile { Other.Excluded, Other.Extra, Other.Umbrella); } + + bool operator<(const HeaderFile &Other) const { + /// For parsing of headers based on ordering, + /// group by type, then whether its an umbrella. + /// Capture 'extra' headers last. + /// This optimizes the chance of a sucessful parse for + /// headers that violate IWYU. + 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..8984758e7b446 --- /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 diff erent file systems might have + // diff erent 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 diff erent + // 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..b81b29c5da9b5 --- /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..029a1cdda5600 --- /dev/null +++ b/clang/test/InstallAPI/directory-scanning-frameworks.test @@ -0,0 +1,88 @@ +; RUN: rm -rf %t +; RUN: split-file %s %t +; RUN: mkdir -p %t/SDKRoot/System/Library/Frameworks +; RUN: cp -r %S/Inputs/Simple/* %t/SDKRoot/System/Library/Frameworks/ +; RUN: cp -r %S/Inputs/Foundation/* %t/SDKRoot/System/Library/Frameworks/ +# Skip over header that produces warnings. +; RUN: rm %t/SDKRoot/System/Library/Frameworks/Simple.framework/Headers/Simple.h + +; 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: -F %t/SDKRoot/System/Library/Frameworks --verify-mode=ErrorsOnly \ +; RUN: %t/SDKRoot/System/Library/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: 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/SDKRoot/System/Library/Frameworks --verify-mode=ErrorsOnly \ +; RUN: %t/SDKRoot/ 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; _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits