diff --git a/clang/include/clang/Basic/DiagnosticInstallAPIKinds.td b/clang/include/clang/Basic/DiagnosticInstallAPIKinds.td index 27df731fa2862..e3263fe9ccb9d 100644 --- a/clang/include/clang/Basic/DiagnosticInstallAPIKinds.td +++ b/clang/include/clang/Basic/DiagnosticInstallAPIKinds.td @@ -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; def warn_glob_did_not_match: Warning<"glob '%0' did not match any header file">, InGroup; +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 { diff --git a/clang/include/clang/InstallAPI/HeaderFile.h b/clang/include/clang/InstallAPI/HeaderFile.h index 235b4da3add84..c67503d4ad49e 100644 --- a/clang/include/clang/InstallAPI/HeaderFile.h +++ b/clang/include/clang/InstallAPI/HeaderFile.h @@ -24,8 +24,6 @@ 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. @@ -33,6 +31,8 @@ enum class HeaderType { /// Represents declarations only accessible as implementation details to the /// input library. Project, + /// Unset or unknown type. + Unknown, }; inline StringRef getName(const HeaderType T) { @@ -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; @@ -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); } }; diff --git a/clang/test/InstallAPI/Inputs/Umbrella/Umbrella.framework/Headers/AAA.h b/clang/test/InstallAPI/Inputs/Umbrella/Umbrella.framework/Headers/AAA.h new file mode 100644 index 0000000000000..993d5d4abadb8 --- /dev/null +++ b/clang/test/InstallAPI/Inputs/Umbrella/Umbrella.framework/Headers/AAA.h @@ -0,0 +1,3 @@ +#ifndef PUBLIC_UMBRELLA_HEADER_FIRST +#error "Public umbrella header was not included first!" +#endif diff --git a/clang/test/InstallAPI/Inputs/Umbrella/Umbrella.framework/Headers/SpecialUmbrella.h b/clang/test/InstallAPI/Inputs/Umbrella/Umbrella.framework/Headers/SpecialUmbrella.h new file mode 100644 index 0000000000000..2599ff14ae172 --- /dev/null +++ b/clang/test/InstallAPI/Inputs/Umbrella/Umbrella.framework/Headers/SpecialUmbrella.h @@ -0,0 +1 @@ +#define PUBLIC_UMBRELLA_HEADER_FIRST diff --git a/clang/test/InstallAPI/Inputs/Umbrella/Umbrella.framework/PrivateHeaders/AAA_Private.h b/clang/test/InstallAPI/Inputs/Umbrella/Umbrella.framework/PrivateHeaders/AAA_Private.h new file mode 100644 index 0000000000000..557209bfeb869 --- /dev/null +++ b/clang/test/InstallAPI/Inputs/Umbrella/Umbrella.framework/PrivateHeaders/AAA_Private.h @@ -0,0 +1,3 @@ +#ifndef PRIVATE_UMBRELLA_HEADER_FIRST +#error "Private umbrella header was not included first!" +#endif diff --git a/clang/test/InstallAPI/Inputs/Umbrella/Umbrella.framework/PrivateHeaders/SpecialPrivateUmbrella.h b/clang/test/InstallAPI/Inputs/Umbrella/Umbrella.framework/PrivateHeaders/SpecialPrivateUmbrella.h new file mode 100644 index 0000000000000..fd5b49b943161 --- /dev/null +++ b/clang/test/InstallAPI/Inputs/Umbrella/Umbrella.framework/PrivateHeaders/SpecialPrivateUmbrella.h @@ -0,0 +1 @@ +#define PRIVATE_UMBRELLA_HEADER_FIRST diff --git a/clang/test/InstallAPI/umbrella-headers-unix.test b/clang/test/InstallAPI/umbrella-headers-unix.test new file mode 100644 index 0000000000000..46118779896cf --- /dev/null +++ b/clang/test/InstallAPI/umbrella-headers-unix.test @@ -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" +} diff --git a/clang/test/InstallAPI/umbrella-headers.test b/clang/test/InstallAPI/umbrella-headers.test new file mode 100644 index 0000000000000..ce9c50608c411 --- /dev/null +++ b/clang/test/InstallAPI/umbrella-headers.test @@ -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" +} diff --git a/clang/tools/clang-installapi/InstallAPIOpts.td b/clang/tools/clang-installapi/InstallAPIOpts.td index ab9e1fe7f2f94..71532c9cf24d1 100644 --- a/clang/tools/clang-installapi/InstallAPIOpts.td +++ b/clang/tools/clang-installapi/InstallAPIOpts.td @@ -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; +def public_umbrella_header : Separate<["-"], "public-umbrella-header">, + MetaVarName<"">, HelpText<"Specify the public umbrella header location">; +def public_umbrella_header_EQ : Joined<["--"], "public-umbrella-header=">, + Alias; +def private_umbrella_header : Separate<["-"], "private-umbrella-header">, + MetaVarName<"">, HelpText<"Specify the private umbrella header location">; +def private_umbrella_header_EQ : Joined<["--"], "private-umbrella-header=">, + Alias; +def project_umbrella_header : Separate<["-"], "project-umbrella-header">, + MetaVarName<"">, HelpText<"Specify the project umbrella header location">; +def project_umbrella_header_EQ : Joined<["--"], "project-umbrella-header=">, + Alias; diff --git a/clang/tools/clang-installapi/Options.cpp b/clang/tools/clang-installapi/Options.cpp index 4f79c62724a62..8e4a1b019fd81 100644 --- a/clang/tools/clang-installapi/Options.cpp +++ b/clang/tools/clang-installapi/Options.cpp @@ -270,6 +270,16 @@ Options::processAndFilterOutInstallAPIOptions(ArrayRef 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 ClangDriverArgs(ParsedArgs.size()); for (const Arg *A : ParsedArgs) { @@ -323,6 +333,15 @@ Options::Options(DiagnosticsEngine &Diag, FileManager *FM, } } +static const Regex Rule("(.+)/(.+)\\.framework/"); +static StringRef getFrameworkNameFromInstallName(StringRef InstallName) { + SmallVector Match; + Rule.match(InstallName, &Match); + if (Match.empty()) + return ""; + return Match.back(); +} + InstallAPIContext Options::createContext() { InstallAPIContext Ctx; Ctx.FM = FM; @@ -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); @@ -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 FullPath(Path); @@ -382,6 +405,7 @@ InstallAPIContext Options::createContext() { std::vector> ExcludedHeaderGlobs; std::set 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) @@ -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(); diff --git a/clang/tools/clang-installapi/Options.h b/clang/tools/clang-installapi/Options.h index c18309f693701..3671e4c8274bd 100644 --- a/clang/tools/clang-installapi/Options.h +++ b/clang/tools/clang-installapi/Options.h @@ -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;