Skip to content

Commit 6ad1cf3

Browse files
authored
[InstallAPI] Add *umbrella-header options (#86587)
Umbrella headers are a concept for Darwin-based libraries. They allow framework authors to control the order in which their headers should be parsed and allow clients to access available headers by including a single header. InstallAPI will attempt to find the umbrella based on the name of the framework. Users can also specify this explicitly by using command line options specifying the umbrella header by file path. There can be an umbrella header per access level.
1 parent 9669aba commit 6ad1cf3

File tree

11 files changed

+206
-7
lines changed

11 files changed

+206
-7
lines changed

clang/include/clang/Basic/DiagnosticInstallAPIKinds.td

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def err_no_output_file: Error<"no output file specified">;
1818
def err_no_such_header_file : Error<"no such %select{public|private|project}1 header file: '%0'">;
1919
def warn_no_such_excluded_header_file : Warning<"no such excluded %select{public|private}0 header file: '%1'">, InGroup<InstallAPIViolation>;
2020
def warn_glob_did_not_match: Warning<"glob '%0' did not match any header file">, InGroup<InstallAPIViolation>;
21+
def err_no_such_umbrella_header_file : Error<"%select{public|private|project}1 umbrella header file not found in input: '%0'">;
2122
} // end of command line category.
2223

