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 
13 static 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 
21 namespace clang{
22 namespace tooling{
23 namespace dependencies{
24 
25 DependencyScanningTool::DependencyScanningTool(
26     DependencyScanningService &Service)
27     : Format(Service.getFormat()), Worker(Service) {
28 }
29 
30 llvm::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