diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index c3242ac2a804b..f74a589d1baaa 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1198,6 +1198,9 @@ def extract_api : Flag<["-"], "extract-api">, Flags<[CC1Option]>, Group; def product_name_EQ: Joined<["--"], "product-name=">, Flags<[CC1Option]>, MarshallingInfoString>; +def emit_symbol_graph_EQ: JoinedOrSeparate<["--"], "emit-symbol-graph=">, Flags<[CC1Option]>, + HelpText<"Generate Extract API information as a side effect of compilation.">, + MarshallingInfoString>; def extract_api_ignores_EQ: CommaJoined<["--"], "extract-api-ignores=">, Flags<[CC1Option]>, HelpText<"Comma separated list of files containing a new line separated list of API symbols to ignore when extracting API information.">, MarshallingInfoStringVector>; diff --git a/clang/include/clang/ExtractAPI/ExtractAPIActionBase.h b/clang/include/clang/ExtractAPI/ExtractAPIActionBase.h new file mode 100644 index 0000000000000..ac4f391db5f14 --- /dev/null +++ b/clang/include/clang/ExtractAPI/ExtractAPIActionBase.h @@ -0,0 +1,54 @@ +//===- ExtractAPI/ExtractAPIActionBase.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 +/// This file defines the ExtractAPIActionBase class. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_EXTRACTAPI_ACTION_BASE_H +#define LLVM_CLANG_EXTRACTAPI_ACTION_BASE_H + +#include "clang/ExtractAPI/API.h" +#include "clang/ExtractAPI/APIIgnoresList.h" + +namespace clang { + +/// Base class to be used by front end actions to generate ExtarctAPI info +/// +/// Deriving from this class equips an action with all the necessary tools to +/// generate ExractAPI information in form of symbol-graphs +class ExtractAPIActionBase { +protected: + /// A representation of the APIs this action extracts. + std::unique_ptr API; + + /// A stream to the output file of this action. + std::unique_ptr OS; + + /// The product this action is extracting API information for. + std::string ProductName; + + /// The synthesized input buffer that contains all the provided input header + /// files. + std::unique_ptr Buffer; + + /// The list of symbols to ignore during serialization + extractapi::APIIgnoresList IgnoresList; + + /// Implements EndSourceFileAction for Symbol-Graph generation + /// + /// Use the serializer to generate output symbol graph files from + /// the information gathered during the execution of Action. + void ImplEndSourceFileAction(); +}; + +} // namespace clang + +#endif // LLVM_CLANG_EXTRACTAPI_ACTION_BASE_H diff --git a/clang/include/clang/ExtractAPI/FrontendActions.h b/clang/include/clang/ExtractAPI/FrontendActions.h index e946b33abbd98..c67864aac9af9 100644 --- a/clang/include/clang/ExtractAPI/FrontendActions.h +++ b/clang/include/clang/ExtractAPI/FrontendActions.h @@ -7,41 +7,27 @@ //===----------------------------------------------------------------------===// /// /// \file -/// This file defines the ExtractAPIAction frontend action. +/// This file defines the ExtractAPIAction and WrappingExtractAPIAction frontend +/// actions. /// //===----------------------------------------------------------------------===// #ifndef LLVM_CLANG_EXTRACTAPI_FRONTEND_ACTIONS_H #define LLVM_CLANG_EXTRACTAPI_FRONTEND_ACTIONS_H -#include "clang/ExtractAPI/API.h" -#include "clang/ExtractAPI/APIIgnoresList.h" +#include "clang/ExtractAPI/ExtractAPIActionBase.h" #include "clang/Frontend/FrontendAction.h" namespace clang { /// ExtractAPIAction sets up the output file and creates the ExtractAPIVisitor. -class ExtractAPIAction : public ASTFrontendAction { +class ExtractAPIAction : public ASTFrontendAction, + private ExtractAPIActionBase { protected: std::unique_ptr CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override; private: - /// A representation of the APIs this action extracts. - std::unique_ptr API; - - /// A stream to the output file of this action. - std::unique_ptr OS; - - /// The product this action is extracting API information for. - std::string ProductName; - - /// The synthesized input buffer that contains all the provided input header - /// files. - std::unique_ptr Buffer; - - /// The list of symbols to ignore during serialization - extractapi::APIIgnoresList IgnoresList; /// The input file originally provided on the command line. /// @@ -62,10 +48,46 @@ class ExtractAPIAction : public ASTFrontendAction { /// emit them in this callback. void EndSourceFileAction() override; + static StringRef getInputBufferName() { return ""; } + static std::unique_ptr CreateOutputFile(CompilerInstance &CI, StringRef InFile); +}; - static StringRef getInputBufferName() { return ""; } +/// Wrap ExtractAPIAction on top of a pre-existing action +/// +/// Used when the ExtractAPI action needs to be executed as a side effect of a +/// regular compilation job. Unlike ExtarctAPIAction, this is meant to be used +/// on regular source files ( .m , .c files) instead of header files +class WrappingExtractAPIAction : public WrapperFrontendAction, + private ExtractAPIActionBase { +public: + WrappingExtractAPIAction(std::unique_ptr WrappedAction) + : WrapperFrontendAction(std::move(WrappedAction)) {} + +protected: + /// Create ExtractAPI consumer multiplexed on another consumer. + /// + /// This allows us to execute ExtractAPI action while on top of + std::unique_ptr CreateASTConsumer(CompilerInstance &CI, + StringRef InFile) override; + +private: + /// Flag to check if the wrapper front end action's consumer is + /// craeted or not + bool CreatedASTConsumer = false; + + void EndSourceFile() override { FrontendAction::EndSourceFile(); } + + /// Called after executing the action on the synthesized input buffer. + /// + /// Executes both Wrapper and ExtractAPIBase end source file + /// actions. This is the place where all the gathered symbol graph + /// information is emited. + void EndSourceFileAction() override; + + static std::unique_ptr + CreateOutputFile(CompilerInstance &CI, StringRef InFile); }; } // namespace clang diff --git a/clang/include/clang/Frontend/FrontendOptions.h b/clang/include/clang/Frontend/FrontendOptions.h index 40aa144a0e369..3132c11705d3d 100644 --- a/clang/include/clang/Frontend/FrontendOptions.h +++ b/clang/include/clang/Frontend/FrontendOptions.h @@ -456,6 +456,12 @@ class FrontendOptions { // ignore when extracting documentation. std::vector ExtractAPIIgnoresFileList; + // Currently this is only used as part of the `-emit-symbol-graph` + // action. + // Location of output directory where symbol graph information would + // be dumped + std::string SymbolGraphOutputDir; + /// Args to pass to the plugins std::map> PluginArgs; diff --git a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp index c27b2d025374a..eb533a934367f 100644 --- a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp +++ b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp @@ -28,12 +28,14 @@ #include "clang/Frontend/ASTConsumers.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendOptions.h" +#include "clang/Frontend/MultiplexConsumer.h" #include "clang/Lex/MacroInfo.h" #include "clang/Lex/PPCallbacks.h" #include "clang/Lex/Preprocessor.h" #include "clang/Lex/PreprocessorOptions.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Error.h" @@ -245,6 +247,20 @@ struct BatchExtractAPIVisitor : ExtractAPIVisitor { LocationFileChecker &LCF; }; +class WrappingExtractAPIConsumer : public ASTConsumer { +public: + WrappingExtractAPIConsumer(ASTContext &Context, APISet &API) + : Visitor(Context, API) {} + + void HandleTranslationUnit(ASTContext &Context) override { + // Use ExtractAPIVisitor to traverse symbol declarations in the context. + Visitor.TraverseDecl(Context.getTranslationUnitDecl()); + } + +private: + ExtractAPIVisitor<> Visitor; +}; + class ExtractAPIConsumer : public ASTConsumer { public: ExtractAPIConsumer(ASTContext &Context, @@ -263,9 +279,8 @@ class ExtractAPIConsumer : public ASTConsumer { class MacroCallback : public PPCallbacks { public: - MacroCallback(const SourceManager &SM, LocationFileChecker &LCF, APISet &API, - Preprocessor &PP) - : SM(SM), LCF(LCF), API(API), PP(PP) {} + MacroCallback(const SourceManager &SM, APISet &API, Preprocessor &PP) + : SM(SM), API(API), PP(PP) {} void MacroDefined(const Token &MacroNameToken, const MacroDirective *MD) override { @@ -305,7 +320,7 @@ class MacroCallback : public PPCallbacks { if (PM.MD->getMacroInfo()->isUsedForHeaderGuard()) continue; - if (!LCF(PM.MacroNameToken.getLocation())) + if (!shouldMacroBeIncluded(PM)) continue; StringRef Name = PM.MacroNameToken.getIdentifierInfo()->getName(); @@ -323,7 +338,7 @@ class MacroCallback : public PPCallbacks { PendingMacros.clear(); } -private: +protected: struct PendingMacro { Token MacroNameToken; const MacroDirective *MD; @@ -332,18 +347,58 @@ class MacroCallback : public PPCallbacks { : MacroNameToken(MacroNameToken), MD(MD) {} }; + virtual bool shouldMacroBeIncluded(const PendingMacro &PM) { return true; } + const SourceManager &SM; - LocationFileChecker &LCF; APISet &API; Preprocessor &PP; llvm::SmallVector PendingMacros; }; +class APIMacroCallback : public MacroCallback { +public: + APIMacroCallback(const SourceManager &SM, APISet &API, Preprocessor &PP, + LocationFileChecker &LCF) + : MacroCallback(SM, API, PP), LCF(LCF) {} + + bool shouldMacroBeIncluded(const PendingMacro &PM) override { + // Do not include macros from external files + return LCF(PM.MacroNameToken.getLocation()); + } + +private: + LocationFileChecker &LCF; +}; + } // namespace +void ExtractAPIActionBase::ImplEndSourceFileAction() { + if (!OS) + return; + + // Setup a SymbolGraphSerializer to write out collected API information in + // the Symbol Graph format. + // FIXME: Make the kind of APISerializer configurable. + SymbolGraphSerializer SGSerializer(*API, IgnoresList); + SGSerializer.serialize(*OS); + OS.reset(); +} + +std::unique_ptr +ExtractAPIAction::CreateOutputFile(CompilerInstance &CI, StringRef InFile) { + std::unique_ptr OS; + OS = CI.createDefaultOutputFile(/*Binary=*/false, InFile, + /*Extension=*/"json", + /*RemoveFileOnSignal=*/false); + if (!OS) + return nullptr; + return OS; +} + std::unique_ptr ExtractAPIAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) { OS = CreateOutputFile(CI, InFile); + if (!OS) return nullptr; @@ -357,8 +412,8 @@ ExtractAPIAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) { auto LCF = std::make_unique(CI, KnownInputFiles); - CI.getPreprocessor().addPPCallbacks(std::make_unique( - CI.getSourceManager(), *LCF, *API, CI.getPreprocessor())); + CI.getPreprocessor().addPPCallbacks(std::make_unique( + CI.getSourceManager(), *API, CI.getPreprocessor(), *LCF)); // Do not include location in anonymous decls. PrintingPolicy Policy = CI.getASTContext().getPrintingPolicy(); @@ -438,23 +493,88 @@ bool ExtractAPIAction::PrepareToExecuteAction(CompilerInstance &CI) { return true; } -void ExtractAPIAction::EndSourceFileAction() { +void ExtractAPIAction::EndSourceFileAction() { ImplEndSourceFileAction(); } + +std::unique_ptr +WrappingExtractAPIAction::CreateASTConsumer(CompilerInstance &CI, + StringRef InFile) { + auto OtherConsumer = WrapperFrontendAction::CreateASTConsumer(CI, InFile); + if (!OtherConsumer) + return nullptr; + + CreatedASTConsumer = true; + + OS = CreateOutputFile(CI, InFile); if (!OS) - return; + return nullptr; - // Setup a SymbolGraphSerializer to write out collected API information in - // the Symbol Graph format. - // FIXME: Make the kind of APISerializer configurable. - SymbolGraphSerializer SGSerializer(*API, IgnoresList); - SGSerializer.serialize(*OS); - OS.reset(); + auto ProductName = CI.getFrontendOpts().ProductName; + + // Now that we have enough information about the language options and the + // target triple, let's create the APISet before anyone uses it. + API = std::make_unique( + CI.getTarget().getTriple(), + CI.getFrontendOpts().Inputs.back().getKind().getLanguage(), ProductName); + + CI.getPreprocessor().addPPCallbacks(std::make_unique( + CI.getSourceManager(), *API, CI.getPreprocessor())); + + // Do not include location in anonymous decls. + PrintingPolicy Policy = CI.getASTContext().getPrintingPolicy(); + Policy.AnonymousTagLocations = false; + CI.getASTContext().setPrintingPolicy(Policy); + + if (!CI.getFrontendOpts().ExtractAPIIgnoresFileList.empty()) { + llvm::handleAllErrors( + APIIgnoresList::create(CI.getFrontendOpts().ExtractAPIIgnoresFileList, + CI.getFileManager()) + .moveInto(IgnoresList), + [&CI](const IgnoresFileNotFound &Err) { + CI.getDiagnostics().Report( + diag::err_extract_api_ignores_file_not_found) + << Err.Path; + }); + } + + auto WrappingConsumer = + std::make_unique(CI.getASTContext(), *API); + std::vector> Consumers; + Consumers.push_back(std::move(OtherConsumer)); + Consumers.push_back(std::move(WrappingConsumer)); + + return std::make_unique(std::move(Consumers)); +} + +void WrappingExtractAPIAction::EndSourceFileAction() { + // Invoke wrapped action's method. + WrapperFrontendAction::EndSourceFileAction(); + + if (CreatedASTConsumer) { + ImplEndSourceFileAction(); + } } std::unique_ptr -ExtractAPIAction::CreateOutputFile(CompilerInstance &CI, StringRef InFile) { - std::unique_ptr OS = - CI.createDefaultOutputFile(/*Binary=*/false, InFile, /*Extension=*/"json", - /*RemoveFileOnSignal=*/false); +WrappingExtractAPIAction::CreateOutputFile(CompilerInstance &CI, + StringRef InFile) { + std::unique_ptr OS; + std::string OutputDir = CI.getFrontendOpts().SymbolGraphOutputDir; + + // The symbol graphs need to be generated as a side effect of regular + // compilation so the output should be dumped in the directory provided with + // the command line option. + llvm::SmallString<128> OutFilePath(OutputDir); + auto Seperator = llvm::sys::path::get_separator(); + auto Infilename = llvm::sys::path::filename(InFile); + OutFilePath.append({Seperator, Infilename}); + llvm::sys::path::replace_extension(OutFilePath, "json"); + // StringRef outputFilePathref = *OutFilePath; + + // don't use the default output file + OS = CI.createOutputFile(/*OutputPath=*/OutFilePath, /*Binary=*/false, + /*RemoveFileOnSignal=*/true, + /*UseTemporary=*/true, + /*CreateMissingDirectories=*/true); if (!OS) return nullptr; return OS; diff --git a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp index 47157ca5092b7..310f67774a660 100644 --- a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -178,6 +178,14 @@ CreateFrontendAction(CompilerInstance &CI) { } #endif + // Wrap the base FE action in an extract api action to generate + // symbol graph as a biproduct of comilation ( enabled with + // --emit-symbol-graph option ) + if (!FEOpts.SymbolGraphOutputDir.empty()) { + CI.getCodeGenOpts().ClearASTBeforeBackend = false; + Act = std::make_unique(std::move(Act)); + } + // If there are any AST files to merge, create a frontend action // adaptor to perform the merge. if (!FEOpts.ASTMergeFiles.empty()) diff --git a/clang/test/ExtractAPI/emit-symbol-graph/multi_file.c b/clang/test/ExtractAPI/emit-symbol-graph/multi_file.c new file mode 100644 index 0000000000000..1b44cfbdeb75a --- /dev/null +++ b/clang/test/ExtractAPI/emit-symbol-graph/multi_file.c @@ -0,0 +1,763 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: split-file %s %t +// RUN: sed -e "s@INPUT_DIR@%{/t:regex_replacement}@g" \ +// RUN: %t/reference.main.json.in >> %t/reference.main.json +// RUN: sed -e "s@INPUT_DIR@%{/t:regex_replacement}@g" \ +// RUN: %t/reference.test.json.in >> %t/reference.test.json +// RUN: %clang_cc1 %t/test.c %t/main.c --emit-symbol-graph=%t/SymbolGraphs --product-name=multifile_test -triple=x86_64-apple-macosx12.0.0 + +// Test main.json +// Generator version is not consistent across test runs, normalize it. +// RUN: sed -e "s@\"generator\": \".*\"@\"generator\": \"?\"@g" \ +// RUN: %t/SymbolGraphs/main.json > %t/output-normalized.json +// RUN: diff %t/reference.main.json %t/output-normalized.json + +// Test test.json +// Generator version is not consistent across test runs, normalize it. +// RUN: sed -e "s@\"generator\": \".*\"@\"generator\": \"?\"@g" \ +// RUN: %t/SymbolGraphs/test.json > %t/output-normalized.json +// RUN: diff %t/reference.test.json %t/output-normalized.json + +// CHECK-NOT: error: +// CHECK-NOT: warning: + +//--- test.h +#ifndef TEST_H +#define TEST_H + +#define testmarcro1 32 +#define testmacro2 42 + +int testfunc (int param1, int param2); +void testfunc2 (); +#endif /* TEST_H */ + +//--- test.c +#include "test.h" + +int testfunc(int param1, int param2) { return param1 + param2; } + +void testfunc2() {} + +//--- main.c +#include "test.h" + +int main () +{ + testfunc2(); + return 0; +} + +//--- reference.main.json.in +{ + "metadata": { + "formatVersion": { + "major": 0, + "minor": 5, + "patch": 3 + }, + "generator": "?" + }, + "module": { + "name": "multifile_test", + "platform": { + "architecture": "x86_64", + "operatingSystem": { + "name": "macosx" + }, + "vendor": "apple" + } + }, + "relationships": [], + "symbols": [ + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "testfunc" + }, + { + "kind": "text", + "spelling": "(" + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "internalParam", + "spelling": "param1" + }, + { + "kind": "text", + "spelling": ", " + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "internalParam", + "spelling": "param2" + }, + { + "kind": "text", + "spelling": ");" + } + ], + "functionSignature": { + "parameters": [ + { + "declarationFragments": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "internalParam", + "spelling": "param1" + } + ], + "name": "param1" + }, + { + "declarationFragments": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "internalParam", + "spelling": "param2" + } + ], + "name": "param2" + } + ], + "returns": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + } + ] + }, + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@F@testfunc" + }, + "kind": { + "displayName": "Function", + "identifier": "c.func" + }, + "location": { + "position": { + "character": 5, + "line": 7 + }, + "uri": "file://INPUT_DIR/test.h" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "testfunc" + } + ], + "subHeading": [ + { + "kind": "identifier", + "spelling": "testfunc" + } + ], + "title": "testfunc" + }, + "pathComponents": [ + "testfunc" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:v", + "spelling": "void" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "testfunc2" + }, + { + "kind": "text", + "spelling": "();" + } + ], + "functionSignature": { + "returns": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:v", + "spelling": "void" + } + ] + }, + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@F@testfunc2" + }, + "kind": { + "displayName": "Function", + "identifier": "c.func" + }, + "location": { + "position": { + "character": 6, + "line": 8 + }, + "uri": "file://INPUT_DIR/test.h" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "testfunc2" + } + ], + "subHeading": [ + { + "kind": "identifier", + "spelling": "testfunc2" + } + ], + "title": "testfunc2" + }, + "pathComponents": [ + "testfunc2" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "main" + }, + { + "kind": "text", + "spelling": "();" + } + ], + "functionSignature": { + "returns": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + } + ] + }, + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@F@main" + }, + "kind": { + "displayName": "Function", + "identifier": "c.func" + }, + "location": { + "position": { + "character": 5, + "line": 3 + }, + "uri": "file://INPUT_DIR/main.c" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "main" + } + ], + "subHeading": [ + { + "kind": "identifier", + "spelling": "main" + } + ], + "title": "main" + }, + "pathComponents": [ + "main" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "#define" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "testmarcro1" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:test.h@39@macro@testmarcro1" + }, + "kind": { + "displayName": "Macro", + "identifier": "c.macro" + }, + "location": { + "position": { + "character": 9, + "line": 4 + }, + "uri": "file://INPUT_DIR/test.h" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "testmarcro1" + } + ], + "subHeading": [ + { + "kind": "identifier", + "spelling": "testmarcro1" + } + ], + "title": "testmarcro1" + }, + "pathComponents": [ + "testmarcro1" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "#define" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "testmacro2" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:test.h@62@macro@testmacro2" + }, + "kind": { + "displayName": "Macro", + "identifier": "c.macro" + }, + "location": { + "position": { + "character": 9, + "line": 5 + }, + "uri": "file://INPUT_DIR/test.h" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "testmacro2" + } + ], + "subHeading": [ + { + "kind": "identifier", + "spelling": "testmacro2" + } + ], + "title": "testmacro2" + }, + "pathComponents": [ + "testmacro2" + ] + } + ] +} +//--- reference.test.json.in +{ + "metadata": { + "formatVersion": { + "major": 0, + "minor": 5, + "patch": 3 + }, + "generator": "?" + }, + "module": { + "name": "multifile_test", + "platform": { + "architecture": "x86_64", + "operatingSystem": { + "name": "macosx" + }, + "vendor": "apple" + } + }, + "relationships": [], + "symbols": [ + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "testfunc" + }, + { + "kind": "text", + "spelling": "(" + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "internalParam", + "spelling": "param1" + }, + { + "kind": "text", + "spelling": ", " + }, + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "internalParam", + "spelling": "param2" + }, + { + "kind": "text", + "spelling": ");" + } + ], + "functionSignature": { + "parameters": [ + { + "declarationFragments": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "internalParam", + "spelling": "param1" + } + ], + "name": "param1" + }, + { + "declarationFragments": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "internalParam", + "spelling": "param2" + } + ], + "name": "param2" + } + ], + "returns": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + } + ] + }, + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@F@testfunc" + }, + "kind": { + "displayName": "Function", + "identifier": "c.func" + }, + "location": { + "position": { + "character": 5, + "line": 7 + }, + "uri": "file://INPUT_DIR/test.h" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "testfunc" + } + ], + "subHeading": [ + { + "kind": "identifier", + "spelling": "testfunc" + } + ], + "title": "testfunc" + }, + "pathComponents": [ + "testfunc" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:v", + "spelling": "void" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "testfunc2" + }, + { + "kind": "text", + "spelling": "();" + } + ], + "functionSignature": { + "returns": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:v", + "spelling": "void" + } + ] + }, + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@F@testfunc2" + }, + "kind": { + "displayName": "Function", + "identifier": "c.func" + }, + "location": { + "position": { + "character": 6, + "line": 8 + }, + "uri": "file://INPUT_DIR/test.h" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "testfunc2" + } + ], + "subHeading": [ + { + "kind": "identifier", + "spelling": "testfunc2" + } + ], + "title": "testfunc2" + }, + "pathComponents": [ + "testfunc2" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "#define" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "testmarcro1" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:test.h@39@macro@testmarcro1" + }, + "kind": { + "displayName": "Macro", + "identifier": "c.macro" + }, + "location": { + "position": { + "character": 9, + "line": 4 + }, + "uri": "file://INPUT_DIR/test.h" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "testmarcro1" + } + ], + "subHeading": [ + { + "kind": "identifier", + "spelling": "testmarcro1" + } + ], + "title": "testmarcro1" + }, + "pathComponents": [ + "testmarcro1" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "#define" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "testmacro2" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:test.h@62@macro@testmacro2" + }, + "kind": { + "displayName": "Macro", + "identifier": "c.macro" + }, + "location": { + "position": { + "character": 9, + "line": 5 + }, + "uri": "file://INPUT_DIR/test.h" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "testmacro2" + } + ], + "subHeading": [ + { + "kind": "identifier", + "spelling": "testmacro2" + } + ], + "title": "testmacro2" + }, + "pathComponents": [ + "testmacro2" + ] + } + ] +} diff --git a/clang/test/ExtractAPI/emit-symbol-graph/single_file.c b/clang/test/ExtractAPI/emit-symbol-graph/single_file.c new file mode 100644 index 0000000000000..aa2a5353ae980 --- /dev/null +++ b/clang/test/ExtractAPI/emit-symbol-graph/single_file.c @@ -0,0 +1,213 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: split-file %s %t +// RUN: sed -e "s@INPUT_DIR@%{/t:regex_replacement}@g" \ +// RUN: %t/reference.output.json.in >> %t/reference.output.json +// RUN: %clang_cc1 %t/main.c --emit-symbol-graph=%t/SymbolGraphs --product-name=basicfile -triple=x86_64-apple-macosx12.0.0 + +// Generator version is not consistent across test runs, normalize it. +// RUN: sed -e "s@\"generator\": \".*\"@\"generator\": \"?\"@g" \ +// RUN: %t/SymbolGraphs/main.json >> %t/output-normalized.json +// RUN: diff %t/reference.output.json %t/output-normalized.json + +// CHECK-NOT: error: +// CHECK-NOT: warning: + +//--- main.c +#define TESTMACRO1 2 +#define TESTMARCRO2 5 + +int main () +{ + return 0; +} + + +//--- reference.output.json.in +{ + "metadata": { + "formatVersion": { + "major": 0, + "minor": 5, + "patch": 3 + }, + "generator": "?" + }, + "module": { + "name": "basicfile", + "platform": { + "architecture": "x86_64", + "operatingSystem": { + "name": "macosx" + }, + "vendor": "apple" + } + }, + "relationships": [], + "symbols": [ + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "main" + }, + { + "kind": "text", + "spelling": "();" + } + ], + "functionSignature": { + "returns": [ + { + "kind": "typeIdentifier", + "preciseIdentifier": "c:I", + "spelling": "int" + } + ] + }, + "identifier": { + "interfaceLanguage": "c", + "precise": "c:@F@main" + }, + "kind": { + "displayName": "Function", + "identifier": "c.func" + }, + "location": { + "position": { + "character": 5, + "line": 4 + }, + "uri": "file://INPUT_DIR/main.c" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "main" + } + ], + "subHeading": [ + { + "kind": "identifier", + "spelling": "main" + } + ], + "title": "main" + }, + "pathComponents": [ + "main" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "#define" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "TESTMACRO1" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:main.c@8@macro@TESTMACRO1" + }, + "kind": { + "displayName": "Macro", + "identifier": "c.macro" + }, + "location": { + "position": { + "character": 9, + "line": 1 + }, + "uri": "file://INPUT_DIR/main.c" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "TESTMACRO1" + } + ], + "subHeading": [ + { + "kind": "identifier", + "spelling": "TESTMACRO1" + } + ], + "title": "TESTMACRO1" + }, + "pathComponents": [ + "TESTMACRO1" + ] + }, + { + "accessLevel": "public", + "declarationFragments": [ + { + "kind": "keyword", + "spelling": "#define" + }, + { + "kind": "text", + "spelling": " " + }, + { + "kind": "identifier", + "spelling": "TESTMARCRO2" + } + ], + "identifier": { + "interfaceLanguage": "c", + "precise": "c:main.c@29@macro@TESTMARCRO2" + }, + "kind": { + "displayName": "Macro", + "identifier": "c.macro" + }, + "location": { + "position": { + "character": 9, + "line": 2 + }, + "uri": "file://INPUT_DIR/main.c" + }, + "names": { + "navigator": [ + { + "kind": "identifier", + "spelling": "TESTMARCRO2" + } + ], + "subHeading": [ + { + "kind": "identifier", + "spelling": "TESTMARCRO2" + } + ], + "title": "TESTMARCRO2" + }, + "pathComponents": [ + "TESTMARCRO2" + ] + } + ] +}