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 using namespace clang;
13 using namespace tooling;
14 using namespace dependencies;
15 
16 std::vector<std::string> FullDependencies::getCommandLine(
17     llvm::function_ref<std::string(const ModuleID &, ModuleOutputKind)>
18         LookupModuleOutput) const {
19   std::vector<std::string> Ret = getCommandLineWithoutModulePaths();
20 
21   for (ModuleID MID : ClangModuleDeps) {
22     auto PCM = LookupModuleOutput(MID, ModuleOutputKind::ModuleFile);
23     Ret.push_back("-fmodule-file=" + PCM);
24   }
25 
26   return Ret;
27 }
28 
29 std::vector<std::string>
30 FullDependencies::getCommandLineWithoutModulePaths() const {
31   std::vector<std::string> Args = OriginalCommandLine;
32 
33   Args.push_back("-fno-implicit-modules");
34   Args.push_back("-fno-implicit-module-maps");
35   for (const PrebuiltModuleDep &PMD : PrebuiltModuleDeps)
36     Args.push_back("-fmodule-file=" + PMD.PCMFile);
37 
38   // These arguments are unused in explicit compiles.
39   llvm::erase_if(Args, [](StringRef Arg) {
40     if (Arg.consume_front("-fmodules-")) {
41       return Arg.startswith("cache-path=") ||
42              Arg.startswith("prune-interval=") ||
43              Arg.startswith("prune-after=") ||
44              Arg == "validate-once-per-build-session";
45     }
46     return Arg.startswith("-fbuild-session-file=");
47   });
48 
49   return Args;
50 }
51 
52 DependencyScanningTool::DependencyScanningTool(
53     DependencyScanningService &Service)
54     : Worker(Service) {}
55 
56 llvm::Expected<std::string> DependencyScanningTool::getDependencyFile(
57     const std::vector<std::string> &CommandLine, StringRef CWD,
58     llvm::Optional<StringRef> ModuleName) {
59   /// Prints out all of the gathered dependencies into a string.
60   class MakeDependencyPrinterConsumer : public DependencyConsumer {
61   public:
62     void
63     handleDependencyOutputOpts(const DependencyOutputOptions &Opts) override {
64       this->Opts = std::make_unique<DependencyOutputOptions>(Opts);
65     }
66 
67     void handleFileDependency(StringRef File) override {
68       Dependencies.push_back(std::string(File));
69     }
70 
71     void handlePrebuiltModuleDependency(PrebuiltModuleDep PMD) override {
72       // Same as `handleModuleDependency`.
73     }
74 
75     void handleModuleDependency(ModuleDeps MD) override {
76       // These are ignored for the make format as it can't support the full
77       // set of deps, and handleFileDependency handles enough for implicitly
78       // built modules to work.
79     }
80 
81     void handleContextHash(std::string Hash) override {}
82 
83     void printDependencies(std::string &S) {
84       assert(Opts && "Handled dependency output options.");
85 
86       class DependencyPrinter : public DependencyFileGenerator {
87       public:
88         DependencyPrinter(DependencyOutputOptions &Opts,
89                           ArrayRef<std::string> Dependencies)
90             : DependencyFileGenerator(Opts) {
91           for (const auto &Dep : Dependencies)
92             addDependency(Dep);
93         }
94 
95         void printDependencies(std::string &S) {
96           llvm::raw_string_ostream OS(S);
97           outputDependencyFile(OS);
98         }
99       };
100 
101       DependencyPrinter Generator(*Opts, Dependencies);
102       Generator.printDependencies(S);
103     }
104 
105   private:
106     std::unique_ptr<DependencyOutputOptions> Opts;
107     std::vector<std::string> Dependencies;
108   };
109 
110   MakeDependencyPrinterConsumer Consumer;
111   auto Result =
112       Worker.computeDependencies(CWD, CommandLine, Consumer, ModuleName);
113   if (Result)
114     return std::move(Result);
115   std::string Output;
116   Consumer.printDependencies(Output);
117   return Output;
118 }
119 
120 llvm::Expected<FullDependenciesResult>
121 DependencyScanningTool::getFullDependencies(
122     const std::vector<std::string> &CommandLine, StringRef CWD,
123     const llvm::StringSet<> &AlreadySeen,
124     llvm::Optional<StringRef> ModuleName) {
125   class FullDependencyPrinterConsumer : public DependencyConsumer {
126   public:
127     FullDependencyPrinterConsumer(const llvm::StringSet<> &AlreadySeen)
128         : AlreadySeen(AlreadySeen) {}
129 
130     void
131     handleDependencyOutputOpts(const DependencyOutputOptions &Opts) override {}
132 
133     void handleFileDependency(StringRef File) override {
134       Dependencies.push_back(std::string(File));
135     }
136 
137     void handlePrebuiltModuleDependency(PrebuiltModuleDep PMD) override {
138       PrebuiltModuleDeps.emplace_back(std::move(PMD));
139     }
140 
141     void handleModuleDependency(ModuleDeps MD) override {
142       ClangModuleDeps[MD.ID.ContextHash + MD.ID.ModuleName] = std::move(MD);
143     }
144 
145     void handleContextHash(std::string Hash) override {
146       ContextHash = std::move(Hash);
147     }
148 
149     FullDependenciesResult getFullDependencies(
150         const std::vector<std::string> &OriginalCommandLine) const {
151       FullDependencies FD;
152 
153       FD.OriginalCommandLine =
154           ArrayRef<std::string>(OriginalCommandLine).slice(1);
155 
156       FD.ID.ContextHash = std::move(ContextHash);
157 
158       FD.FileDeps.assign(Dependencies.begin(), Dependencies.end());
159 
160       for (auto &&M : ClangModuleDeps) {
161         auto &MD = M.second;
162         if (MD.ImportedByMainFile)
163           FD.ClangModuleDeps.push_back(MD.ID);
164       }
165 
166       FD.PrebuiltModuleDeps = std::move(PrebuiltModuleDeps);
167 
168       FullDependenciesResult FDR;
169 
170       for (auto &&M : ClangModuleDeps) {
171         // TODO: Avoid handleModuleDependency even being called for modules
172         //   we've already seen.
173         if (AlreadySeen.count(M.first))
174           continue;
175         FDR.DiscoveredModules.push_back(std::move(M.second));
176       }
177 
178       FDR.FullDeps = std::move(FD);
179       return FDR;
180     }
181 
182   private:
183     std::vector<std::string> Dependencies;
184     std::vector<PrebuiltModuleDep> PrebuiltModuleDeps;
185     llvm::MapVector<std::string, ModuleDeps, llvm::StringMap<unsigned>> ClangModuleDeps;
186     std::string ContextHash;
187     std::vector<std::string> OutputPaths;
188     const llvm::StringSet<> &AlreadySeen;
189   };
190 
191   FullDependencyPrinterConsumer Consumer(AlreadySeen);
192   llvm::Error Result =
193       Worker.computeDependencies(CWD, CommandLine, Consumer, ModuleName);
194   if (Result)
195     return std::move(Result);
196   return Consumer.getFullDependencies(CommandLine);
197 }
198