2324
let CategoryName = "Verification" in {

clang/include/clang/InstallAPI/HeaderFile.h

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@
2424

2525
namespace clang::installapi {
2626
enum class HeaderType {
27-
/// Unset or unknown type.
28-
Unknown,
2927
/// Represents declarations accessible to all clients.
3028
Public,
3129
/// Represents declarations accessible to a disclosed set of clients.
3230
Private,
3331
/// Represents declarations only accessible as implementation details to the
3432
/// input library.
3533
Project,
34+
/// Unset or unknown type.
35+
Unknown,
3636
};
3737

3838
inline StringRef getName(const HeaderType T) {
@@ -62,6 +62,8 @@ class HeaderFile {
6262
bool Excluded{false};
6363
/// Add header file to processing.
6464
bool Extra{false};
65+
/// Specify that header file is the umbrella header for library.
66+
bool Umbrella{false};
6567

6668
public:
6769
HeaderFile() = delete;
@@ -79,17 +81,21 @@ class HeaderFile {
7981

8082
void setExtra(bool V = true) { Extra = V; }
8183
void setExcluded(bool V = true) { Excluded = V; }
84+
void setUmbrellaHeader(bool V = true) { Umbrella = V; }
8285
bool isExtra() const { return Extra; }
8386
bool isExcluded() const { return Excluded; }
87+
bool isUmbrellaHeader() const { return Umbrella; }
8488

8589
bool useIncludeName() const {
8690
return Type != HeaderType::Project && !IncludeName.empty();
8791
}
8892

8993
bool operator==(const HeaderFile &Other) const {
90-
return std::tie(Type, FullPath, IncludeName, Language, Excluded, Extra) ==
91-
std::tie(Other.Type, Other.FullPath, Other.IncludeName,
92-
Other.Language, Other.Excluded, Other.Extra);
94+
return std::tie(Type, FullPath, IncludeName, Language, Excluded, Extra,
95+
Umbrella) == std::tie(Other.Type, Other.FullPath,
96+
Other.IncludeName, Other.Language,
97+
Other.Excluded, Other.Extra,
98+
Other.Umbrella);
9399
}
94100
};
95101

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#ifndef PUBLIC_UMBRELLA_HEADER_FIRST
2+
#error "Public umbrella header was not included first!"
3+
#endif
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#define PUBLIC_UMBRELLA_HEADER_FIRST
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#ifndef PRIVATE_UMBRELLA_HEADER_FIRST
2+
#error "Private umbrella header was not included first!"
3+
#endif
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#define PRIVATE_UMBRELLA_HEADER_FIRST
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// UNSUPPORTED: system-windows
2+
3+
; RUN: rm -rf %t
4+
; RUN: split-file %s %t
5+
; RUN: sed -e "s|DSTROOT|%/t|g" %t/inputs.json.in > %t/inputs.json
6+
; RUN: mkdir %t/Frameworks/
7+
; RUN: cp -r %S/Inputs/Umbrella/Umbrella.framework %t/Frameworks/
8+
9+
// Only validate path based input that rely on regex matching on unix based file systems.
10+
; RUN: clang-installapi --target=arm64-apple-macosx13 \
11+
; RUN: -install_name /System/Library/Frameworks/Umbrella2.framework/Versions/A/Umbrella \
12+
; RUN: -ObjC -F%t/Frameworks/ %t/inputs.json \
13+
; RUN: --public-umbrella-header=%t/Frameworks/Umbrella.framework/Headers/SpecialUmbrella.h \
14+
; RUN: -private-umbrella-header \
15+
; RUN: %t/Frameworks/Umbrella.framework/PrivateHeaders/SpecialPrivateUmbrella.h \
16+
; RUN: -o %t/output.tbd 2>&1 | FileCheck -allow-empty %s
17+
18+
; CHECK-NOT: error
19+
; CHECK-NOT: warning
20+
21+
;--- inputs.json.in
22+
{
23+
"headers": [ {
24+
"path" : "DSTROOT/Frameworks/Umbrella.framework/Headers/AAA.h",
25+
"type" : "public"
26+
},
27+
{
28+
"path" : "DSTROOT/Frameworks/Umbrella.framework/Headers/SpecialUmbrella.h",
29+
"type" : "public"
30+
},
31+
{
32+
"path" : "DSTROOT/Frameworks/Umbrella.framework/PrivateHeaders/AAA_Private.h",
33+
"type" : "private"
34+
},
35+
{
36+
"path" : "DSTROOT/Frameworks/Umbrella.framework/PrivateHeaders/SpecialPrivateUmbrella.h",
37+
"type" : "private"
38+
}],
39+
"version": "3"
40+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
; RUN: rm -rf %t
2+
; RUN: split-file %s %t
3+
; RUN: sed -e "s|DSTROOT|%/t|g" %t/inputs.json.in > %t/inputs.json
4+
; RUN: cp -r %S/Inputs/Umbrella/Umbrella.framework %t/Frameworks/
5+
6+
// Check base filename matches.
7+
; RUN: clang-installapi --target=arm64-apple-macosx13 \
8+
; RUN: -install_name /System/Library/Frameworks/Umbrella.framework/Versions/A/Umbrella \
9+
; RUN: -ObjC -F%t/Frameworks/ %t/inputs.json \
10+
; RUN: --public-umbrella-header=SpecialUmbrella.h \
11+
; RUN: --private-umbrella-header=SpecialPrivateUmbrella.h \
12+
; RUN: -o %t/output.tbd 2>&1 | FileCheck -allow-empty %s
13+
14+
// Try missing umbrella header argument.
15+
; RUN: not clang-installapi --target=arm64-apple-macosx13 \
16+
; RUN: -install_name /System/Library/Frameworks/Umbrella.framework/Versions/A/Umbrella \
17+
; RUN: -ObjC -F%t/Frameworks/ %t/inputs.json \
18+
; RUN: --public-umbrella-header=Ignore.h \
19+
; RUN: -o %t/output.tbd 2>&1 | FileCheck %s -check-prefix=ERR
20+
21+
; ERR: error: public umbrella header file not found in input: 'Ignore.h'
22+
23+
; CHECK-NOT: error
24+
; CHECK-NOT: warning
25+
26+
;--- Frameworks/Umbrella.framework/Headers/Ignore.h
27+
#error "This header should be ignored"
28+
29+
;--- inputs.json.in
30+
{
31+
"headers": [ {
32+
"path" : "DSTROOT/Frameworks/Umbrella.framework/Headers/AAA.h",
33+
"type" : "public"
34+
},
35+
{
36+
"path" : "DSTROOT/Frameworks/Umbrella.framework/Headers/SpecialUmbrella.h",
37+
"type" : "public"
38+
},
39+
{
40+
"path" : "DSTROOT/Frameworks/Umbrella.framework/PrivateHeaders/AAA_Private.h",
41+
"type" : "private"
42+
},
43+
{
44+
"path" : "DSTROOT/Frameworks/Umbrella.framework/PrivateHeaders/SpecialPrivateUmbrella.h",
45+
"type" : "private"
46+
}],
47+
"version": "3"
48+
}

clang/tools/clang-installapi/InstallAPIOpts.td

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,15 @@ def exclude_private_header : Separate<["-"], "exclude-private-header">,
6161
HelpText<"Exclude private header from parsing">;
6262
def exclude_private_header_EQ : Joined<["--"], "exclude-private-header=">,
6363
Alias<exclude_private_header>;
64+
def public_umbrella_header : Separate<["-"], "public-umbrella-header">,
65+
MetaVarName<"<path>">, HelpText<"Specify the public umbrella header location">;
66+
def public_umbrella_header_EQ : Joined<["--"], "public-umbrella-header=">,
67+
Alias<public_umbrella_header>;
68+
def private_umbrella_header : Separate<["-"], "private-umbrella-header">,
69+
MetaVarName<"<path>">, HelpText<"Specify the private umbrella header location">;
70+
def private_umbrella_header_EQ : Joined<["--"], "private-umbrella-header=">,
71+
Alias<private_umbrella_header>;
72+
def project_umbrella_header : Separate<["-"], "project-umbrella-header">,
73+
MetaVarName<"<path>">, HelpText<"Specify the project umbrella header location">;
74+
def project_umbrella_header_EQ : Joined<["--"], "project-umbrella-header=">,
75+
Alias<project_umbrella_header>;

clang/tools/clang-installapi/Options.cpp

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,16 @@ Options::processAndFilterOutInstallAPIOptions(ArrayRef<const char *> Args) {
270270
OPT_exclude_project_header))
271271
return {};
272272

273+
// Handle umbrella headers.
274+
if (const Arg *A = ParsedArgs.getLastArg(OPT_public_umbrella_header))
275+
DriverOpts.PublicUmbrellaHeader = A->getValue();
276+
277+
if (const Arg *A = ParsedArgs.getLastArg(OPT_private_umbrella_header))
278+
DriverOpts.PrivateUmbrellaHeader = A->getValue();
279+
280+
if (const Arg *A = ParsedArgs.getLastArg(OPT_project_umbrella_header))
281+
DriverOpts.ProjectUmbrellaHeader = A->getValue();
282+
273283
/// Any unclaimed arguments should be forwarded to the clang driver.
274284
std::vector<const char *> ClangDriverArgs(ParsedArgs.size());
275285
for (const Arg *A : ParsedArgs) {
@@ -323,6 +333,15 @@ Options::Options(DiagnosticsEngine &Diag, FileManager *FM,
323333
}
324334
}
325335

336+
static const Regex Rule("(.+)/(.+)\\.framework/");
337+
static StringRef getFrameworkNameFromInstallName(StringRef InstallName) {
338+
SmallVector<StringRef, 3> Match;
339+
Rule.match(InstallName, &Match);
340+
if (Match.empty())
341+
return "";
342+
return Match.back();
343+
}
344+
326345
InstallAPIContext Options::createContext() {
327346
InstallAPIContext Ctx;
328347
Ctx.FM = FM;
@@ -339,6 +358,11 @@ InstallAPIContext Options::createContext() {
339358
Ctx.OutputLoc = DriverOpts.OutputPath;
340359
Ctx.LangMode = FEOpts.LangMode;
341360

361+
// Attempt to find umbrella headers by capturing framework name.
362+
StringRef FrameworkName;
363+
if (!LinkerOpts.IsDylib)
364+
FrameworkName = getFrameworkNameFromInstallName(LinkerOpts.InstallName);
365+
342366
// Process inputs.
343367
for (const std::string &ListPath : DriverOpts.FileLists) {
344368
auto Buffer = FM->getBufferForFile(ListPath);
@@ -357,8 +381,7 @@ InstallAPIContext Options::createContext() {
357381
assert(Type != HeaderType::Unknown && "Missing header type.");
358382
for (const StringRef Path : Headers) {
359383
if (!FM->getOptionalFileRef(Path)) {
360-
Diags->Report(diag::err_no_such_header_file)
361-
<< Path << (unsigned)Type - 1;
384+
Diags->Report(diag::err_no_such_header_file) << Path << (unsigned)Type;
362385
return false;
363386
}
364387
SmallString<PATH_MAX> FullPath(Path);
@@ -382,6 +405,7 @@ InstallAPIContext Options::createContext() {
382405
std::vector<std::unique_ptr<HeaderGlob>> ExcludedHeaderGlobs;
383406
std::set<FileEntryRef> ExcludedHeaderFiles;
384407
auto ParseGlobs = [&](const PathSeq &Paths, HeaderType Type) {
408+
assert(Type != HeaderType::Unknown && "Missing header type.");
385409
for (const StringRef Path : Paths) {
386410
auto Glob = HeaderGlob::create(Path, Type);
387411
if (Glob)
@@ -424,6 +448,57 @@ InstallAPIContext Options::createContext() {
424448
if (!Glob->didMatch())
425449
Diags->Report(diag::warn_glob_did_not_match) << Glob->str();
426450

451+
// Mark any explicit or inferred umbrella headers. If one exists, move
452+
// that to the beginning of the input headers.
453+
auto MarkandMoveUmbrellaInHeaders = [&](llvm::Regex &Regex,
454+
HeaderType Type) -> bool {
455+
auto It = find_if(Ctx.InputHeaders, [&Regex, Type](const HeaderFile &H) {
456+
return (H.getType() == Type) && Regex.match(H.getPath());
457+
});
458+
459+
if (It == Ctx.InputHeaders.end())
460+
return false;
461+
It->setUmbrellaHeader();
462+
463+
// Because there can be an umbrella header per header type,
464+
// find the first non umbrella header to swap position with.
465+
auto BeginPos = find_if(Ctx.InputHeaders, [](const HeaderFile &H) {
466+
return !H.isUmbrellaHeader();
467+
});
468+
if (BeginPos != Ctx.InputHeaders.end() && BeginPos < It)
469+
std::swap(*BeginPos, *It);
470+
return true;
471+
};
472+
473+
auto FindUmbrellaHeader = [&](StringRef HeaderPath, HeaderType Type) -> bool {
474+
assert(Type != HeaderType::Unknown && "Missing header type.");
475+
if (!HeaderPath.empty()) {
476+
auto EscapedString = Regex::escape(HeaderPath);
477+
Regex UmbrellaRegex(EscapedString);
478+
if (!MarkandMoveUmbrellaInHeaders(UmbrellaRegex, Type)) {
479+
Diags->Report(diag::err_no_such_umbrella_header_file)
480+
<< HeaderPath << (unsigned)Type;
481+
return false;
482+
}
483+
} else if (!FrameworkName.empty() && (Type != HeaderType::Project)) {
484+
auto UmbrellaName = "/" + Regex::escape(FrameworkName);
485+
if (Type == HeaderType::Public)
486+
UmbrellaName += "\\.h";
487+
else
488+
UmbrellaName += "[_]?Private\\.h";
489+
Regex UmbrellaRegex(UmbrellaName);
490+
MarkandMoveUmbrellaInHeaders(UmbrellaRegex, Type);
491+
}
492+
return true;
493+
};
494+
if (!FindUmbrellaHeader(DriverOpts.PublicUmbrellaHeader,
495+
HeaderType::Public) ||
496+
!FindUmbrellaHeader(DriverOpts.PrivateUmbrellaHeader,
497+
HeaderType::Private) ||
498+
!FindUmbrellaHeader(DriverOpts.ProjectUmbrellaHeader,
499+
HeaderType::Project))
500+
return Ctx;
501+
427502
// Parse binary dylib and initialize verifier.
428503
if (DriverOpts.DylibToVerify.empty()) {
429504
Ctx.Verifier = std::make_unique<DylibVerifier>();

clang/tools/clang-installapi/Options.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ struct DriverOptions {
3131
/// \brief Path to input file lists (JSON).
3232
llvm::MachO::PathSeq FileLists;
3333

34+
/// \brief Path to public umbrella header.
35+
std::string PublicUmbrellaHeader;
36+
37+
/// \brief Path to private umbrella header.
38+
std::string PrivateUmbrellaHeader;
39+
40+
/// \brief Path to project umbrella header.
41+
std::string ProjectUmbrellaHeader;
42+
3443
/// \brief Paths of extra public headers.
3544
PathSeq ExtraPublicHeaders;
3645

0 commit comments

Comments
 (0)