Skip to content

Commit

Permalink
[clang][InstallAPI] Add input file support to library (#81701)
Browse files Browse the repository at this point in the history
This patch adds support for expected InstallAPI inputs. InstallAPI
accepts a well defined filelist of headers and how those headers
represent a single library.

InstallAPI captures header files to determine linkable symbols to then
compare against what was compiled in a binary dylib and generate TBD
files.
  • Loading branch information
cyndyishida committed Feb 20, 2024
1 parent ae8facc commit 4c6043d
Show file tree
Hide file tree
Showing 11 changed files with 591 additions and 4 deletions.
42 changes: 42 additions & 0 deletions clang/include/clang/InstallAPI/FileList.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//===- InstallAPI/FileList.h ------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// The JSON file list parser is used to communicate input to InstallAPI.
///
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_INSTALLAPI_FILELIST_H
#define LLVM_CLANG_INSTALLAPI_FILELIST_H

#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/FileManager.h"
#include "clang/InstallAPI/HeaderFile.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/MemoryBuffer.h"

namespace clang {
namespace installapi {

class FileListReader {
public:
/// Decode JSON input and append header input into destination container.
/// Headers are loaded in the order they appear in the JSON input.
///
/// \param InputBuffer JSON input data.
/// \param Destination Container to load headers into.
static llvm::Error
loadHeaders(std::unique_ptr<llvm::MemoryBuffer> InputBuffer,
HeaderSeq &Destination);

FileListReader() = delete;
};

} // namespace installapi
} // namespace clang

#endif // LLVM_CLANG_INSTALLAPI_FILELIST_H
72 changes: 72 additions & 0 deletions clang/include/clang/InstallAPI/HeaderFile.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//===- InstallAPI/HeaderFile.h ----------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// Representations of a library's headers for InstallAPI.
///
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_INSTALLAPI_HEADERFILE_H
#define LLVM_CLANG_INSTALLAPI_HEADERFILE_H

#include "clang/Basic/LangStandard.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Regex.h"
#include <optional>
#include <string>

namespace clang::installapi {
enum class HeaderType {
/// Represents declarations accessible to all clients.
Public,
/// Represents declarations accessible to a disclosed set of clients.
Private,
/// Represents declarations only accessible as implementation details to the
/// input library.
Project,
};

class HeaderFile {
/// Full input path to header.
std::string FullPath;
/// Access level of header.
HeaderType Type;
/// Expected way header will be included by clients.
std::string IncludeName;
/// Supported language mode for header.
std::optional<clang::Language> Language;

public:
HeaderFile(StringRef FullPath, HeaderType Type,
StringRef IncludeName = StringRef(),
std::optional<clang::Language> Language = std::nullopt)
: FullPath(FullPath), Type(Type), IncludeName(IncludeName),
Language(Language) {}

static llvm::Regex getFrameworkIncludeRule();

bool operator==(const HeaderFile &Other) const {
return std::tie(Type, FullPath, IncludeName, Language) ==
std::tie(Other.Type, Other.FullPath, Other.IncludeName,
Other.Language);
}
};

/// Assemble expected way header will be included by clients.
/// As in what maps inside the brackets of `#include <IncludeName.h>`
/// For example,
/// "/System/Library/Frameworks/Foo.framework/Headers/Foo.h" returns
/// "Foo/Foo.h"
///
/// \param FullPath Path to the header file which includes the library
/// structure.
std::optional<std::string> createIncludeHeaderName(const StringRef FullPath);
using HeaderSeq = std::vector<HeaderFile>;

} // namespace clang::installapi

#endif // LLVM_CLANG_INSTALLAPI_HEADERFILE_H
1 change: 1 addition & 0 deletions clang/lib/ExtractAPI/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ add_clang_library(clangExtractAPI
clangBasic
clangFrontend
clangIndex
clangInstallAPI
clangLex
)
7 changes: 3 additions & 4 deletions clang/lib/ExtractAPI/ExtractAPIConsumer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendOptions.h"
#include "clang/Frontend/MultiplexConsumer.h"
#include "clang/InstallAPI/HeaderFile.h"
#include "clang/Lex/MacroInfo.h"
#include "clang/Lex/PPCallbacks.h"
#include "clang/Lex/Preprocessor.h"
Expand Down Expand Up @@ -61,9 +62,6 @@ std::optional<std::string> getRelativeIncludeName(const CompilerInstance &CI,
"CompilerInstance does not have a FileNamager!");

using namespace llvm::sys;
// Matches framework include patterns
const llvm::Regex Rule("/(.+)\\.framework/(.+)?Headers/(.+)");

const auto &FS = CI.getVirtualFileSystem();

SmallString<128> FilePath(File.begin(), File.end());
Expand Down Expand Up @@ -147,7 +145,8 @@ std::optional<std::string> getRelativeIncludeName(const CompilerInstance &CI,
// include name `<Framework/Header.h>`
if (Entry.IsFramework) {
SmallVector<StringRef, 4> Matches;
Rule.match(File, &Matches);
clang::installapi::HeaderFile::getFrameworkIncludeRule().match(
File, &Matches);
// Returned matches are always in stable order.
if (Matches.size() != 4)
return std::nullopt;
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/InstallAPI/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ set(LLVM_LINK_COMPONENTS

add_clang_library(clangInstallAPI
Context.cpp
FileList.cpp
HeaderFile.cpp

LINK_LIBS
clangAST
Expand Down
184 changes: 184 additions & 0 deletions clang/lib/InstallAPI/FileList.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
//===- FileList.cpp ---------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "clang/InstallAPI/FileList.h"
#include "clang/Basic/DiagnosticFrontend.h"
#include "clang/InstallAPI/FileList.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/JSON.h"
#include "llvm/TextAPI/TextAPIError.h"
#include <optional>

// clang-format off
/*
InstallAPI JSON Input Format specification.
{
"headers" : [ # Required: Key must exist.
{ # Optional: May contain 0 or more header inputs.
"path" : "/usr/include/mach-o/dlfn.h", # Required: Path should point to destination
# location where applicable.
"type" : "public", # Required: Maps to HeaderType for header.
"language": "c++" # Optional: Language mode for header.
}
],
"version" : "3" # Required: Version 3 supports language mode
& project header input.
}
*/
// clang-format on

using namespace llvm;
using namespace llvm::json;
using namespace llvm::MachO;
using namespace clang::installapi;

namespace {
class Implementation {
private:
Expected<StringRef> parseString(const Object *Obj, StringRef Key,
StringRef Error);
Expected<StringRef> parsePath(const Object *Obj);
Expected<HeaderType> parseType(const Object *Obj);
std::optional<clang::Language> parseLanguage(const Object *Obj);
Error parseHeaders(Array &Headers);

public:
std::unique_ptr<MemoryBuffer> InputBuffer;
unsigned Version;
HeaderSeq HeaderList;

Error parse(StringRef Input);
};

Expected<StringRef>
Implementation::parseString(const Object *Obj, StringRef Key, StringRef Error) {
auto Str = Obj->getString(Key);
if (!Str)
return make_error<StringError>(Error, inconvertibleErrorCode());
return *Str;
}

Expected<HeaderType> Implementation::parseType(const Object *Obj) {
auto TypeStr =
parseString(Obj, "type", "required field 'type' not specified");
if (!TypeStr)
return TypeStr.takeError();

if (*TypeStr == "public")
return HeaderType::Public;
else if (*TypeStr == "private")
return HeaderType::Private;
else if (*TypeStr == "project" && Version >= 2)
return HeaderType::Project;

return make_error<TextAPIError>(TextAPIErrorCode::InvalidInputFormat,
"unsupported header type");
}

Expected<StringRef> Implementation::parsePath(const Object *Obj) {
auto Path = parseString(Obj, "path", "required field 'path' not specified");
if (!Path)
return Path.takeError();

return *Path;
}

std::optional<clang::Language>
Implementation::parseLanguage(const Object *Obj) {
auto Language = Obj->getString("language");
if (!Language)
return std::nullopt;

return StringSwitch<clang::Language>(*Language)
.Case("c", clang::Language::C)
.Case("c++", clang::Language::CXX)
.Case("objective-c", clang::Language::ObjC)
.Case("objective-c++", clang::Language::ObjCXX)
.Default(clang::Language::Unknown);
}

Error Implementation::parseHeaders(Array &Headers) {
for (const auto &H : Headers) {
auto *Obj = H.getAsObject();
if (!Obj)
return make_error<StringError>("expect a JSON object",
inconvertibleErrorCode());
auto Type = parseType(Obj);
if (!Type)
return Type.takeError();
auto Path = parsePath(Obj);
if (!Path)
return Path.takeError();
auto Language = parseLanguage(Obj);

StringRef PathStr = *Path;
if (*Type == HeaderType::Project) {
HeaderList.emplace_back(
HeaderFile{PathStr, *Type, /*IncludeName=*/"", Language});
continue;
}
auto IncludeName = createIncludeHeaderName(PathStr);
HeaderList.emplace_back(PathStr, *Type,
IncludeName.has_value() ? IncludeName.value() : "",
Language);
}

return Error::success();
}

Error Implementation::parse(StringRef Input) {
auto Val = json::parse(Input);
if (!Val)
return Val.takeError();

auto *Root = Val->getAsObject();
if (!Root)
return make_error<StringError>("not a JSON object",
inconvertibleErrorCode());

auto VersionStr = Root->getString("version");
if (!VersionStr)
return make_error<TextAPIError>(TextAPIErrorCode::InvalidInputFormat,
"required field 'version' not specified");
if (VersionStr->getAsInteger(10, Version))
return make_error<TextAPIError>(TextAPIErrorCode::InvalidInputFormat,
"invalid version number");

if (Version < 1 || Version > 3)
return make_error<TextAPIError>(TextAPIErrorCode::InvalidInputFormat,
"unsupported version");

// Not specifying any header files should be atypical, but valid.
auto Headers = Root->getArray("headers");
if (!Headers)
return Error::success();

Error Err = parseHeaders(*Headers);
if (Err)
return Err;

return Error::success();
}
} // namespace

llvm::Error
FileListReader::loadHeaders(std::unique_ptr<MemoryBuffer> InputBuffer,
HeaderSeq &Destination) {
Implementation Impl;
Impl.InputBuffer = std::move(InputBuffer);

if (llvm::Error Err = Impl.parse(Impl.InputBuffer->getBuffer()))
return Err;

Destination.reserve(Destination.size() + Impl.HeaderList.size());
llvm::move(Impl.HeaderList, std::back_inserter(Destination));

return Error::success();
}
37 changes: 37 additions & 0 deletions clang/lib/InstallAPI/HeaderFile.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//===- HeaderFile.cpp ------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "clang/InstallAPI/HeaderFile.h"

using namespace llvm;
namespace clang::installapi {

llvm::Regex HeaderFile::getFrameworkIncludeRule() {
return llvm::Regex("/(.+)\\.framework/(.+)?Headers/(.+)");
}

std::optional<std::string> createIncludeHeaderName(const StringRef FullPath) {
// Headers in usr(/local)*/include.
std::string Pattern = "/include/";
auto PathPrefix = FullPath.find(Pattern);
if (PathPrefix != StringRef::npos) {
PathPrefix += Pattern.size();
return FullPath.drop_front(PathPrefix).str();
}

// Framework Headers.
SmallVector<StringRef, 4> Matches;
HeaderFile::getFrameworkIncludeRule().match(FullPath, &Matches);
// Returned matches are always in stable order.
if (Matches.size() != 4)
return std::nullopt;

return Matches[1].drop_front(Matches[1].rfind('/') + 1).str() + "/" +
Matches[3].str();
}
} // namespace clang::installapi
1 change: 1 addition & 0 deletions clang/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,6 @@ endif()
add_subdirectory(DirectoryWatcher)
add_subdirectory(Rename)
add_subdirectory(Index)
add_subdirectory(InstallAPI)
add_subdirectory(Serialization)
add_subdirectory(Support)
Loading

0 comments on commit 4c6043d

Please sign in to comment.