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