[clang] 8e9145e - [clang][ExtractAPI] Add --emit-symbol-graph option

via cfe-commits cfe-commits at lists.llvm.org
Mon Jul 3 05:07:45 PDT 2023


Author: Ankur
Date: 2023-07-03T17:32:30+05:30
New Revision: 8e9145e4314202b960dd883e6a7b21707ed5c176

URL: https://github.com/llvm/llvm-project/commit/8e9145e4314202b960dd883e6a7b21707ed5c176
DIFF: https://github.com/llvm/llvm-project/commit/8e9145e4314202b960dd883e6a7b21707ed5c176.diff

LOG: [clang][ExtractAPI] Add --emit-symbol-graph option

Add new --emit-symbol-graph=<DIR> option which generates ExtractAPI symbol
graph information of .c/.m files on regular compilation job and put them in
the provided "DIR" directory.

Reviewed By: dang

Differential Revision: https://reviews.llvm.org/D152356

Added: 
    clang/include/clang/ExtractAPI/ExtractAPIActionBase.h
    clang/test/ExtractAPI/emit-symbol-graph/multi_file.c
    clang/test/ExtractAPI/emit-symbol-graph/single_file.c

Modified: 
    clang/include/clang/Driver/Options.td
    clang/include/clang/ExtractAPI/FrontendActions.h
    clang/include/clang/Frontend/FrontendOptions.h
    clang/lib/ExtractAPI/ExtractAPIConsumer.cpp
    clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index c3242ac2a804b8..f74a589d1baaa6 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<Action_G
   HelpText<"Extract API information">;
 def product_name_EQ: Joined<["--"], "product-name=">, Flags<[CC1Option]>,
   MarshallingInfoString<FrontendOpts<"ProductName">>;
+def emit_symbol_graph_EQ: JoinedOrSeparate<["--"], "emit-symbol-graph=">, Flags<[CC1Option]>,
+    HelpText<"Generate Extract API information as a side effect of compilation.">,
+    MarshallingInfoString<FrontendOpts<"SymbolGraphOutputDir">>;
 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<FrontendOpts<"ExtractAPIIgnoresFileList">>;

diff  --git a/clang/include/clang/ExtractAPI/ExtractAPIActionBase.h b/clang/include/clang/ExtractAPI/ExtractAPIActionBase.h
new file mode 100644
index 00000000000000..ac4f391db5f14a
--- /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<extractapi::APISet> API;
+
+  /// A stream to the output file of this action.
+  std::unique_ptr<raw_pwrite_stream> 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<llvm::MemoryBuffer> 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 e946b33abbd984..c67864aac9af9c 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<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
                                                  StringRef InFile) override;
 
 private:
-  /// A representation of the APIs this action extracts.
-  std::unique_ptr<extractapi::APISet> API;
-
-  /// A stream to the output file of this action.
-  std::unique_ptr<raw_pwrite_stream> 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<llvm::MemoryBuffer> 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 "<extract-api-includes>"; }
+
   static std::unique_ptr<llvm::raw_pwrite_stream>
   CreateOutputFile(CompilerInstance &CI, StringRef InFile);
+};
 
-  static StringRef getInputBufferName() { return "<extract-api-includes>"; }
+/// 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<FrontendAction> 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<ASTConsumer> 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<llvm::raw_pwrite_stream>
+  CreateOutputFile(CompilerInstance &CI, StringRef InFile);
 };
 
 } // namespace clang

diff  --git a/clang/include/clang/Frontend/FrontendOptions.h b/clang/include/clang/Frontend/FrontendOptions.h
index 40aa144a0e3698..3132c11705d3d8 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<std::string> 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<std::string, std::vector<std::string>> PluginArgs;
 

diff  --git a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp
index c27b2d025374a3..eb533a934367f4 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<BatchExtractAPIVisitor> {
   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<PendingMacro> 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<raw_pwrite_stream>
+ExtractAPIAction::CreateOutputFile(CompilerInstance &CI, StringRef InFile) {
+  std::unique_ptr<raw_pwrite_stream> OS;
+  OS = CI.createDefaultOutputFile(/*Binary=*/false, InFile,
+                                  /*Extension=*/"json",
+                                  /*RemoveFileOnSignal=*/false);
+  if (!OS)
+    return nullptr;
+  return OS;
+}
+
 std::unique_ptr<ASTConsumer>
 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<LocationFileChecker>(CI, KnownInputFiles);
 
-  CI.getPreprocessor().addPPCallbacks(std::make_unique<MacroCallback>(
-      CI.getSourceManager(), *LCF, *API, CI.getPreprocessor()));
+  CI.getPreprocessor().addPPCallbacks(std::make_unique<APIMacroCallback>(
+      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<ASTConsumer>
+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<APISet>(
+      CI.getTarget().getTriple(),
+      CI.getFrontendOpts().Inputs.back().getKind().getLanguage(), ProductName);
+
+  CI.getPreprocessor().addPPCallbacks(std::make_unique<MacroCallback>(
+      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<WrappingExtractAPIConsumer>(CI.getASTContext(), *API);
+  std::vector<std::unique_ptr<ASTConsumer>> Consumers;
+  Consumers.push_back(std::move(OtherConsumer));
+  Consumers.push_back(std::move(WrappingConsumer));
+
+  return std::make_unique<MultiplexConsumer>(std::move(Consumers));
+}
+
+void WrappingExtractAPIAction::EndSourceFileAction() {
+  // Invoke wrapped action's method.
+  WrapperFrontendAction::EndSourceFileAction();
+
+  if (CreatedASTConsumer) {
+    ImplEndSourceFileAction();
+  }
 }
 
 std::unique_ptr<raw_pwrite_stream>
-ExtractAPIAction::CreateOutputFile(CompilerInstance &CI, StringRef InFile) {
-  std::unique_ptr<raw_pwrite_stream> OS =
-      CI.createDefaultOutputFile(/*Binary=*/false, InFile, /*Extension=*/"json",
-                                 /*RemoveFileOnSignal=*/false);
+WrappingExtractAPIAction::CreateOutputFile(CompilerInstance &CI,
+                                           StringRef InFile) {
+  std::unique_ptr<raw_pwrite_stream> 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 47157ca5092b75..310f67774a6605 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<WrappingExtractAPIAction>(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 00000000000000..1b44cfbdeb75a9
--- /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 at INPUT_DIR@%{/t:regex_replacement}@g" \
+// RUN: %t/reference.main.json.in >> %t/reference.main.json
+// RUN: sed -e "s at 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 at 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 at 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 at 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 at 39@macro at 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 at 62@macro at 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 at 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 at 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 at 39@macro at 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 at 62@macro at 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 00000000000000..aa2a5353ae980b
--- /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 at 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 at 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 at 8@macro at 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 at 29@macro at 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"
+      ]
+    }
+  ]
+}


        


More information about the cfe-commits mailing list