1//===- DependencyScanningWorker.cpp - Thread-Safe Scanning Worker ---------===//
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/DependencyScanning/DependencyScanningWorker.h"
10#include "clang/Basic/Diagnostic.h"
11#include "clang/Basic/DiagnosticFrontend.h"
12#include "clang/DependencyScanning/DependencyScannerImpl.h"
13#include "clang/Driver/Driver.h"
14#include "clang/Driver/Tool.h"
15#include "clang/Serialization/ObjectFilePCHContainerReader.h"
16#include "llvm/ADT/IntrusiveRefCntPtr.h"
17#include "llvm/Support/VirtualFileSystem.h"
18
19using namespace clang;
20using namespace dependencies;
21
22DependencyScanningWorker::DependencyScanningWorker(
23 DependencyScanningService &Service)
24 : Service(Service) {
25 PCHContainerOps = std::make_shared<PCHContainerOperations>();
26 // We need to read object files from PCH built outside the scanner.
27 PCHContainerOps->registerReader(
28 Reader: std::make_unique<ObjectFilePCHContainerReader>());
29 // The scanner itself writes only raw ast files.
30 PCHContainerOps->registerWriter(Writer: std::make_unique<RawPCHContainerWriter>());
31
32 auto BaseFS = Service.getOpts().MakeVFS();
33
34 if (Service.getOpts().TraceVFS)
35 BaseFS = llvm::makeIntrusiveRefCnt<llvm::vfs::TracingFileSystem>(
36 A: std::move(BaseFS));
37
38 DepFS = llvm::makeIntrusiveRefCnt<DependencyScanningWorkerFilesystem>(
39 A&: Service.getSharedCache(), A: std::move(BaseFS));
40}
41
42DependencyScanningWorker::~DependencyScanningWorker() = default;
43DependencyActionController::~DependencyActionController() = default;
44
45static bool createAndRunToolInvocation(
46 ArrayRef<std::string> CommandLine, DependencyScanningAction &Action,
47 IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
48 std::shared_ptr<clang::PCHContainerOperations> &PCHContainerOps,
49 DiagnosticsEngine &Diags) {
50 auto Invocation = createCompilerInvocation(CommandLine, Diags);
51 if (!Invocation)
52 return false;
53
54 return Action.runInvocation(Executable: CommandLine[0], Invocation: std::move(Invocation),
55 FS: std::move(FS), PCHContainerOps,
56 DiagConsumer: Diags.getClient());
57}
58
59bool DependencyScanningWorker::computeDependencies(
60 StringRef WorkingDirectory, ArrayRef<std::string> CommandLine,
61 DependencyConsumer &DepConsumer, DependencyActionController &Controller,
62 DiagnosticConsumer &DiagConsumer,
63 llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS) {
64 return computeDependencies(WorkingDirectory,
65 CommandLines: ArrayRef<ArrayRef<std::string>>(CommandLine),
66 DepConsumer, Controller, DiagConsumer, OverlayFS);
67}
68
69bool DependencyScanningWorker::computeDependencies(
70 StringRef WorkingDirectory, ArrayRef<ArrayRef<std::string>> CommandLines,
71 DependencyConsumer &DepConsumer, DependencyActionController &Controller,
72 DiagnosticConsumer &DiagConsumer,
73 llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS) {
74 IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS = nullptr;
75 if (OverlayFS) {
76#ifndef NDEBUG
77 bool SawDepFS = false;
78 OverlayFS->visit(
79 [&](llvm::vfs::FileSystem &VFS) { SawDepFS |= &VFS == DepFS.get(); });
80 assert(SawDepFS && "OverlayFS not based on DepFS");
81#endif
82 FS = std::move(OverlayFS);
83 } else {
84 FS = DepFS;
85 FS->setCurrentWorkingDirectory(WorkingDirectory);
86 }
87
88 DependencyScanningAction Action(Service, WorkingDirectory, DepConsumer,
89 Controller, DepFS);
90
91 const bool Success = llvm::all_of(Range&: CommandLines, P: [&](const auto &Cmd) {
92 if (StringRef(Cmd[1]) != "-cc1") {
93 // Non-clang command. Just pass through to the dependency consumer.
94 DepConsumer.handleBuildCommand(
95 Cmd: {Cmd.front(), {Cmd.begin() + 1, Cmd.end()}});
96 return true;
97 }
98
99 auto DiagEngineWithDiagOpts =
100 DiagnosticsEngineWithDiagOpts(Cmd, FS, DiagConsumer);
101 auto &Diags = *DiagEngineWithDiagOpts.DiagEngine;
102
103 // Create an invocation that uses the underlying file system to ensure that
104 // any file system requests that are made by the driver do not go through
105 // the dependency scanning filesystem.
106 return createAndRunToolInvocation(Cmd, Action, FS, PCHContainerOps, Diags);
107 });
108
109 // Ensure finish() is called even if we never reached ExecuteAction().
110 if (!Action.hasDiagConsumerFinished())
111 DiagConsumer.finish();
112
113 return Success && Action.hasScanned();
114}
115
116bool DependencyScanningWorker::initializeCompilerInstanceWithContext(
117 StringRef CWD, ArrayRef<std::string> CommandLine, DiagnosticConsumer &DC) {
118 auto [OverlayFS, ModifiedCommandLine] =
119 initVFSForByNameScanning(BaseFS: DepFS, CommandLine, WorkingDirectory: CWD, ModuleName: "ScanningByName");
120 auto DiagEngineWithCmdAndOpts =
121 std::make_unique<DiagnosticsEngineWithDiagOpts>(args&: ModifiedCommandLine,
122 args&: OverlayFS, args&: DC);
123 return initializeCompilerInstanceWithContext(
124 CWD, CommandLine: ModifiedCommandLine, DiagEngineWithCmdAndOpts: std::move(DiagEngineWithCmdAndOpts), OverlayFS);
125}
126
127bool DependencyScanningWorker::initializeCompilerInstanceWithContext(
128 StringRef CWD, ArrayRef<std::string> CommandLine,
129 std::unique_ptr<DiagnosticsEngineWithDiagOpts> DiagEngineWithDiagOpts,
130 IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS) {
131 CIWithContext =
132 std::make_unique<CompilerInstanceWithContext>(args&: *this, args&: CWD, args&: CommandLine);
133 return CIWithContext->initialize(DiagEngineWithDiagOpts: std::move(DiagEngineWithDiagOpts),
134 OverlayFS);
135}
136
137bool DependencyScanningWorker::computeDependenciesByNameWithContext(
138 StringRef ModuleName, DependencyConsumer &Consumer,
139 DependencyActionController &Controller) {
140 assert(CIWithContext && "CompilerInstance with context required!");
141 return CIWithContext->computeDependencies(ModuleName, Consumer, Controller);
142}
143
144bool DependencyScanningWorker::finalizeCompilerInstanceWithContext() {
145 return CIWithContext->finalize();
146}
147
148std::pair<IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem>,
149 std::vector<std::string>>
150dependencies::initVFSForTUBufferScanning(
151 IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS,
152 ArrayRef<std::string> CommandLine, StringRef WorkingDirectory,
153 llvm::MemoryBufferRef TUBuffer) {
154 // Reset what might have been modified in the previous worker invocation.
155 BaseFS->setCurrentWorkingDirectory(WorkingDirectory);
156
157 auto OverlayFS =
158 llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(A&: BaseFS);
159 auto InMemoryFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
160 InMemoryFS->setCurrentWorkingDirectory(WorkingDirectory);
161 auto InputPath = TUBuffer.getBufferIdentifier();
162 InMemoryFS->addFile(
163 Path: InputPath, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: TUBuffer.getBuffer()));
164 IntrusiveRefCntPtr<llvm::vfs::FileSystem> InMemoryOverlay = InMemoryFS;
165
166 OverlayFS->pushOverlay(FS: InMemoryOverlay);
167 std::vector<std::string> ModifiedCommandLine(CommandLine);
168 ModifiedCommandLine.emplace_back(args&: InputPath);
169
170 return std::make_pair(x&: OverlayFS, y&: ModifiedCommandLine);
171}
172
173std::pair<IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem>,
174 std::vector<std::string>>
175dependencies::initVFSForByNameScanning(
176 IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS,
177 ArrayRef<std::string> CommandLine, StringRef WorkingDirectory,
178 StringRef ModuleName) {
179 // Reset what might have been modified in the previous worker invocation.
180 BaseFS->setCurrentWorkingDirectory(WorkingDirectory);
181
182 // If we're scanning based on a module name alone, we don't expect the client
183 // to provide us with an input file. However, the driver really wants to have
184 // one. Let's just make it up to make the driver happy.
185 auto OverlayFS =
186 llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(A&: BaseFS);
187 auto InMemoryFS = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
188 InMemoryFS->setCurrentWorkingDirectory(WorkingDirectory);
189 SmallString<128> FakeInputPath;
190 // TODO: We should retry the creation if the path already exists.
191 llvm::sys::fs::createUniquePath(Model: ModuleName + "-%%%%%%%%.input", ResultPath&: FakeInputPath,
192 /*MakeAbsolute=*/false);
193 InMemoryFS->addFile(Path: FakeInputPath, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: ""));
194 IntrusiveRefCntPtr<llvm::vfs::FileSystem> InMemoryOverlay = InMemoryFS;
195 OverlayFS->pushOverlay(FS: InMemoryOverlay);
196
197 std::vector<std::string> ModifiedCommandLine(CommandLine);
198 ModifiedCommandLine.emplace_back(args&: FakeInputPath);
199
200 return std::make_pair(x&: OverlayFS, y&: ModifiedCommandLine);
201}
202