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
15#include "clang/Driver/Driver.h"
16#include "clang/Driver/Tool.h"
20#include "llvm/ADT/SmallVectorExtras.h"
21#include "llvm/ADT/iterator.h"
22#include "llvm/TargetParser/Host.h"
23#include <optional>
24
25using namespace clang;
26using namespace tooling;
27using namespace dependencies;
28
29namespace {
30/// Prints out all of the gathered dependencies into a string.
31class MakeDependencyPrinterConsumer : public DependencyConsumer {
32public:
33 void handleBuildCommand(Command) override {}
34
35 void
36 handleDependencyOutputOpts(const DependencyOutputOptions &Opts) override {
37 this->Opts = std::make_unique<DependencyOutputOptions>(Opts);
38 }
39
40 void handleFileDependency(StringRef File) override {
41 SmallString<128> NormalizedFile = File;
42 llvm::sys::path::remove_dots(NormalizedFile, /*remove_dot_dot=*/true);
43 Dependencies.emplace_back(NormalizedFile.str());
44 }
45
46 // These are ignored for the make format as it can't support the full
47 // set of deps, and handleFileDependency handles enough for implicitly
48 // built modules to work.
49 void handlePrebuiltModuleDependency(PrebuiltModuleDep PMD) override {}
50 void handleModuleDependency(ModuleDeps MD) override {
51 MD.forEachFileDep([this](StringRef File) {
52 DependenciesFromModules.push_back(std::string(File));
53 });
54 }
55 void handleDirectModuleDependency(ModuleID ID) override {}
56 void handleVisibleModule(std::string ModuleName) override {}
57 void handleContextHash(std::string Hash) override {}
58
59 void printDependencies(std::string &S) {
60 assert(Opts && "Handled dependency output options.");
61
62 class DependencyPrinter : public DependencyFileGenerator {
63 public:
64 DependencyPrinter(DependencyOutputOptions &Opts,
65 ArrayRef<std::string> Dependencies,
66 ArrayRef<std::string> ModuleDependencies)
67 : DependencyFileGenerator(Opts) {
68 for (const auto &Dep : Dependencies)
69 addDependency(Dep);
70 for (const auto &Dep : ModuleDependencies)
71 addDependency(Dep);
72 }
73
74 void printDependencies(std::string &S) {
75 llvm::raw_string_ostream OS(S);
76 outputDependencyFile(OS);
77 }
78 };
79
80 DependencyPrinter Generator(*Opts, Dependencies, DependenciesFromModules);
81 Generator.printDependencies(S);
82 }
83
84protected:
85 std::unique_ptr<DependencyOutputOptions> Opts;
86 std::vector<std::string> Dependencies;
87 std::vector<std::string> DependenciesFromModules;
88};
89} // anonymous namespace
90
91static std::pair<std::unique_ptr<driver::Driver>,
92 std::unique_ptr<driver::Compilation>>
95 llvm::BumpPtrAllocator &Alloc, AtomicLineLogger &Logger) {
97 Argv.reserve(ArgStrs.size());
98 for (const std::string &Arg : ArgStrs)
99 Argv.push_back(Arg.c_str());
100
101 std::unique_ptr<driver::Driver> Driver = std::make_unique<driver::Driver>(
102 Argv[0], llvm::sys::getDefaultTargetTriple(), Diags,
103 "clang LLVM compiler", FS);
104 Driver->setTitle("clang_based_tool");
105
106 bool CLMode = driver::IsClangCL(
107 driver::getDriverMode(Argv[0], ArrayRef(Argv).slice(1)));
108
109 if (llvm::Error E =
110 driver::expandResponseFiles(Argv, CLMode, Alloc, FS.get())) {
111 Diags.Report(diag::err_drv_expand_response_file)
112 << llvm::toString(std::move(E));
113 return std::make_pair(nullptr, nullptr);
114 }
115
116 std::unique_ptr<driver::Compilation> Compilation(
117 Driver->BuildCompilation(Argv));
118 if (!Compilation)
119 return std::make_pair(nullptr, nullptr);
120
121 if (Compilation->containsError())
122 return std::make_pair(nullptr, nullptr);
123
124 if (Compilation->getJobs().empty()) {
125 Diags.Report(diag::err_fe_expected_compiler_job)
126 << llvm::join(ArgStrs, " ");
127 return std::make_pair(nullptr, nullptr);
128 }
129
130 const llvm::opt::Arg *LogPathArg =
131 Compilation->getArgs().getLastArg(options::OPT_fdepscan_log_path);
132 StringRef LogPath =
133 LogPathArg ? StringRef(LogPathArg->getValue()).trim() : StringRef();
134
135 // Forbid -fdepscan-log-path="".
136 if (LogPathArg && LogPath.empty()) {
137 Diags.Report(diag::err_drv_depscan_log_path_empty);
138 return std::make_pair(nullptr, nullptr);
139 }
140
141 if (!Logger.enable(LogPath)) {
142 Diags.Report(diag::err_drv_depscan_log_path_inconsistent)
143 << unsigned(!LogPath.empty()) << LogPath
144 << unsigned(!Logger.getLogPath().empty()) << Logger.getLogPath();
145 return std::make_pair(nullptr, nullptr);
146 }
147
148 return std::make_pair(std::move(Driver), std::move(Compilation));
149}
150
151/// Constructs the full frontend command line, including executable, for the
152/// given driver \c Cmd.
155 const auto &Args = Cmd.getArguments();
157 Out.reserve(Args.size() + 1);
158 Out.emplace_back(Cmd.getExecutable());
159 llvm::append_range(Out, Args);
160 return Out;
161}
162
164 DependencyScanningWorker &Worker, StringRef WorkingDirectory,
165 ArrayRef<std::string> CommandLine, DependencyConsumer &Consumer,
166 DependencyActionController &Controller, DiagnosticConsumer &DiagConsumer,
168 auto FS = Worker.makeEffectiveVFS(WorkingDirectory, OverlayFS);
169
170 // Compilation holds a non-owning a reference to the Driver, hence we need to
171 // keep the Driver alive when we use Compilation. Arguments to commands may be
172 // owned by Alloc when expanded from response files.
173 llvm::BumpPtrAllocator Alloc;
174 auto DiagOpts = createScanningDiagOptions(CommandLine);
175 auto DiagEngine =
176 CompilerInstance::createDiagnostics(*FS, *DiagOpts, &DiagConsumer,
177 /*ShouldOwnClient=*/false);
178 const auto [Driver, Compilation] = buildCompilation(
179 CommandLine, *DiagEngine, FS, Alloc, Worker.getService().getLogger());
180 if (!Compilation)
181 return false;
182
183 SmallVector<SmallVector<std::string, 0>> FrontendCommandLines;
184 for (const auto &Cmd : Compilation->getJobs())
185 FrontendCommandLines.push_back(buildCC1CommandLine(Cmd));
186 SmallVector<ArrayRef<std::string>> FrontendCommandLinesView(
187 FrontendCommandLines.begin(), FrontendCommandLines.end());
188
189 return Worker.computeDependencies(WorkingDirectory, FrontendCommandLinesView,
190 Consumer, Controller, DiagConsumer,
191 std::move(OverlayFS));
192}
193
195 DependencyScanningWorker &Worker, StringRef WorkingDirectory,
196 ArrayRef<std::string> CommandLine, DependencyConsumer &Consumer,
197 DependencyActionController &Controller, DiagnosticConsumer &DiagConsumer,
199 const auto IsCC1Input = (CommandLine.size() >= 2 && CommandLine[1] == "-cc1");
200 return IsCC1Input ? Worker.computeDependencies(WorkingDirectory, CommandLine,
201 Consumer, Controller,
202 DiagConsumer, OverlayFS)
204 Worker, WorkingDirectory, CommandLine, Consumer,
205 Controller, DiagConsumer, OverlayFS);
206}
207
209 ArrayRef<std::string> CommandLine, StringRef CWD,
210 LookupModuleOutputCallback LookupModuleOutput,
211 DiagnosticConsumer &DiagConsumer) {
212 MakeDependencyPrinterConsumer DepConsumer;
213 CallbackActionController Controller(LookupModuleOutput);
214 if (!computeDependencies(Worker, CWD, CommandLine, DepConsumer, Controller,
215 DiagConsumer))
216 return std::nullopt;
217 std::string Output;
218 DepConsumer.printDependencies(Output);
219 return Output;
220}
221
223 const CompileCommand &Command, StringRef CWD, std::string &MakeformatOutput,
224 std::string &MakeformatOutputPath, DiagnosticConsumer &DiagConsumer) {
225 class P1689ModuleDependencyPrinterConsumer
226 : public MakeDependencyPrinterConsumer {
227 public:
228 P1689ModuleDependencyPrinterConsumer(P1689Rule &Rule,
229 const CompileCommand &Command)
230 : Filename(Command.Filename), Rule(Rule) {
231 Rule.PrimaryOutput = Command.Output;
232 }
233
234 void handleProvidedAndRequiredStdCXXModules(
235 std::optional<P1689ModuleInfo> Provided,
236 std::vector<P1689ModuleInfo> Requires) override {
237 Rule.Provides = std::move(Provided);
238 if (Rule.Provides)
239 Rule.Provides->SourcePath = Filename.str();
240 Rule.Requires = std::move(Requires);
241 }
242
243 StringRef getMakeFormatDependencyOutputPath() {
245 return {};
246 return Opts->OutputFile;
247 }
248
249 private:
250 StringRef Filename;
251 P1689Rule &Rule;
252 };
253
254 class P1689ActionController : public DependencyActionController {
255 public:
256 void initializeScanInvocation(CompilerInvocation &ScanInvocation) override {
258 true;
259 }
260
261 // The lookupModuleOutput is for clang modules. P1689 format don't need it.
262 std::string lookupModuleOutput(const ModuleDeps &,
263 ModuleOutputKind Kind) override {
264 return "";
265 }
266
267 std::unique_ptr<DependencyActionController> clone() const override {
268 return std::make_unique<P1689ActionController>();
269 }
270 };
271
272 P1689Rule Rule;
273 P1689ModuleDependencyPrinterConsumer Consumer(Rule, Command);
274 P1689ActionController Controller;
275 if (!computeDependencies(Worker, CWD, Command.CommandLine, Consumer,
276 Controller, DiagConsumer))
277 return std::nullopt;
278
279 MakeformatOutputPath = Consumer.getMakeFormatDependencyOutputPath();
280 if (!MakeformatOutputPath.empty())
281 Consumer.printDependencies(MakeformatOutput);
282 return Rule;
283}
284
285static std::pair<IntrusiveRefCntPtr<llvm::vfs::FileSystem>,
286 std::vector<std::string>>
288 llvm::MemoryBufferRef TUBuffer) {
289 StringRef InputPath = TUBuffer.getBufferIdentifier();
290 auto InputBuf = llvm::MemoryBuffer::getMemBufferCopy(TUBuffer.getBuffer());
291
292 auto FS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
293 FS->addFile(InputPath, 0, std::move(InputBuf));
294
295 std::vector<std::string> ModifiedCommandLine(CommandLine);
296 ModifiedCommandLine.emplace_back(InputPath);
297
298 return std::make_pair(std::move(FS), ModifiedCommandLine);
299}
300
301static std::pair<IntrusiveRefCntPtr<llvm::vfs::FileSystem>,
302 std::vector<std::string>>
304 // The fake input buffer is read-only, and it is used to produce unique source
305 // locations for the diagnostics. Therefore, sharing this global buffer across
306 // threads is ok.
307 static const std::string FakeInput(
309
310 StringRef InputPath =
311 llvm::sys::path::is_style_windows(llvm::sys::path::Style::native)
312 ? "Z:\\module-include.input"
313 : "/module-include.input";
314 auto InputBuf = llvm::MemoryBuffer::getMemBuffer(FakeInput, InputPath);
315
316 auto FS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
317 FS->addFile(InputPath, 0, std::move(InputBuf));
318
319 std::vector<std::string> ModifiedCommandLine(CommandLine);
320 ModifiedCommandLine.emplace_back(InputPath);
321
322 return std::make_pair(std::move(FS), ModifiedCommandLine);
323}
324
325std::optional<TranslationUnitDeps>
327 ArrayRef<std::string> CommandLine, StringRef CWD,
328 DiagnosticConsumer &DiagConsumer,
329 const llvm::DenseSet<ModuleID> &AlreadySeen,
330 LookupModuleOutputCallback LookupModuleOutput,
331 std::optional<llvm::MemoryBufferRef> TUBuffer) {
332 FullDependencyConsumer Consumer(AlreadySeen);
333 CallbackActionController Controller(LookupModuleOutput);
334
335 // If we are scanning from a TUBuffer, create an overlay filesystem with the
336 // input as an in-memory file and add it to the command line.
338 std::vector<std::string> CommandLineWithTUBufferInput;
339 if (TUBuffer) {
340 std::tie(OverlayFS, CommandLineWithTUBufferInput) =
341 initVFSForTUBufferScanning(CommandLine, *TUBuffer);
342 CommandLine = CommandLineWithTUBufferInput;
343 }
344
345 if (!computeDependencies(Worker, CWD, CommandLine, Consumer, Controller,
346 DiagConsumer, std::move(OverlayFS)))
347 return std::nullopt;
348 return Consumer.takeTranslationUnitDeps();
349}
350
351static std::optional<SmallVector<std::string, 0>>
353 DiagnosticsEngine &Diags,
355 AtomicLineLogger &Logger) {
356 // Compilation holds a non-owning a reference to the Driver, hence we need to
357 // keep the Driver alive when we use Compilation. Arguments to commands may be
358 // owned by Alloc when expanded from response files.
359 llvm::BumpPtrAllocator Alloc;
360 const auto [Driver, Compilation] =
361 buildCompilation(CommandLine, Diags, std::move(FS), Alloc, Logger);
362 if (!Compilation)
363 return std::nullopt;
364
365 const auto IsClangCmd = [](const driver::Command &Cmd) {
366 return StringRef(Cmd.getCreator().getName()) == "clang";
367 };
368
369 const auto &Jobs = Compilation->getJobs();
370 if (const auto It = llvm::find_if(Jobs, IsClangCmd); It != Jobs.end())
371 return buildCC1CommandLine(*It);
372 return std::nullopt;
373}
374
376 StringRef CWD, ArrayRef<std::string> CommandLine,
377 DiagnosticConsumer &DiagConsumer, DependencyActionController &Controller,
378 llvm::function_ref<std::optional<std::string>()> getNextName,
379 DependencyConsumer &DepConsumer) {
380 auto [OverlayFS, ModifiedCommandLine] = initVFSForByNameScanning(CommandLine);
381 auto FS = Worker.makeEffectiveVFS(CWD, OverlayFS);
382 std::vector<std::string> CC1CommandLine;
383 if (ModifiedCommandLine.size() >= 2 && ModifiedCommandLine[1] == "-cc1") {
384 CC1CommandLine = std::move(ModifiedCommandLine);
385 } else {
386 // Driver-style (or ill-formed): lower to a cc1 command line, or diagnose.
387 auto DiagOpts = createScanningDiagOptions(ModifiedCommandLine);
388 auto DiagEngine =
389 CompilerInstance::createDiagnostics(*FS, *DiagOpts, &DiagConsumer,
390 /*ShouldOwnClient=*/false);
391 auto MaybeFirstCC1 = getFirstCC1CommandLine(
392 ModifiedCommandLine, *DiagEngine, FS, Worker.getService().getLogger());
393 if (!MaybeFirstCC1)
394 return false;
395 CC1CommandLine.assign(MaybeFirstCC1->begin(), MaybeFirstCC1->end());
396 }
397
398 return Worker.computeDependenciesByName(CWD, CC1CommandLine,
399 std::move(OverlayFS), DiagConsumer,
400 Controller, getNextName, DepConsumer);
401}
Defines a logger where each line is written atomically to the file.
Defines the Diagnostic-related interfaces.
static std::pair< IntrusiveRefCntPtr< llvm::vfs::FileSystem >, std::vector< std::string > > initVFSForByNameScanning(ArrayRef< std::string > CommandLine)
static bool computeDependenciesForDriverCommandLine(DependencyScanningWorker &Worker, StringRef WorkingDirectory, ArrayRef< std::string > CommandLine, DependencyConsumer &Consumer, DependencyActionController &Controller, DiagnosticConsumer &DiagConsumer, IntrusiveRefCntPtr< llvm::vfs::FileSystem > OverlayFS)
static std::optional< SmallVector< std::string, 0 > > getFirstCC1CommandLine(ArrayRef< std::string > CommandLine, DiagnosticsEngine &Diags, llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > FS, AtomicLineLogger &Logger)
static std::pair< IntrusiveRefCntPtr< llvm::vfs::FileSystem >, std::vector< std::string > > initVFSForTUBufferScanning(ArrayRef< std::string > CommandLine, llvm::MemoryBufferRef TUBuffer)
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, AtomicLineLogger &Logger)
static SmallVector< std::string, 0 > buildCC1CommandLine(const driver::Command &Cmd)
Constructs the full frontend command line, including executable, for the given driver Cmd.
bool enable(StringRef RequestedLogPath)
Enables the logger if it is not already enabled.
StringRef getLogPath() const
void createDiagnostics(DiagnosticConsumer *Client=nullptr, bool ShouldOwnClient=true)
Create the diagnostics engine using the invocation's diagnostic options and replace any existing one ...
Helper class for holding the data necessary to invoke the compiler.
PreprocessorOptions & getPreprocessorOpts()
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.
bool DependencyScanningModuleMapImports
When enabled, we don't try to load the corresponding module required by the module map.
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.
std::unique_ptr< DiagnosticOptions > createScanningDiagOptions(ArrayRef< std::string > CommandLine)
llvm::StringRef getDriverMode(StringRef ProgName, ArrayRef< const char * > Args)
Returns the driver mode option's value, i.e.
Definition Driver.cpp:6561
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:6578
bool IsClangCL(StringRef DriverMode)
Checks whether the value produced by getDriverMode is for CL mode.
Definition Driver.cpp:6576
@ 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
Top level wrappers for InstallAPI frontend operations.
@ Worker
'worker' clause, allowed on 'loop', Combined, and 'routine' directives.
A command-line tool invocation that is part of building a TU.
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.