diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1091,6 +1091,8 @@ def exported__symbols__list : Separate<["-"], "exported_symbols_list">; def extract_api : Flag<["-"], "extract-api">, Flags<[CC1Option]>, Group, HelpText<"Extract API information">; +def product_name_EQ: Joined<["--"], "product-name=">, Flags<[CC1Option]>, + MarshallingInfoString>; def e : JoinedOrSeparate<["-"], "e">, Flags<[LinkerInput]>, Group; def fmax_tokens_EQ : Joined<["-"], "fmax-tokens=">, Group, Flags<[CC1Option]>, HelpText<"Max total number of preprocessed tokens for -Wmax-tokens.">, diff --git a/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h b/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h --- a/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h +++ b/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h @@ -34,6 +34,12 @@ protected: const APISet &API; + + /// The product name of API. + /// + /// Note: This should be used for populating metadata about the API. + StringRef ProductName; + APISerializerOption Options; public: @@ -44,8 +50,9 @@ APISerializer &operator=(APISerializer &&) = delete; protected: - APISerializer(const APISet &API, APISerializerOption Options = {}) - : API(API), Options(Options) {} + APISerializer(const APISet &API, StringRef ProductName, + APISerializerOption Options = {}) + : API(API), ProductName(ProductName), Options(Options) {} virtual ~APISerializer() = default; }; diff --git a/clang/include/clang/Frontend/FrontendOptions.h b/clang/include/clang/Frontend/FrontendOptions.h --- a/clang/include/clang/Frontend/FrontendOptions.h +++ b/clang/include/clang/Frontend/FrontendOptions.h @@ -410,6 +410,10 @@ /// The name of the action to run when using a plugin action. std::string ActionName; + // Currently this is only used as part of the `-extract-api` action. + /// The name of the product the input files belong too. + std::string ProductName; + /// Args to pass to the plugins std::map> PluginArgs; diff --git a/clang/include/clang/SymbolGraph/Serialization.h b/clang/include/clang/SymbolGraph/Serialization.h new file mode 100644 --- /dev/null +++ b/clang/include/clang/SymbolGraph/Serialization.h @@ -0,0 +1,60 @@ +//===- SymbolGraph/Serialization.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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Defines the SymbolGraph serializer and parser. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_SYMBOLGRAPH_SERIALIZATION_H +#define LLVM_CLANG_SYMBOLGRAPH_SERIALIZATION_H + +#include "clang/SymbolGraph/API.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/VersionTuple.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { +namespace symbolgraph { + +using namespace llvm::json; + +struct SerializerOption { + bool Compact; +}; + +class Serializer { +public: + Serializer(const APISet &API, StringRef ProductName, + SerializerOption Options = {}) + : API(API), ProductName(ProductName), Options(Options) {} + + Object serialize(); + void serialize(raw_ostream &os); + +private: + Object serializeMetadata() const; + Object serializeModule() const; + Optional serializeAPIRecord(const APIRecord &Record) const; + void serializeGlobalRecord(const GlobalRecord &Record); + + bool shouldSkip(const APIRecord &Record) const; + + const APISet &API; + StringRef ProductName; + SerializerOption Options; + Array Symbols; + Array Relationships; + + static const VersionTuple FormatVersion; +}; + +} // namespace symbolgraph +} // namespace clang + +#endif // LLVM_CLANG_SYMBOLGRAPH_SERIALIZATION_H diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -4641,6 +4641,8 @@ assert(JA.getType() == types::TY_API_INFO && "Extract API actions must generate a API information."); CmdArgs.push_back("-extract-api"); + if (Arg *ProductNameArg = Args.getLastArg(options::OPT_product_name_EQ)) + ProductNameArg->render(Args, CmdArgs); } else { assert((isa(JA) || isa(JA)) && "Invalid action for clang tool."); diff --git a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp --- a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp +++ b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp @@ -183,8 +183,9 @@ class ExtractAPIConsumer : public ASTConsumer { public: - ExtractAPIConsumer(ASTContext &Context, std::unique_ptr OS) - : Visitor(Context), OS(std::move(OS)) {} + ExtractAPIConsumer(ASTContext &Context, StringRef ProductName, + std::unique_ptr OS) + : Visitor(Context), ProductName(ProductName), OS(std::move(OS)) {} void HandleTranslationUnit(ASTContext &Context) override { // Use ExtractAPIVisitor to traverse symbol declarations in the context. @@ -199,6 +200,7 @@ private: ExtractAPIVisitor Visitor; + std::string ProductName; std::unique_ptr OS; }; @@ -209,8 +211,9 @@ std::unique_ptr OS = CreateOutputFile(CI, InFile); if (!OS) return nullptr; - return std::make_unique(CI.getASTContext(), - std::move(OS)); + return std::make_unique( + CI.getASTContext(), CI.getInvocation().getFrontendOpts().ProductName, + std::move(OS)); } std::unique_ptr diff --git a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp --- a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp +++ b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp @@ -376,10 +376,9 @@ Object SymbolGraphSerializer::serializeModule() const { Object Module; - // FIXME: We might not be building a module, some Clang-based languages might - // not have a "module" concept. Figure out a way to provide a name to - // describe the API set. - Module["name"] = ""; + // The user is expected to always pass `--product-name=` on the command line + // to populate this field. + Module["name"] = ProductName; serializeObject(Module, "platform", serializePlatform(API.getTarget())); return Module; } diff --git a/clang/lib/SymbolGraph/Serialization.cpp b/clang/lib/SymbolGraph/Serialization.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/SymbolGraph/Serialization.cpp @@ -0,0 +1,331 @@ +//===- SymbolGraph/Serialization.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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Defines the SymbolGraph serializer and parser. +/// +//===----------------------------------------------------------------------===// + +#include "clang/SymbolGraph/Serialization.h" +#include "clang/Basic/Version.h" +#include "clang/SymbolGraph/API.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/VersionTuple.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; +using namespace clang::symbolgraph; +using namespace llvm; +using namespace llvm::json; + +namespace { + +static void serializeObject(Object &Paren, StringRef Key, + Optional Obj) { + if (Obj) + Paren[Key] = std::move(Obj.getValue()); +} + +static void serializeArray(Object &Paren, StringRef Key, + Optional Array) { + if (Array) + Paren[Key] = std::move(Array.getValue()); +} + +// SymbolGraph: SemanticVersion +static Optional serializeSemanticVersion(const VersionTuple &V) { + if (V.empty()) + return None; + + Object Version; + Version["major"] = V.getMajor(); + Version["minor"] = V.getMinor().getValueOr(0); + Version["patch"] = V.getSubminor().getValueOr(0); + return Version; +} + +static Object serializeOperatingSystem(const Triple &T) { + Object OS; + OS["name"] = T.getOSTypeName(T.getOS()); + serializeObject(OS, "minimumVersion", + serializeSemanticVersion(T.getMinimumSupportedOSVersion())); + return OS; +} + +// SymbolGraph: Platform +static Object serializePlatform(const Triple &T) { + Object Platform; + Platform["architecture"] = T.getArchName(); + Platform["vendor"] = T.getVendorName(); + Platform["operatingSystem"] = serializeOperatingSystem(T); + return Platform; +} + +// SymbolGraph: SourcePosition +static Object serializeSourcePosition(const PresumedLoc &Loc, + bool IncludeFileURI = false) { + assert(Loc.isValid() && "invalid source position"); + + Object SourcePosition; + SourcePosition["line"] = Loc.getLine(); + SourcePosition["character"] = Loc.getColumn(); + + if (IncludeFileURI) { + std::string FileURI = "file://"; + FileURI += sys::path::convert_to_slash(Loc.getFilename()); + SourcePosition["uri"] = FileURI; + } + + return SourcePosition; +} + +// SymbolGraph: SourceRange +static Object serializeSourceRange(const PresumedLoc &BeginLoc, + const PresumedLoc &EndLoc) { + Object SourceRange; + serializeObject(SourceRange, "start", serializeSourcePosition(BeginLoc)); + serializeObject(SourceRange, "end", serializeSourcePosition(EndLoc)); + return SourceRange; +} + +// SymbolGraph: AvailabilityItem +static Optional serializeAvailability(const AvailabilityInfo &Avail) { + if (Avail.isDefault()) + return None; + + Object Availbility; + serializeObject(Availbility, "introducedVersion", + serializeSemanticVersion(Avail.Introduced)); + serializeObject(Availbility, "deprecatedVersion", + serializeSemanticVersion(Avail.Deprecated)); + serializeObject(Availbility, "obsoletedVersion", + serializeSemanticVersion(Avail.Obsoleted)); + if (Avail.isUnavailable()) + Availbility["isUnconditionallyUnavailable"] = true; + if (Avail.isUnconditionallyDeprecated()) + Availbility["isUnconditionallyDeprecated"] = true; + + return Availbility; +} + +static StringRef getLanguageName(const LangOptions &LangOpts) { + auto Language = + LangStandard::getLangStandardForKind(LangOpts.LangStd).getLanguage(); + switch (Language) { + case Language::C: + return "c"; + case Language::ObjC: + return "objc"; + + // Unsupported language currently + case Language::CXX: + case Language::ObjCXX: + case Language::OpenCL: + case Language::OpenCLCXX: + case Language::CUDA: + case Language::RenderScript: + case Language::HIP: + + // Languages that the frontend cannot parse and compile + case Language::Unknown: + case Language::Asm: + case Language::LLVM_IR: + llvm_unreachable("Unsupported language kind"); + } + + llvm_unreachable("Unhandled language kind"); +} + +// SymbolGraph: Symbol::identifier +static Object serializeIdentifier(const APIRecord &Record, + const LangOptions &LangOpts) { + Object Identifier; + Identifier["precise"] = Record.USR; + Identifier["interfaceLanguage"] = getLanguageName(LangOpts); + + return Identifier; +} + +// SymbolGraph: DocComment +static Optional serializeDocComment(const DocComment &Comment) { + if (Comment.empty()) + return None; + + Object DocComment; + Array LinesArray; + for (const auto &CommentLine : Comment) { + Object Line; + Line["text"] = CommentLine.Text; + serializeObject(Line, "range", + serializeSourceRange(CommentLine.Begin, CommentLine.End)); + LinesArray.emplace_back(std::move(Line)); + } + serializeArray(DocComment, "lines", LinesArray); + + return DocComment; +} + +static Optional +serializeDeclarationFragments(const DeclarationFragments &DF) { + if (DF.getFragments().empty()) + return None; + + Array Fragments; + for (const auto &F : DF.getFragments()) { + Object Fragment; + Fragment["spelling"] = F.Spelling; + Fragment["kind"] = DeclarationFragments::getFragmentKindString(F.Kind); + if (!F.PreciseIdentifier.empty()) + Fragment["preciseIdentifier"] = F.PreciseIdentifier; + Fragments.emplace_back(std::move(Fragment)); + } + + return Fragments; +} + +static Optional +serializeFunctionSignature(const FunctionSignature &FS) { + if (FS.empty()) + return None; + + Object Signature; + serializeArray(Signature, "returns", + serializeDeclarationFragments(FS.getReturnType())); + + Array Parameters; + for (const auto &P : FS.getParameters()) { + Object Parameter; + Parameter["name"] = P.Name; + serializeArray(Parameter, "declarationFragments", + serializeDeclarationFragments(P.Fragments)); + Parameters.emplace_back(std::move(Parameter)); + } + + if (!Parameters.empty()) + Signature["parameters"] = std::move(Parameters); + + return Signature; +} + +static Object serializeNames(const APIRecord &Record) { + Object Names; + Names["title"] = Record.Name; + serializeArray(Names, "subHeading", + serializeDeclarationFragments(Record.SubHeading)); + + return Names; +} + +// SymbolGraph: Symbol::kind +static Object serializeSymbolKind(const APIRecord &Record, + const LangOptions &LangOpts) { + Object Kind; + switch (Record.getKind()) { + case APIRecord::RK_Global: + auto *GR = dyn_cast(&Record); + switch (GR->GlobalKind) { + case GVKind::Function: + Kind["identifier"] = (getLanguageName(LangOpts) + ".func").str(); + Kind["displayName"] = "Function"; + break; + case GVKind::Variable: + Kind["identifier"] = (getLanguageName(LangOpts) + ".var").str(); + Kind["displayName"] = "Global Variable"; + break; + case GVKind::Unknown: + // Unknown global kind + break; + } + break; + } + + return Kind; +} + +} // namespace + +const VersionTuple Serializer::FormatVersion{0, 5, 3}; + +Object Serializer::serializeMetadata() const { + Object Metadata; + serializeObject(Metadata, "formatVersion", + serializeSemanticVersion(FormatVersion)); + Metadata["generator"] = clang::getClangFullVersion(); + return Metadata; +} + +Object Serializer::serializeModule() const { + Object Module; + Module["name"] = ProductName; + serializeObject(Module, "platform", serializePlatform(API.getTarget())); + return Module; +} + +bool Serializer::shouldSkip(const APIRecord &Record) const { + // Skip unconditionally unavailable symbols + if (Record.Availability.isUnconditionallyUnavailable()) + return true; + + return false; +} + +Optional Serializer::serializeAPIRecord(const APIRecord &Record) const { + if (shouldSkip(Record)) + return None; + + Object Obj; + serializeObject(Obj, "identifier", + serializeIdentifier(Record, API.getLangOpts())); + serializeObject(Obj, "kind", serializeSymbolKind(Record, API.getLangOpts())); + serializeObject(Obj, "names", serializeNames(Record)); + serializeObject( + Obj, "location", + serializeSourcePosition(Record.Location, /*IncludeFileURI=*/true)); + serializeObject(Obj, "availbility", + serializeAvailability(Record.Availability)); + serializeObject(Obj, "docComment", serializeDocComment(Record.Comment)); + serializeArray(Obj, "declarationFragments", + serializeDeclarationFragments(Record.Declaration)); + + return Obj; +} + +void Serializer::serializeGlobalRecord(const GlobalRecord &Record) { + auto Obj = serializeAPIRecord(Record); + if (!Obj) + return; + + if (Record.GlobalKind == GVKind::Function) + serializeObject(*Obj, "parameters", + serializeFunctionSignature(Record.Signature)); + + Symbols.emplace_back(std::move(*Obj)); +} + +Object Serializer::serialize() { + Object Root; + serializeObject(Root, "metadata", serializeMetadata()); + serializeObject(Root, "module", serializeModule()); + + for (const auto &Global : API.getGlobals()) + serializeGlobalRecord(*Global.second); + + Root["symbols"] = std::move(Symbols); + Root["relationhips"] = std::move(Relationships); + + return Root; +} + +void Serializer::serialize(raw_ostream &os) { + Object root = serialize(); + if (Options.Compact) + os << formatv("{0}", Value(std::move(root))) << "\n"; + else + os << formatv("{0:2}", Value(std::move(root))) << "\n"; +} diff --git a/clang/test/ExtractAPI/global_record.c b/clang/test/ExtractAPI/global_record.c --- a/clang/test/ExtractAPI/global_record.c +++ b/clang/test/ExtractAPI/global_record.c @@ -2,7 +2,7 @@ // RUN: split-file %s %t // RUN: sed -e "s@INPUT_DIR@%/t@g" %t/reference.output.json.in >> \ // RUN: %t/reference.output.json -// RUN: %clang -extract-api -target arm64-apple-macosx \ +// RUN: %clang -extract-api --product-name=GlobalRecord -target arm64-apple-macosx \ // RUN: %t/input.h -o %t/output.json | FileCheck -allow-empty %s // Generator version is not consistent across test runs, normalize it. @@ -37,7 +37,7 @@ "generator": "?" }, "module": { - "name": "", + "name": "GlobalRecord", "platform": { "architecture": "arm64", "operatingSystem": {