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