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