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
12namespace clang{
13namespace tooling{
14namespace dependencies{
15
16std::vector<std::string> FullDependencies::getAdditionalCommandLine(
17    std::function<StringRef(ClangModuleDep)> LookupPCMPath,
18    std::function<const ModuleDeps &(ClangModuleDep)> LookupModuleDeps) const {
19  std::vector<std::string> Ret = AdditionalNonPathCommandLine;
20
21  dependencies::detail::appendCommonModuleArguments(
22      ClangModuleDeps, LookupPCMPath, LookupModuleDeps, Ret);
23
24  return Ret;
25}
26
27DependencyScanningTool::DependencyScanningTool(
28    DependencyScanningService &Service)
29    : Worker(Service) {}
30
31llvm::Expected<std::string> DependencyScanningTool::getDependencyFile(
32    const tooling::CompilationDatabase &Compilations, StringRef CWD) {
33  /// Prints out all of the gathered dependencies into a string.
34  class MakeDependencyPrinterConsumer : public DependencyConsumer {
35  public:
36    void handleFileDependency(const DependencyOutputOptions &Opts,
37                              StringRef File) override {
38      if (!this->Opts)
39        this->Opts = std::make_unique<DependencyOutputOptions>(Opts);
40      Dependencies.push_back(std::string(File));
41    }
42
43    void handleModuleDependency(ModuleDeps MD) override {
44      // These are ignored for the make format as it can't support the full
45      // set of deps, and handleFileDependency handles enough for implicitly
46      // built modules to work.
47    }
48
49    void handleContextHash(std::string Hash) override {}
50
51    void printDependencies(std::string &S) {
52      if (!Opts)
53        return;
54
55      class DependencyPrinter : public DependencyFileGenerator {
56      public:
57        DependencyPrinter(DependencyOutputOptions &Opts,
58                          ArrayRef<std::string> Dependencies)
59            : DependencyFileGenerator(Opts) {
60          for (const auto &Dep : Dependencies)
61            addDependency(Dep);
62        }
63
64        void printDependencies(std::string &S) {
65          llvm::raw_string_ostream OS(S);
66          outputDependencyFile(OS);
67        }
68      };
69
70      DependencyPrinter Generator(*Opts, Dependencies);
71      Generator.printDependencies(S);
72    }
73
74  private:
75    std::unique_ptr<DependencyOutputOptions> Opts;
76    std::vector<std::string> Dependencies;
77  };
78
79  // We expect a single command here because if a source file occurs multiple
80  // times in the original CDB, then `computeDependencies` would run the
81  // `DependencyScanningAction` once for every time the input occured in the
82  // CDB. Instead we split up the CDB into single command chunks to avoid this
83  // behavior.
84  assert(Compilations.getAllCompileCommands().size() == 1 &&
85         "Expected a compilation database with a single command!");
86  std::string Input = Compilations.getAllCompileCommands().front().Filename;
87
88  MakeDependencyPrinterConsumer Consumer;
89  auto Result = Worker.computeDependencies(Input, CWD, Compilations, Consumer);
90  if (Result)
91    return std::move(Result);
92  std::string Output;
93  Consumer.printDependencies(Output);
94  return Output;
95}
96
97llvm::Expected<FullDependenciesResult>
98DependencyScanningTool::getFullDependencies(
99    const tooling::CompilationDatabase &Compilations, StringRef CWD,
100    const llvm::StringSet<> &AlreadySeen) {
101  class FullDependencyPrinterConsumer : public DependencyConsumer {
102  public:
103    FullDependencyPrinterConsumer(const llvm::StringSet<> &AlreadySeen)
104        : AlreadySeen(AlreadySeen) {}
105
106    void handleFileDependency(const DependencyOutputOptions &Opts,
107                              StringRef File) override {
108      Dependencies.push_back(std::string(File));
109    }
110
111    void handleModuleDependency(ModuleDeps MD) override {
112      ClangModuleDeps[MD.ContextHash + MD.ModuleName] = std::move(MD);
113    }
114
115    void handleContextHash(std::string Hash) override {
116      ContextHash = std::move(Hash);
117    }
118
119    FullDependenciesResult getFullDependencies() const {
120      FullDependencies FD;
121
122      FD.ContextHash = std::move(ContextHash);
123
124      FD.FileDeps.assign(Dependencies.begin(), Dependencies.end());
125
126      for (auto &&M : ClangModuleDeps) {
127        auto &MD = M.second;
128        if (MD.ImportedByMainFile)
129          FD.ClangModuleDeps.push_back({MD.ModuleName, ContextHash});
130      }
131
132      FullDependenciesResult FDR;
133
134      for (auto &&M : ClangModuleDeps) {
135        // TODO: Avoid handleModuleDependency even being called for modules
136        //   we've already seen.
137        if (AlreadySeen.count(M.first))
138          continue;
139        FDR.DiscoveredModules.push_back(std::move(M.second));
140      }
141
142      FDR.FullDeps = std::move(FD);
143      return FDR;
144    }
145
146  private:
147    std::vector<std::string> Dependencies;
148    std::unordered_map<std::string, ModuleDeps> ClangModuleDeps;
149    std::string ContextHash;
150    std::vector<std::string> OutputPaths;
151    const llvm::StringSet<> &AlreadySeen;
152  };
153
154  // We expect a single command here because if a source file occurs multiple
155  // times in the original CDB, then `computeDependencies` would run the
156  // `DependencyScanningAction` once for every time the input occured in the
157  // CDB. Instead we split up the CDB into single command chunks to avoid this
158  // behavior.
159  assert(Compilations.getAllCompileCommands().size() == 1 &&
160         "Expected a compilation database with a single command!");
161  std::string Input = Compilations.getAllCompileCommands().front().Filename;
162
163  FullDependencyPrinterConsumer Consumer(AlreadySeen);
164  llvm::Error Result =
165      Worker.computeDependencies(Input, CWD, Compilations, Consumer);
166  if (Result)
167    return std::move(Result);
168  return Consumer.getFullDependencies();
169}
170
171} // end namespace dependencies
172} // end namespace tooling
173} // end namespace clang
174