Skip to content

[InstallAPI] Add *umbrella-header options #86587

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang/include/clang/Basic/DiagnosticInstallAPIKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def err_no_output_file: Error<"no output file specified">;
def err_no_such_header_file : Error<"no such %select{public|private|project}1 header file: '%0'">;
def warn_no_such_excluded_header_file : Warning<"no such excluded %select{public|private}0 header file: '%1'">, InGroup<InstallAPIViolation>;
def warn_glob_did_not_match: Warning<"glob '%0' did not match any header file">, InGroup<InstallAPIViolation>;
def err_no_such_umbrella_header_file : Error<"%select{public|private|project}1 umbrella header file not found in input: '%0'">;
} // end of command line category.

let CategoryName = "Verification" in {
Expand Down
16 changes: 11 additions & 5 deletions clang/include/clang/InstallAPI/HeaderFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@

namespace clang::installapi {
enum class HeaderType {
/// Unset or unknown type.
Unknown,
/// 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,
/// Unset or unknown type.
Unknown,
};

inline StringRef getName(const HeaderType T) {
Expand Down Expand Up @@ -62,6 +62,8 @@ class HeaderFile {
bool Excluded{false};
/// Add header file to processing.
bool Extra{false};
/// Specify that header file is the umbrella header for library.
bool Umbrella{false};

public:
HeaderFile() = delete;
Expand All @@ -79,17 +81,21 @@ class HeaderFile {

void setExtra(bool V = true) { Extra = V; }
void setExcluded(bool V = true) { Excluded = V; }
void setUmbrellaHeader(bool V = true) { Umbrella = V; }
bool isExtra() const { return Extra; }
bool isExcluded() const { return Excluded; }
bool isUmbrellaHeader() const { return Umbrella; }

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

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#ifndef PUBLIC_UMBRELLA_HEADER_FIRST
#error "Public umbrella header was not included first!"
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#define PUBLIC_UMBRELLA_HEADER_FIRST
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#ifndef PRIVATE_UMBRELLA_HEADER_FIRST
#error "Private umbrella header was not included first!"
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#define PRIVATE_UMBRELLA_HEADER_FIRST
40 changes: 40 additions & 0 deletions clang/test/InstallAPI/umbrella-headers-unix.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// UNSUPPORTED: system-windows

; RUN: rm -rf %t
; RUN: split-file %s %t
; RUN: sed -e "s|DSTROOT|%/t|g" %t/inputs.json.in > %t/inputs.json
; RUN: mkdir %t/Frameworks/
; RUN: cp -r %S/Inputs/Umbrella/Umbrella.framework %t/Frameworks/

// Only validate path based input that rely on regex matching on unix based file systems.
; RUN: clang-installapi --target=arm64-apple-macosx13 \
; RUN: -install_name /System/Library/Frameworks/Umbrella2.framework/Versions/A/Umbrella \
; RUN: -ObjC -F%t/Frameworks/ %t/inputs.json \
; RUN: --public-umbrella-header=%t/Frameworks/Umbrella.framework/Headers/SpecialUmbrella.h \
; RUN: -private-umbrella-header \
; RUN: %t/Frameworks/Umbrella.framework/PrivateHeaders/SpecialPrivateUmbrella.h \
; RUN: -o %t/output.tbd 2>&1 | FileCheck -allow-empty %s

; CHECK-NOT: error
; CHECK-NOT: warning

;--- inputs.json.in
{
"headers": [ {
"path" : "DSTROOT/Frameworks/Umbrella.framework/Headers/AAA.h",
"type" : "public"
},
{
"path" : "DSTROOT/Frameworks/Umbrella.framework/Headers/SpecialUmbrella.h",
"type" : "public"
},
{
"path" : "DSTROOT/Frameworks/Umbrella.framework/PrivateHeaders/AAA_Private.h",
"type" : "private"
},
{
"path" : "DSTROOT/Frameworks/Umbrella.framework/PrivateHeaders/SpecialPrivateUmbrella.h",
"type" : "private"
}],
"version": "3"
}
48 changes: 48 additions & 0 deletions clang/test/InstallAPI/umbrella-headers.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
; RUN: rm -rf %t
; RUN: split-file %s %t
; RUN: sed -e "s|DSTROOT|%/t|g" %t/inputs.json.in > %t/inputs.json
; RUN: cp -r %S/Inputs/Umbrella/Umbrella.framework %t/Frameworks/

// Check base filename matches.
; RUN: clang-installapi --target=arm64-apple-macosx13 \
; RUN: -install_name /System/Library/Frameworks/Umbrella.framework/Versions/A/Umbrella \
; RUN: -ObjC -F%t/Frameworks/ %t/inputs.json \
; RUN: --public-umbrella-header=SpecialUmbrella.h \
; RUN: --private-umbrella-header=SpecialPrivateUmbrella.h \
; RUN: -o %t/output.tbd 2>&1 | FileCheck -allow-empty %s

// Try missing umbrella header argument.
; RUN: not clang-installapi --target=arm64-apple-macosx13 \
; RUN: -install_name /System/Library/Frameworks/Umbrella.framework/Versions/A/Umbrella \
; RUN: -ObjC -F%t/Frameworks/ %t/inputs.json \
; RUN: --public-umbrella-header=Ignore.h \
; RUN: -o %t/output.tbd 2>&1 | FileCheck %s -check-prefix=ERR

; ERR: error: public umbrella header file not found in input: 'Ignore.h'

; CHECK-NOT: error
; CHECK-NOT: warning

;--- Frameworks/Umbrella.framework/Headers/Ignore.h
#error "This header should be ignored"

;--- inputs.json.in
{
"headers": [ {
"path" : "DSTROOT/Frameworks/Umbrella.framework/Headers/AAA.h",
"type" : "public"
},
{
"path" : "DSTROOT/Frameworks/Umbrella.framework/Headers/SpecialUmbrella.h",
"type" : "public"
},
{
"path" : "DSTROOT/Frameworks/Umbrella.framework/PrivateHeaders/AAA_Private.h",
"type" : "private"
},
{
"path" : "DSTROOT/Frameworks/Umbrella.framework/PrivateHeaders/SpecialPrivateUmbrella.h",
"type" : "private"
}],
"version": "3"
}
12 changes: 12 additions & 0 deletions clang/tools/clang-installapi/InstallAPIOpts.td
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,15 @@ def exclude_private_header : Separate<["-"], "exclude-private-header">,
HelpText<"Exclude private header from parsing">;
def exclude_private_header_EQ : Joined<["--"], "exclude-private-header=">,
Alias<exclude_private_header>;
def public_umbrella_header : Separate<["-"], "public-umbrella-header">,
MetaVarName<"<path>">, HelpText<"Specify the public umbrella header location">;
def public_umbrella_header_EQ : Joined<["--"], "public-umbrella-header=">,
Alias<public_umbrella_header>;
def private_umbrella_header : Separate<["-"], "private-umbrella-header">,
MetaVarName<"<path>">, HelpText<"Specify the private umbrella header location">;
def private_umbrella_header_EQ : Joined<["--"], "private-umbrella-header=">,
Alias<private_umbrella_header>;
def project_umbrella_header : Separate<["-"], "project-umbrella-header">,
MetaVarName<"<path>">, HelpText<"Specify the project umbrella header location">;
def project_umbrella_header_EQ : Joined<["--"], "project-umbrella-header=">,
Alias<project_umbrella_header>;
79 changes: 77 additions & 2 deletions clang/tools/clang-installapi/Options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,16 @@ Options::processAndFilterOutInstallAPIOptions(ArrayRef<const char *> Args) {
OPT_exclude_project_header))
return {};

// Handle umbrella headers.
if (const Arg *A = ParsedArgs.getLastArg(OPT_public_umbrella_header))
DriverOpts.PublicUmbrellaHeader = A->getValue();

if (const Arg *A = ParsedArgs.getLastArg(OPT_private_umbrella_header))
DriverOpts.PrivateUmbrellaHeader = A->getValue();

if (const Arg *A = ParsedArgs.getLastArg(OPT_project_umbrella_header))
DriverOpts.ProjectUmbrellaHeader = A->getValue();

/// Any unclaimed arguments should be forwarded to the clang driver.
std::vector<const char *> ClangDriverArgs(ParsedArgs.size());
for (const Arg *A : ParsedArgs) {
Expand Down Expand Up @@ -323,6 +333,15 @@ 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();
}

InstallAPIContext Options::createContext() {
InstallAPIContext Ctx;
Ctx.FM = FM;
Expand All @@ -339,6 +358,11 @@ InstallAPIContext Options::createContext() {
Ctx.OutputLoc = DriverOpts.OutputPath;
Ctx.LangMode = FEOpts.LangMode;

// Attempt to find umbrella headers by capturing framework name.
StringRef FrameworkName;
if (!LinkerOpts.IsDylib)
FrameworkName = getFrameworkNameFromInstallName(LinkerOpts.InstallName);

// Process inputs.
for (const std::string &ListPath : DriverOpts.FileLists) {
auto Buffer = FM->getBufferForFile(ListPath);
Expand All @@ -357,8 +381,7 @@ InstallAPIContext Options::createContext() {
assert(Type != HeaderType::Unknown && "Missing header type.");
for (const StringRef Path : Headers) {
if (!FM->getOptionalFileRef(Path)) {
Diags->Report(diag::err_no_such_header_file)
<< Path << (unsigned)Type - 1;
Diags->Report(diag::err_no_such_header_file) << Path << (unsigned)Type;
return false;
}
SmallString<PATH_MAX> FullPath(Path);
Expand All @@ -382,6 +405,7 @@ InstallAPIContext Options::createContext() {
std::vector<std::unique_ptr<HeaderGlob>> ExcludedHeaderGlobs;
std::set<FileEntryRef> ExcludedHeaderFiles;
auto ParseGlobs = [&](const PathSeq &Paths, HeaderType Type) {
assert(Type != HeaderType::Unknown && "Missing header type.");
for (const StringRef Path : Paths) {
auto Glob = HeaderGlob::create(Path, Type);
if (Glob)
Expand Down Expand Up @@ -424,6 +448,57 @@ InstallAPIContext Options::createContext() {
if (!Glob->didMatch())
Diags->Report(diag::warn_glob_did_not_match) << Glob->str();

// Mark any explicit or inferred umbrella headers. If one exists, move
// that to the beginning of the input headers.
auto MarkandMoveUmbrellaInHeaders = [&](llvm::Regex &Regex,
HeaderType Type) -> bool {
auto It = find_if(Ctx.InputHeaders, [&Regex, Type](const HeaderFile &H) {
return (H.getType() == Type) && Regex.match(H.getPath());
});

if (It == Ctx.InputHeaders.end())
return false;
It->setUmbrellaHeader();

// Because there can be an umbrella header per header type,
// find the first non umbrella header to swap position with.
auto BeginPos = find_if(Ctx.InputHeaders, [](const HeaderFile &H) {
return !H.isUmbrellaHeader();
});
if (BeginPos != Ctx.InputHeaders.end() && BeginPos < It)
std::swap(*BeginPos, *It);
return true;
};

auto FindUmbrellaHeader = [&](StringRef HeaderPath, HeaderType Type) -> bool {
assert(Type != HeaderType::Unknown && "Missing header type.");
if (!HeaderPath.empty()) {
auto EscapedString = Regex::escape(HeaderPath);
Regex UmbrellaRegex(EscapedString);
if (!MarkandMoveUmbrellaInHeaders(UmbrellaRegex, Type)) {
Diags->Report(diag::err_no_such_umbrella_header_file)
<< HeaderPath << (unsigned)Type;
return false;
}
} else if (!FrameworkName.empty() && (Type != HeaderType::Project)) {
auto UmbrellaName = "/" + Regex::escape(FrameworkName);
if (Type == HeaderType::Public)
UmbrellaName += "\\.h";
else
UmbrellaName += "[_]?Private\\.h";
Regex UmbrellaRegex(UmbrellaName);
MarkandMoveUmbrellaInHeaders(UmbrellaRegex, Type);
}
return true;
};
if (!FindUmbrellaHeader(DriverOpts.PublicUmbrellaHeader,
HeaderType::Public) ||
!FindUmbrellaHeader(DriverOpts.PrivateUmbrellaHeader,
HeaderType::Private) ||
!FindUmbrellaHeader(DriverOpts.ProjectUmbrellaHeader,
HeaderType::Project))
return Ctx;

// Parse binary dylib and initialize verifier.
if (DriverOpts.DylibToVerify.empty()) {
Ctx.Verifier = std::make_unique<DylibVerifier>();
Expand Down
9 changes: 9 additions & 0 deletions clang/tools/clang-installapi/Options.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ struct DriverOptions {
/// \brief Path to input file lists (JSON).
llvm::MachO::PathSeq FileLists;

/// \brief Path to public umbrella header.
std::string PublicUmbrellaHeader;

/// \brief Path to private umbrella header.
std::string PrivateUmbrellaHeader;

/// \brief Path to project umbrella header.
std::string ProjectUmbrellaHeader;

/// \brief Paths of extra public headers.
PathSeq ExtraPublicHeaders;

Expand Down