clang 24.0.0git
DependencyScanningTool.cpp
Go to the documentation of this file.
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
14#include "clang/Driver/Driver.h"
15#include "clang/Driver/Tool.h"
17#include "llvm/ADT/SmallVectorExtras.h"
18#include "llvm/ADT/iterator.h"
19#include "llvm/TargetParser/Host.h"
20#include <optional>
21
22using namespace clang;
23using namespace tooling;
24using namespace dependencies;
25
26namespace {
27/// Prints out all of the gathered dependencies into a string.
28class MakeDependencyPrinterConsumer : public DependencyConsumer {
29public:
30 void handleBuildCommand(Command) override {}
31
32 void
33 handleDependencyOutputOpts(const DependencyOutputOptions &Opts) override {
34 this->Opts = std::make_unique<DependencyOutputOptions>(Opts);
35 }
36
37 void handleFileDependency(StringRef File) override {
38 SmallString<128> NormalizedFile = File;
39 llvm::sys::path::remove_dots(NormalizedFile, /*remove_dot_dot=*/true);
40 Dependencies.emplace_back(NormalizedFile.str());
41 }
42
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 void handlePrebuiltModuleDependency(PrebuiltModuleDep PMD) override {}
47 void handleModuleDependency(ModuleDeps MD) override {
48 MD.forEachFileDep([this](StringRef File) {
49 DependenciesFromModules.push_back(std::string(File));
50 });
51 }
52 void handleDirectModuleDependency(ModuleID ID) override {}
53 void handleVisibleModule(std::string ModuleName) override {}
54 void handleContextHash(std::string Hash) override {}
55
56 void printDependencies(std::string &S) {
57 assert(Opts && "Handled dependency output options.");
58
59 class DependencyPrinter : public DependencyFileGenerator {
60 public:
61 DependencyPrinter(DependencyOutputOptions &Opts,
62 ArrayRef<std::string> Dependencies,
63 ArrayRef<std::string> ModuleDependencies)
64 : DependencyFileGenerator(Opts) {
65 for (const auto &Dep : Dependencies)
66 addDependency(Dep);
67 for (const auto &Dep : ModuleDependencies)
68 addDependency(Dep);
69 }
70
71 void printDependencies(std::string &S) {
72 llvm::raw_string_ostream OS(S);
73 outputDependencyFile(OS);
74 }
75 };
76
77 DependencyPrinter Generator(*Opts, Dependencies, DependenciesFromModules);
78 Generator.printDependencies(S);
79 }
80
81protected:
82 std::unique_ptr<DependencyOutputOptions> Opts;
83 std::vector<std::string> Dependencies;
84 std::vector<std::string> DependenciesFromModules;
85};
86} // anonymous namespace
87
88static std::pair<std::unique_ptr<driver::Driver>,
89 std::unique_ptr<driver::Compilation>>
92 llvm::BumpPtrAllocator &Alloc) {
94 Argv.reserve(ArgStrs.size());
95 for (const std::string &Arg : ArgStrs)
96 Argv.push_back(Arg.c_str());
97
98 std::unique_ptr<driver::Driver> Driver = std::make_unique<driver::Driver>(
99 Argv[0], llvm::sys::getDefaultTargetTriple(), Diags,
100 "clang LLVM compiler", FS);
101 Driver->setTitle("clang_based_tool");
102
103 bool CLMode = driver::IsClangCL(
104 driver::getDriverMode(Argv[0], ArrayRef(Argv).slice(1)));
105
106 if (llvm::Error E =
107 driver::expandResponseFiles(Argv, CLMode, Alloc, FS.get())) {
108 Diags.Report(diag::err_drv_expand_response_file)
109 << llvm::toString(std::move(E));
110 return std::make_pair(nullptr, nullptr);
111 }
112
113 std::unique_ptr<driver::Compilation> Compilation(
114 Driver->BuildCompilation(Argv));
115 if (!Compilation)
116 return std::make_pair(nullptr, nullptr);
117
118 if (Compilation->containsError())
119 return std::make_pair(nullptr, nullptr);
120
121 if (Compilation->getJobs().empty()) {
122 Diags.Report(diag::err_fe_expected_compiler_job)
123 << llvm::join(ArgStrs, " ");
124 return std::make_pair(nullptr, nullptr);
125 }
126
127 return std::make_pair(std::move(Driver), std::move(Compilation));
128}
129
130/// Constructs the full frontend command line, including executable, for the
131/// given driver \c Cmd.
134 const auto &Args = Cmd.getArguments();
136 Out.reserve(Args.size() + 1);
137 Out.emplace_back(Cmd.getExecutable());
138 llvm::append_range(Out, Args);
139 return Out;
140}
141
143 DependencyScanningWorker &Worker, StringRef WorkingDirectory,
144 ArrayRef<std::string> CommandLine, DependencyConsumer &Consumer,
145 DependencyActionController &Controller, DiagnosticConsumer &DiagConsumer,
147 auto FS = Worker.makeEffectiveVFS(WorkingDirectory, OverlayFS);
148
149 // Compilation holds a non-owning a reference to the Driver, hence we need to
150 // keep the Driver alive when we use Compilation. Arguments to commands may be
151 // owned by Alloc when expanded from response files.
152 llvm::BumpPtrAllocator Alloc;
153 auto DiagEngineWithDiagOpts =
154 DiagnosticsEngineWithDiagOpts(CommandLine, FS, DiagConsumer);
155 const auto [Driver, Compilation] = buildCompilation(
156 CommandLine, *DiagEngineWithDiagOpts.DiagEngine, FS, Alloc);
157 if (!Compilation)
158 return false;
159
160 SmallVector<SmallVector<std::string, 0>> FrontendCommandLines;
161 for (const auto &Cmd : Compilation->getJobs())
162 FrontendCommandLines.push_back(buildCC1CommandLine(Cmd));
163 SmallVector<ArrayRef<std::string>> FrontendCommandLinesView(
164 FrontendCommandLines.begin(), FrontendCommandLines.end());
165
166 return Worker.computeDependencies(WorkingDirectory, FrontendCommandLinesView,
167 Consumer, Controller, DiagConsumer,
168 std::move(OverlayFS));
169}
170
172 DependencyScanningWorker &Worker, StringRef WorkingDirectory,
173 ArrayRef<std::string> CommandLine, DependencyConsumer &Consumer,
174 DependencyActionController &Controller, DiagnosticConsumer &DiagConsumer,
176 const auto IsCC1Input = (CommandLine.size() >= 2 && CommandLine[1] == "-cc1");
177 return IsCC1Input ? Worker.computeDependencies(WorkingDirectory, CommandLine,
178 Consumer, Controller,
179 DiagConsumer, OverlayFS)
181 Worker, WorkingDirectory, CommandLine, Consumer,
182 Controller, DiagConsumer, OverlayFS);
183}
184
186 ArrayRef<std::string> CommandLine, StringRef CWD,
187 LookupModuleOutputCallback LookupModuleOutput,
188 DiagnosticConsumer &DiagConsumer) {
189 MakeDependencyPrinterConsumer DepConsumer;
190 CallbackActionController Controller(LookupModuleOutput);
191 if (!computeDependencies(Worker, CWD, CommandLine, DepConsumer, Controller,
192 DiagConsumer))
193 return std::nullopt;
194 std::string Output;
195 DepConsumer.printDependencies(Output);
196 return Output;
197}
198
200 const CompileCommand &Command, StringRef CWD, std::string &MakeformatOutput,
201 std::string &MakeformatOutputPath, DiagnosticConsumer &DiagConsumer) {
202 class P1689ModuleDependencyPrinterConsumer
203 : public MakeDependencyPrinterConsumer {
204 public:
205 P1689ModuleDependencyPrinterConsumer(P1689Rule &Rule,
206 const CompileCommand &Command)
207 : Filename(Command.Filename), Rule(Rule) {
208 Rule.PrimaryOutput = Command.Output;
209 }
210
211 void handleProvidedAndRequiredStdCXXModules(
212 std::optional<P1689ModuleInfo> Provided,
213 std::vector<P1689ModuleInfo> Requires) override {
214 Rule.Provides = std::move(Provided);
215 if (Rule.Provides)
216 Rule.Provides->SourcePath = Filename.str();
217 Rule.Requires = std::move(Requires);
218 }
219
220 StringRef getMakeFormatDependencyOutputPath() {
222 return {};
223 return Opts->OutputFile;
224 }
225
226 private:
227 StringRef Filename;
228 P1689Rule &Rule;
229 };
230
231 class P1689ActionController : public DependencyActionController {
232 public:
233 // The lookupModuleOutput is for clang modules. P1689 format don't need it.
234 std::string lookupModuleOutput(const ModuleDeps &,
235 ModuleOutputKind Kind) override {
236 return "";
237 }
238
239 std::unique_ptr<DependencyActionController> clone() const override {
240 return std::make_unique<P1689ActionController>();
241 }
242 };
243
244 P1689Rule Rule;
245 P1689ModuleDependencyPrinterConsumer Consumer(Rule, Command);
246 P1689ActionController Controller;
247 if (!computeDependencies(Worker, CWD, Command.CommandLine, Consumer,
248 Controller, DiagConsumer))
249 return std::nullopt;
250
251 MakeformatOutputPath = Consumer.getMakeFormatDependencyOutputPath();
252 if (!MakeformatOutputPath.empty())
253 Consumer.printDependencies(MakeformatOutput);
254 return Rule;
255}
256
257static std::pair<IntrusiveRefCntPtr<llvm::vfs::FileSystem>,
258 std::vector<std::string>>
260 llvm::MemoryBufferRef TUBuffer) {
261 StringRef InputPath = TUBuffer.getBufferIdentifier();
262 auto InputBuf = llvm::MemoryBuffer::getMemBufferCopy(TUBuffer.getBuffer());
263
264 auto FS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
265 FS->addFile(InputPath, 0, std::move(InputBuf));
266
267 std::vector<std::string> ModifiedCommandLine(CommandLine);
268 ModifiedCommandLine.emplace_back(InputPath);
269
270 return std::make_pair(std::move(FS), ModifiedCommandLine);
271}
272
273static std::pair<IntrusiveRefCntPtr<llvm::vfs::FileSystem>,
274 std::vector<std::string>>
276 // The fake input buffer is read-only, and it is used to produce unique source
277 // locations for the diagnostics. Therefore, sharing this global buffer across
278 // threads is ok.
279 static const std::string FakeInput(
281
282 StringRef InputPath =
283 llvm::sys::path::is_style_windows(llvm::sys::path::Style::native)
284 ? "Z:\\module-include.input"
285 : "/module-include.input";
286 auto InputBuf = llvm::MemoryBuffer::getMemBuffer(FakeInput, InputPath);
287
288 auto FS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
289 FS->addFile(InputPath, 0, std::move(InputBuf));
290
291 std::vector<std::string> ModifiedCommandLine(CommandLine);
292 ModifiedCommandLine.emplace_back(InputPath);
293
294 return std::make_pair(std::move(FS), ModifiedCommandLine);
295}
296
297std::optional<TranslationUnitDeps>
299 ArrayRef<std::string> CommandLine, StringRef CWD,
300 DiagnosticConsumer &DiagConsumer,
301 const llvm::DenseSet<ModuleID> &AlreadySeen,
302 LookupModuleOutputCallback LookupModuleOutput,
303 std::optional<llvm::MemoryBufferRef> TUBuffer) {
304 FullDependencyConsumer Consumer(AlreadySeen);
305 CallbackActionController Controller(LookupModuleOutput);
306
307 // If we are scanning from a TUBuffer, create an overlay filesystem with the
308 // input as an in-memory file and add it to the command line.
310 std::vector<std::string> CommandLineWithTUBufferInput;
311 if (TUBuffer) {
312 std::tie(OverlayFS, CommandLineWithTUBufferInput) =
313 initVFSForTUBufferScanning(CommandLine, *TUBuffer);
314 CommandLine = CommandLineWithTUBufferInput;
315 }
316
317 if (!computeDependencies(Worker, CWD, CommandLine, Consumer, Controller,
318 DiagConsumer, std::move(OverlayFS)))
319 return std::nullopt;
320 return Consumer.takeTranslationUnitDeps();
321}
322
323static std::optional<SmallVector<std::string, 0>>
325 DiagnosticsEngine &Diags,
327 // Compilation holds a non-owning a reference to the Driver, hence we need to
328 // keep the Driver alive when we use Compilation. Arguments to commands may be
329 // owned by Alloc when expanded from response files.
330 llvm::BumpPtrAllocator Alloc;
331 const auto [Driver, Compilation] =
332 buildCompilation(CommandLine, Diags, std::move(FS), Alloc);
333 if (!Compilation)
334 return std::nullopt;
335
336 const auto IsClangCmd = [](const driver::Command &Cmd) {
337 return StringRef(Cmd.getCreator().getName()) == "clang";
338 };
339
340 const auto &Jobs = Compilation->getJobs();
341 if (const auto It = llvm::find_if(Jobs, IsClangCmd); It != Jobs.end())
342 return buildCC1CommandLine(*It);
343 return std::nullopt;
344}
345
347 StringRef CWD, ArrayRef<std::string> CommandLine,
348 DiagnosticConsumer &DiagConsumer, DependencyActionController &Controller,
349 llvm::function_ref<std::optional<std::string>()> getNextName,
350 DependencyConsumer &DepConsumer) {
351 auto [OverlayFS, ModifiedCommandLine] = initVFSForByNameScanning(CommandLine);
352 auto FS = Worker.makeEffectiveVFS(CWD, OverlayFS);
353 std::vector<std::string> CC1CommandLine;
354 if (ModifiedCommandLine.size() >= 2 && ModifiedCommandLine[1] == "-cc1") {
355 CC1CommandLine = std::move(ModifiedCommandLine);
356 } else {
357 // Driver-style (or ill-formed): lower to a cc1 command line, or diagnose.
358 DiagnosticsEngineWithDiagOpts DiagEngineWithOpts(ModifiedCommandLine, FS,
359 DiagConsumer);
360 auto MaybeFirstCC1 = getFirstCC1CommandLine(
361 ModifiedCommandLine, *DiagEngineWithOpts.DiagEngine, FS);
362 if (!MaybeFirstCC1)
363 return false;
364 CC1CommandLine.assign(MaybeFirstCC1->begin(), MaybeFirstCC1->end());
365 }
366
367 return Worker.computeDependenciesByName(CWD, CC1CommandLine,
368 std::move(OverlayFS), DiagConsumer,
369 Controller, getNextName, DepConsumer);
370}
Defines the Diagnostic-related interfaces.
static std::pair< IntrusiveRefCntPtr< llvm::vfs::FileSystem >, std::vector< std::string > > initVFSForByNameScanning(ArrayRef< std::string > CommandLine)
static std::optional< SmallVector< std::string, 0 > > getFirstCC1CommandLine(ArrayRef< std::string > CommandLine, DiagnosticsEngine &Diags, llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > FS)
static std::pair< std::unique_ptr< driver::Driver >, std::unique_ptr< driver::Compilation > > buildCompilation(ArrayRef< std::string > ArgStrs, DiagnosticsEngine &Diags, IntrusiveRefCntPtr< llvm::vfs::FileSystem > FS, llvm::BumpPtrAllocator &Alloc)
static bool computeDependenciesForDriverCommandLine(DependencyScanningWorker &Worker, StringRef WorkingDirectory, ArrayRef< std::string > CommandLine, DependencyConsumer &Consumer, DependencyActionController &Controller, DiagnosticConsumer &DiagConsumer, IntrusiveRefCntPtr< llvm::vfs::FileSystem > OverlayFS)
static std::pair< IntrusiveRefCntPtr< llvm::vfs::FileSystem >, std::vector< std::string > > initVFSForTUBufferScanning(ArrayRef< std::string > CommandLine, llvm::MemoryBufferRef TUBuffer)
static SmallVector< std::string, 0 > buildCC1CommandLine(const driver::Command &Cmd)
Constructs the full frontend command line, including executable, for the given driver Cmd.
DependencyOutputFormat OutputFormat
The format for the dependency file.
std::string OutputFile
The file to write dependency output to.
Abstract interface, implemented by clients of the front-end, which formats and prints fully processed...
Concrete class used by the front-end to report problems and issues.
Definition Diagnostic.h:234
DiagnosticBuilder Report(SourceLocation Loc, unsigned DiagID)
Issue the message to the client.
A simple dependency action controller that uses a callback.
Dependency scanner callbacks that are used during scanning to influence the behaviour of the scan - f...
An individual dependency scanning worker that is able to run on its own thread.
Command - An executable path/name and argument vector to execute.
Definition Job.h:107
const llvm::opt::ArgStringList & getArguments() const
Definition Job.h:243
const char * getExecutable() const
Definition Job.h:241
std::optional< std::string > getDependencyFile(ArrayRef< std::string > CommandLine, StringRef CWD, dependencies::LookupModuleOutputCallback LookupModuleOutput, DiagnosticConsumer &DiagConsumer)
Print out the dependency information into a string using the dependency file format that is specified...
std::optional< dependencies::TranslationUnitDeps > getTranslationUnitDependencies(ArrayRef< std::string > CommandLine, StringRef CWD, DiagnosticConsumer &DiagConsumer, const llvm::DenseSet< dependencies::ModuleID > &AlreadySeen, dependencies::LookupModuleOutputCallback LookupModuleOutput, std::optional< llvm::MemoryBufferRef > TUBuffer=std::nullopt)
Given a Clang driver command-line for a translation unit, gather the modular dependencies and return ...
bool getByNameDependencies(StringRef CWD, ArrayRef< std::string > CommandLine, DiagnosticConsumer &DiagConsumer, dependencies::DependencyActionController &Controller, llvm::function_ref< std::optional< std::string >()> getNextName, dependencies::DependencyConsumer &DepConsumer)
By-name scanning given a Clang command-line.
std::optional< P1689Rule > getP1689ModuleDependencyFile(const CompileCommand &Command, StringRef CWD, std::string &MakeformatOutput, std::string &MakeformatOutputPath, DiagnosticConsumer &DiagConsumer)
Collect the module dependency in P1689 format for C++20 named modules.
llvm::function_ref< std::string(const ModuleDeps &, ModuleOutputKind)> LookupModuleOutputCallback
A callback to lookup module outputs for "-fmodule-file=", "-o" etc.
ModuleOutputKind
An output from a module compilation, such as the path of the module file.
llvm::StringRef getDriverMode(StringRef ProgName, ArrayRef< const char * > Args)
Returns the driver mode option's value, i.e.
Definition Driver.cpp:7531
llvm::Error expandResponseFiles(SmallVectorImpl< const char * > &Args, bool ClangCLMode, llvm::BumpPtrAllocator &Alloc, llvm::vfs::FileSystem *FS=nullptr)
Expand response files from a clang driver or cc1 invocation.
Definition Driver.cpp:7548
bool IsClangCL(StringRef DriverMode)
Checks whether the value produced by getDriverMode is for CL mode.
Definition Driver.cpp:7546
@ OS
Indicates that the tracking object is a descendant of a referenced-counted OSObject,...
bool computeDependencies(dependencies::DependencyScanningWorker &Worker, StringRef WorkingDirectory, ArrayRef< std::string > CommandLine, dependencies::DependencyConsumer &Consumer, dependencies::DependencyActionController &Controller, DiagnosticConsumer &DiagConsumer, IntrusiveRefCntPtr< llvm::vfs::FileSystem > OverlayFS=nullptr)
Run the dependency scanning worker for the given driver or frontend command-line, and report the disc...
std::shared_ptr< MatchComputation< T > > Generator
Definition RewriteRule.h:65
The JSON file list parser is used to communicate input to InstallAPI.
@ Worker
'worker' clause, allowed on 'loop', Combined, and 'routine' directives.
A command-line tool invocation that is part of building a TU.
IntrusiveRefCntPtr< DiagnosticsEngine > DiagEngine
void forEachFileDep(llvm::function_ref< void(StringRef)> Cb) const
Invokes Cb for all file dependencies of this module.
Specifies the working directory and command of a compilation.