1//===- DependencyScanningTool.cpp - clang-scan-deps service ---------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "clang/Tooling/DependencyScanning/DependencyScanningTool.h"
10#include "clang/Frontend/Utils.h"
11#include "llvm/Support/JSON.h"
12
13static llvm::json::Array toJSONSorted(const llvm::StringSet<> &Set) {
14  std::vector<llvm::StringRef> Strings;
15  for (auto &&I : Set)
16    Strings.push_back(I.getKey());
17  std::sort(Strings.begin(), Strings.end());
18  return llvm::json::Array(Strings);
19}
20
21namespace clang{
22namespace tooling{
23namespace dependencies{
24
25DependencyScanningTool::DependencyScanningTool(
26    DependencyScanningService &Service)
27    : Format(Service.getFormat()), Worker(Service) {
28}
29
30llvm::Expected<std::string> DependencyScanningTool::getDependencyFile(
31    const tooling::CompilationDatabase &Compilations, StringRef CWD) {
32  /// Prints out all of the gathered dependencies into a string.
33  class MakeDependencyPrinterConsumer : public DependencyConsumer {
34  public:
35    void handleFileDependency(const DependencyOutputOptions &Opts,
36                              StringRef File) override {
37      if (!this->Opts)
38        this->Opts = std::make_unique<DependencyOutputOptions>(Opts);
39      Dependencies.push_back(File);
40    }
41
42    void handleModuleDependency(ModuleDeps MD) override {
43      // These are ignored for the make format as it can't support the full
44      // set of deps, and handleFileDependency handles enough for implicitly
45      // built modules to work.
46    }
47
48    void handleContextHash(std::string Hash) override {}
49
50    void printDependencies(std::string &S) {
51      if (!Opts)
52        return;
53
54      class DependencyPrinter : public DependencyFileGenerator {
55      public:
56        DependencyPrinter(DependencyOutputOptions &Opts,
57                          ArrayRef<std::string> Dependencies)
58            : DependencyFileGenerator(Opts) {
59          for (const auto &Dep : Dependencies)
60            addDependency(Dep);
61        }
62
63        void printDependencies(std::string &S) {
64          llvm::raw_string_ostream OS(S);
65          outputDependencyFile(OS);
66        }
67      };
68
69      DependencyPrinter Generator(*Opts, Dependencies);
70      Generator.printDependencies(S);
71    }
72
73  private:
74    std::unique_ptr<DependencyOutputOptions> Opts;
75    std::vector<std::string> Dependencies;
76  };
77
78  class FullDependencyPrinterConsumer : public DependencyConsumer {
79  public:
80    void handleFileDependency(const DependencyOutputOptions &Opts,
81                              StringRef File) override {
82      Dependencies.push_back(File);
83    }
84
85    void handleModuleDependency(ModuleDeps MD) override {
86      ClangModuleDeps[MD.ContextHash + MD.ModuleName] = std::move(MD);
87    }
88
89    void handleContextHash(std::string Hash) override {
90      ContextHash = std::move(Hash);
91    }
92
93    void printDependencies(std::string &S, StringRef MainFile) {
94      // Sort the modules by name to get a deterministic order.
95      std::vector<StringRef> Modules;
96      for (auto &&Dep : ClangModuleDeps)
97        Modules.push_back(Dep.first);
98      std::sort(Modules.begin(), Modules.end());
99
100      llvm::raw_string_ostream OS(S);
101
102      using namespace llvm::json;
103
104      Array Imports;
105      for (auto &&ModName : Modules) {
106        auto &MD = ClangModuleDeps[ModName];
107        if (MD.ImportedByMainFile)
108          Imports.push_back(MD.ModuleName);
109      }
110
111      Array Mods;
112      for (auto &&ModName : Modules) {
113        auto &MD = ClangModuleDeps[ModName];
114        Object Mod{
115            {"name", MD.ModuleName},
116            {"file-deps", toJSONSorted(MD.FileDeps)},
117            {"clang-module-deps", toJSONSorted(MD.ClangModuleDeps)},
118            {"clang-modulemap-file", MD.ClangModuleMapFile},
119        };
120        Mods.push_back(std::move(Mod));
121      }
122
123      Object O{
124          {"input-file", MainFile},
125          {"clang-context-hash", ContextHash},
126          {"file-deps", Dependencies},
127          {"clang-module-deps", std::move(Imports)},
128          {"clang-modules", std::move(Mods)},
129      };
130
131      S = llvm::formatv("{0:2},\n", Value(std::move(O))).str();
132      return;
133    }
134
135  private:
136    std::vector<std::string> Dependencies;
137    std::unordered_map<std::string, ModuleDeps> ClangModuleDeps;
138    std::string ContextHash;
139  };
140
141
142  // We expect a single command here because if a source file occurs multiple
143  // times in the original CDB, then `computeDependencies` would run the
144  // `DependencyScanningAction` once for every time the input occured in the
145  // CDB. Instead we split up the CDB into single command chunks to avoid this
146  // behavior.
147  assert(Compilations.getAllCompileCommands().size() == 1 &&
148         "Expected a compilation database with a single command!");
149  std::string Input = Compilations.getAllCompileCommands().front().Filename;
150
151  if (Format == ScanningOutputFormat::Make) {
152    MakeDependencyPrinterConsumer Consumer;
153    auto Result =
154        Worker.computeDependencies(Input, CWD, Compilations, Consumer);
155    if (Result)
156      return std::move(Result);
157    std::string Output;
158    Consumer.printDependencies(Output);
159    return Output;
160  } else {
161    FullDependencyPrinterConsumer Consumer;
162    auto Result =
163        Worker.computeDependencies(Input, CWD, Compilations, Consumer);
164    if (Result)
165      return std::move(Result);
166    std::string Output;
167    Consumer.printDependencies(Output, Input);
168    return Output;
169  }
170}
171
172} // end namespace dependencies
173} // end namespace tooling
174} // end namespace clang
175