Skip to content

Commit 9d0da00

Browse files
committed
[clang][InstallAPI] Add input file support to library
This patch adds support for expected InstallAPI inputs. InstallAPI accepts a well defined filelist of headers and how those headers represent a single library. InstallAPI captures header files to determine linkable symbols to then compare against what was compiled in a binary dylib and generate TBD files.
1 parent 0fc5786 commit 9d0da00

File tree

11 files changed

+603
-4
lines changed

11 files changed

+603
-4
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//===- InstallAPI/FileList.h ------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
///
9+
/// The JSON file list parser is used to communicate input to InstallAPI.
10+
///
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef LLVM_CLANG_INSTALLAPI_FILELIST_H
14+
#define LLVM_CLANG_INSTALLAPI_FILELIST_H
15+
16+
#include "clang/Basic/Diagnostic.h"
17+
#include "clang/Basic/FileManager.h"
18+
#include "clang/InstallAPI/HeaderFile.h"
19+
#include "llvm/Support/Error.h"
20+
#include "llvm/Support/MemoryBuffer.h"
21+
22+
namespace clang {
23+
namespace installapi {
24+
25+
/// JSON decoder for InstallAPI Inputs.
26+
class FileListReader {
27+
28+
class Implementation;
29+
Implementation &Impl;
30+
31+
FileListReader(std::unique_ptr<llvm::MemoryBuffer> InputBuffer,
32+
llvm::Error &Err);
33+
34+
public:
35+
/// Decode JSON input and append header input into destination container.
36+
/// Headers are loaded in the order they appear in the JSON input.
37+
///
38+
/// \param InputBuffer JSON input data.
39+
/// \param Destination Container to load headers into.
40+
static llvm::Error
41+
loadHeaders(std::unique_ptr<llvm::MemoryBuffer> InputBuffer,
42+
HeaderSeq &Destination);
43+
44+
~FileListReader();
45+
46+
FileListReader(const FileListReader &) = delete;
47+
FileListReader &operator=(const FileListReader &) = delete;
48+
};
49+
50+
} // namespace installapi
51+
} // namespace clang
52+
53+
#endif // LLVM_CLANG_INSTALLAPI_FILELIST_H
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//===- InstallAPI/HeaderFile.h ----------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
///
9+
/// Representations of a library's headers for InstallAPI.
10+
///
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef LLVM_CLANG_INSTALLAPI_HEADERFILE_H
14+
#define LLVM_CLANG_INSTALLAPI_HEADERFILE_H
15+
16+
#include "clang/Basic/LangStandard.h"
17+
#include "llvm/ADT/StringRef.h"
18+
#include "llvm/Support/Regex.h"
19+
#include <optional>
20+
#include <string>
21+
22+
namespace clang::installapi {
23+
enum class HeaderType {
24+
/// Represents declarations accessible to all clients.
25+
Public,
26+
/// Represents declarations accessible to a disclosed set of clients.
27+
Private,
28+
/// Represents declarations only accessible as implementation details to the
29+
/// input library.
30+
Project,
31+
};
32+
33+
class HeaderFile {
34+
/// Full input path to header.
35+
std::string FullPath;
36+
/// Access level of header.
37+
HeaderType Type;
38+
/// Expected way header will be included by clients.
39+
std::string IncludeName;
40+
/// Supported language mode for header.
41+
std::optional<clang::Language> Language;
42+
43+
public:
44+
HeaderFile(StringRef FullPath, HeaderType Type,
45+
StringRef IncludeName = StringRef(),
46+
std::optional<clang::Language> Language = std::nullopt)
47+
: FullPath(FullPath), Type(Type), IncludeName(IncludeName),
48+
Language(Language) {}
49+
50+
static llvm::Regex getFrameworkIncludeRule();
51+
52+
bool operator==(const HeaderFile &Other) const {
53+
return std::tie(Type, FullPath, IncludeName, Language) ==
54+
std::tie(Other.Type, Other.FullPath, Other.IncludeName,
55+
Other.Language);
56+
}
57+
};
58+
59+
/// Assemble expected way header will be included by clients.
60+
/// As in what maps inside the brackets of `#include <IncludeName.h>`
61+
/// For example,
62+
/// "/System/Library/Frameworks/Foo.framework/Headers/Foo.h" returns
63+
/// "Foo/Foo.h"
64+
///
65+
/// \param FullPath Path to the header file which includes the library
66+
/// structure.
67+
std::optional<std::string> createIncludeHeaderName(const StringRef FullPath);
68+
using HeaderSeq = std::vector<HeaderFile>;
69+
70+
} // namespace clang::installapi
71+
72+
#endif // LLVM_CLANG_INSTALLAPI_HEADERFILE_H

clang/lib/ExtractAPI/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ add_clang_library(clangExtractAPI
1717
clangBasic
1818
clangFrontend
1919
clangIndex
20+
clangInstallAPI
2021
clangLex
2122
)

clang/lib/ExtractAPI/ExtractAPIConsumer.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "clang/Frontend/CompilerInstance.h"
3131
#include "clang/Frontend/FrontendOptions.h"
3232
#include "clang/Frontend/MultiplexConsumer.h"
33+
#include "clang/InstallAPI/HeaderFile.h"
3334
#include "clang/Lex/MacroInfo.h"
3435
#include "clang/Lex/PPCallbacks.h"
3536
#include "clang/Lex/Preprocessor.h"
@@ -61,9 +62,6 @@ std::optional<std::string> getRelativeIncludeName(const CompilerInstance &CI,
6162
"CompilerInstance does not have a FileNamager!");
6263

6364
using namespace llvm::sys;
64-
// Matches framework include patterns
65-
const llvm::Regex Rule("/(.+)\\.framework/(.+)?Headers/(.+)");
66-
6765
const auto &FS = CI.getVirtualFileSystem();
6866

6967
SmallString<128> FilePath(File.begin(), File.end());
@@ -147,7 +145,8 @@ std::optional<std::string> getRelativeIncludeName(const CompilerInstance &CI,
147145
// include name `<Framework/Header.h>`
148146
if (Entry.IsFramework) {
149147
SmallVector<StringRef, 4> Matches;
150-
Rule.match(File, &Matches);
148+
clang::installapi::HeaderFile::getFrameworkIncludeRule().match(
149+
File, &Matches);
151150
// Returned matches are always in stable order.
152151
if (Matches.size() != 4)
153152
return std::nullopt;

clang/lib/InstallAPI/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ set(LLVM_LINK_COMPONENTS
55

66
add_clang_library(clangInstallAPI
77
Context.cpp
8+
FileList.cpp
9+
HeaderFile.cpp
810

911
LINK_LIBS
1012
clangAST

clang/lib/InstallAPI/FileList.cpp

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
//===- FileList.cpp ---------------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "clang/InstallAPI/FileList.h"
10+
#include "clang/Basic/DiagnosticFrontend.h"
11+
#include "clang/InstallAPI/FileList.h"
12+
#include "llvm/ADT/StringSwitch.h"
13+
#include "llvm/Support/Error.h"
14+
#include "llvm/Support/JSON.h"
15+
#include "llvm/TextAPI/TextAPIError.h"
16+
#include <optional>
17+
18+
// clang-format off
19+
/*
20+
InstallAPI JSON Input Format specification.
21+
22+
{
23+
"headers" : [ # Required: Key must exist.
24+
{ # Optional: May contain 0 or more header inputs.
25+
"path" : "/usr/include/mach-o/dlfn.h", # Required: Path should point to destination
26+
# location where applicable.
27+
"type" : "public", # Required: Maps to HeaderType for header.
28+
"language": "c++" # Optional: Language mode for header.
29+
}
30+
],
31+
"version" : "3" # Required: Version 3 supports language mode
32+
& project header input.
33+
}
34+
*/
35+
// clang-format on
36+
37+
using namespace llvm;
38+
using namespace llvm::json;
39+
using namespace llvm::MachO;
40+
using namespace clang::installapi;
41+
42+
class FileListReader::Implementation {
43+
private:
44+
Expected<StringRef> parseString(const Object *Obj, StringRef Key,
45+
StringRef Error);
46+
Expected<StringRef> parsePath(const Object *Obj);
47+
Expected<HeaderType> parseType(const Object *Obj);
48+
std::optional<clang::Language> parseLanguage(const Object *Obj);
49+
Error parseHeaders(Array &Headers);
50+
51+
public:
52+
std::unique_ptr<MemoryBuffer> InputBuffer;
53+
unsigned Version;
54+
HeaderSeq HeaderList;
55+
56+
Error parse(StringRef Input);
57+
};
58+
59+
Expected<StringRef>
60+
FileListReader::Implementation::parseString(const Object *Obj, StringRef Key,
61+
StringRef Error) {
62+
auto Str = Obj->getString(Key);
63+
if (!Str)
64+
return make_error<StringError>(Error, inconvertibleErrorCode());
65+
return *Str;
66+
}
67+
68+
Expected<HeaderType>
69+
FileListReader::Implementation::parseType(const Object *Obj) {
70+
auto TypeStr =
71+
parseString(Obj, "type", "required field 'type' not specified");
72+
if (!TypeStr)
73+
return TypeStr.takeError();
74+
75+
if (*TypeStr == "public")
76+
return HeaderType::Public;
77+
else if (*TypeStr == "private")
78+
return HeaderType::Private;
79+
else if (*TypeStr == "project" && Version >= 2)
80+
return HeaderType::Project;
81+
82+
return make_error<TextAPIError>(TextAPIErrorCode::InvalidInputFormat,
83+
"unsupported header type");
84+
}
85+
86+
Expected<StringRef>
87+
FileListReader::Implementation::parsePath(const Object *Obj) {
88+
auto Path = parseString(Obj, "path", "required field 'path' not specified");
89+
if (!Path)
90+
return Path.takeError();
91+
92+
return *Path;
93+
}
94+
95+
std::optional<clang::Language>
96+
FileListReader::Implementation::parseLanguage(const Object *Obj) {
97+
auto Language = Obj->getString("language");
98+
if (!Language)
99+
return std::nullopt;
100+
101+
return StringSwitch<clang::Language>(*Language)
102+
.Case("c", clang::Language::C)
103+
.Case("c++", clang::Language::CXX)
104+
.Case("objective-c", clang::Language::ObjC)
105+
.Case("objective-c++", clang::Language::ObjCXX)
106+
.Default(clang::Language::Unknown);
107+
}
108+
109+
Error FileListReader::Implementation::parseHeaders(Array &Headers) {
110+
for (const auto &H : Headers) {
111+
auto *Obj = H.getAsObject();
112+
if (!Obj)
113+
return make_error<StringError>("expect a JSON object",
114+
inconvertibleErrorCode());
115+
auto Type = parseType(Obj);
116+
if (!Type)
117+
return Type.takeError();
118+
auto Path = parsePath(Obj);
119+
if (!Path)
120+
return Path.takeError();
121+
auto Language = parseLanguage(Obj);
122+
123+
StringRef PathStr = *Path;
124+
if (*Type == HeaderType::Project) {
125+
HeaderList.emplace_back(
126+
HeaderFile{PathStr, *Type, /*IncludeName=*/"", Language});
127+
continue;
128+
}
129+
auto IncludeName = createIncludeHeaderName(PathStr);
130+
HeaderList.emplace_back(PathStr, *Type,
131+
IncludeName.has_value() ? IncludeName.value() : "",
132+
Language);
133+
}
134+
135+
return Error::success();
136+
}
137+
138+
Error FileListReader::Implementation::parse(StringRef Input) {
139+
auto Val = json::parse(Input);
140+
if (!Val)
141+
return Val.takeError();
142+
143+
auto *Root = Val->getAsObject();
144+
if (!Root)
145+
return make_error<StringError>("not a JSON object",
146+
inconvertibleErrorCode());
147+
148+
auto VersionStr = Root->getString("version");
149+
if (!VersionStr)
150+
return make_error<TextAPIError>(TextAPIErrorCode::InvalidInputFormat,
151+
"required field 'version' not specified");
152+
if (VersionStr->getAsInteger(10, Version))
153+
return make_error<TextAPIError>(TextAPIErrorCode::InvalidInputFormat,
154+
"invalid version number");
155+
156+
if (Version < 1 || Version > 3)
157+
return make_error<TextAPIError>(TextAPIErrorCode::InvalidInputFormat,
158+
"unsupported version");
159+
160+
// Not specifying any header files should be atypical, but valid.
161+
auto Headers = Root->getArray("headers");
162+
if (!Headers)
163+
return Error::success();
164+
165+
Error Err = parseHeaders(*Headers);
166+
if (Err)
167+
return Err;
168+
169+
return Error::success();
170+
}
171+
172+
FileListReader::FileListReader(std::unique_ptr<MemoryBuffer> InputBuffer,
173+
Error &Error)
174+
: Impl(*new FileListReader::Implementation()) {
175+
ErrorAsOutParameter ErrorAsOutParam(&Error);
176+
Impl.InputBuffer = std::move(InputBuffer);
177+
178+
Error = Impl.parse(Impl.InputBuffer->getBuffer());
179+
}
180+
181+
llvm::Error
182+
FileListReader::loadHeaders(std::unique_ptr<MemoryBuffer> InputBuffer,
183+
HeaderSeq &Destination) {
184+
llvm::Error Err = Error::success();
185+
std::unique_ptr<FileListReader> Reader(
186+
new FileListReader(std::move(InputBuffer), Err));
187+
if (Err)
188+
return Err;
189+
190+
Destination.reserve(Destination.size() + Reader->Impl.HeaderList.size());
191+
llvm::move(Reader->Impl.HeaderList, std::back_inserter(Destination));
192+
193+
return Err;
194+
}
195+
196+
FileListReader::~FileListReader() { delete &Impl; }

0 commit comments

Comments
 (0)