1 //===--- FileRemapper.cpp - File Remapping Helper -------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "clang/ARCMigrate/FileRemapper.h"
10 #include "clang/Basic/Diagnostic.h"
11 #include "clang/Basic/FileManager.h"
12 #include "clang/Lex/PreprocessorOptions.h"
13 #include "llvm/Support/FileSystem.h"
14 #include "llvm/Support/MemoryBuffer.h"
15 #include "llvm/Support/Path.h"
16 #include "llvm/Support/raw_ostream.h"
19 using namespace clang
;
20 using namespace arcmt
;
22 FileRemapper::FileRemapper() {
23 FileMgr
.reset(new FileManager(FileSystemOptions()));
26 FileRemapper::~FileRemapper() {
30 void FileRemapper::clear(StringRef outputDir
) {
31 for (MappingsTy::iterator
32 I
= FromToMappings
.begin(), E
= FromToMappings
.end(); I
!= E
; ++I
)
33 resetTarget(I
->second
);
34 FromToMappings
.clear();
35 assert(ToFromMappings
.empty());
36 if (!outputDir
.empty()) {
37 std::string infoFile
= getRemapInfoFile(outputDir
);
38 llvm::sys::fs::remove(infoFile
);
42 std::string
FileRemapper::getRemapInfoFile(StringRef outputDir
) {
43 assert(!outputDir
.empty());
44 SmallString
<128> InfoFile
= outputDir
;
45 llvm::sys::path::append(InfoFile
, "remap");
46 return std::string(InfoFile
);
49 bool FileRemapper::initFromDisk(StringRef outputDir
, DiagnosticsEngine
&Diag
,
50 bool ignoreIfFilesChanged
) {
51 std::string infoFile
= getRemapInfoFile(outputDir
);
52 return initFromFile(infoFile
, Diag
, ignoreIfFilesChanged
);
55 bool FileRemapper::initFromFile(StringRef filePath
, DiagnosticsEngine
&Diag
,
56 bool ignoreIfFilesChanged
) {
57 assert(FromToMappings
.empty() &&
58 "initFromDisk should be called before any remap calls");
59 std::string infoFile
= std::string(filePath
);
60 if (!llvm::sys::fs::exists(infoFile
))
63 std::vector
<std::pair
<FileEntryRef
, FileEntryRef
>> pairs
;
65 llvm::ErrorOr
<std::unique_ptr
<llvm::MemoryBuffer
>> fileBuf
=
66 llvm::MemoryBuffer::getFile(infoFile
, /*IsText=*/true);
68 return report("Error opening file: " + infoFile
, Diag
);
70 SmallVector
<StringRef
, 64> lines
;
71 fileBuf
.get()->getBuffer().split(lines
, "\n");
73 for (unsigned idx
= 0; idx
+3 <= lines
.size(); idx
+= 3) {
74 StringRef fromFilename
= lines
[idx
];
75 unsigned long long timeModified
;
76 if (lines
[idx
+1].getAsInteger(10, timeModified
))
77 return report("Invalid file data: '" + lines
[idx
+1] + "' not a number",
79 StringRef toFilename
= lines
[idx
+2];
81 auto origFE
= FileMgr
->getOptionalFileRef(fromFilename
);
83 if (ignoreIfFilesChanged
)
85 return report("File does not exist: " + fromFilename
, Diag
);
87 auto newFE
= FileMgr
->getOptionalFileRef(toFilename
);
89 if (ignoreIfFilesChanged
)
91 return report("File does not exist: " + toFilename
, Diag
);
94 if ((uint64_t)origFE
->getModificationTime() != timeModified
) {
95 if (ignoreIfFilesChanged
)
97 return report("File was modified: " + fromFilename
, Diag
);
100 pairs
.push_back(std::make_pair(*origFE
, *newFE
));
103 for (unsigned i
= 0, e
= pairs
.size(); i
!= e
; ++i
)
104 remap(pairs
[i
].first
, pairs
[i
].second
);
109 bool FileRemapper::flushToDisk(StringRef outputDir
, DiagnosticsEngine
&Diag
) {
110 using namespace llvm::sys
;
112 if (fs::create_directory(outputDir
))
113 return report("Could not create directory: " + outputDir
, Diag
);
115 std::string infoFile
= getRemapInfoFile(outputDir
);
116 return flushToFile(infoFile
, Diag
);
119 bool FileRemapper::flushToFile(StringRef outputPath
, DiagnosticsEngine
&Diag
) {
120 using namespace llvm::sys
;
123 std::string infoFile
= std::string(outputPath
);
124 llvm::raw_fd_ostream
infoOut(infoFile
, EC
, llvm::sys::fs::OF_Text
);
126 return report(EC
.message(), Diag
);
128 for (MappingsTy::iterator
129 I
= FromToMappings
.begin(), E
= FromToMappings
.end(); I
!= E
; ++I
) {
131 FileEntryRef origFE
= I
->first
;
132 SmallString
<200> origPath
= StringRef(origFE
.getName());
133 fs::make_absolute(origPath
);
134 infoOut
<< origPath
<< '\n';
135 infoOut
<< (uint64_t)origFE
.getModificationTime() << '\n';
137 if (const auto *FE
= std::get_if
<FileEntryRef
>(&I
->second
)) {
138 SmallString
<200> newPath
= StringRef(FE
->getName());
139 fs::make_absolute(newPath
);
140 infoOut
<< newPath
<< '\n';
143 SmallString
<64> tempPath
;
145 if (fs::createTemporaryFile(
146 path::filename(origFE
.getName()),
147 path::extension(origFE
.getName()).drop_front(), fd
, tempPath
,
148 llvm::sys::fs::OF_Text
))
149 return report("Could not create file: " + tempPath
.str(), Diag
);
151 llvm::raw_fd_ostream
newOut(fd
, /*shouldClose=*/true);
152 llvm::MemoryBuffer
*mem
= std::get
<llvm::MemoryBuffer
*>(I
->second
);
153 newOut
.write(mem
->getBufferStart(), mem
->getBufferSize());
156 auto newE
= FileMgr
->getOptionalFileRef(tempPath
);
158 remap(origFE
, *newE
);
159 infoOut
<< newE
->getName() << '\n';
168 bool FileRemapper::overwriteOriginal(DiagnosticsEngine
&Diag
,
169 StringRef outputDir
) {
170 using namespace llvm::sys
;
172 for (MappingsTy::iterator
173 I
= FromToMappings
.begin(), E
= FromToMappings
.end(); I
!= E
; ++I
) {
174 FileEntryRef origFE
= I
->first
;
175 assert(std::holds_alternative
<llvm::MemoryBuffer
*>(I
->second
));
176 if (!fs::exists(origFE
.getName()))
177 return report(StringRef("File does not exist: ") + origFE
.getName(),
181 llvm::raw_fd_ostream
Out(origFE
.getName(), EC
, llvm::sys::fs::OF_None
);
183 return report(EC
.message(), Diag
);
185 llvm::MemoryBuffer
*mem
= std::get
<llvm::MemoryBuffer
*>(I
->second
);
186 Out
.write(mem
->getBufferStart(), mem
->getBufferSize());
194 void FileRemapper::forEachMapping(
195 llvm::function_ref
<void(StringRef
, StringRef
)> CaptureFile
,
196 llvm::function_ref
<void(StringRef
, const llvm::MemoryBufferRef
&)>
197 CaptureBuffer
) const {
198 for (auto &Mapping
: FromToMappings
) {
199 if (const auto *FE
= std::get_if
<FileEntryRef
>(&Mapping
.second
)) {
200 CaptureFile(Mapping
.first
.getName(), FE
->getName());
204 Mapping
.first
.getName(),
205 std::get
<llvm::MemoryBuffer
*>(Mapping
.second
)->getMemBufferRef());
209 void FileRemapper::applyMappings(PreprocessorOptions
&PPOpts
) const {
210 for (MappingsTy::const_iterator
211 I
= FromToMappings
.begin(), E
= FromToMappings
.end(); I
!= E
; ++I
) {
212 if (const auto *FE
= std::get_if
<FileEntryRef
>(&I
->second
)) {
213 PPOpts
.addRemappedFile(I
->first
.getName(), FE
->getName());
215 llvm::MemoryBuffer
*mem
= std::get
<llvm::MemoryBuffer
*>(I
->second
);
216 PPOpts
.addRemappedFile(I
->first
.getName(), mem
);
220 PPOpts
.RetainRemappedFileBuffers
= true;
223 void FileRemapper::remap(StringRef filePath
,
224 std::unique_ptr
<llvm::MemoryBuffer
> memBuf
) {
225 OptionalFileEntryRef File
= getOriginalFile(filePath
);
227 remap(*File
, std::move(memBuf
));
230 void FileRemapper::remap(FileEntryRef File
,
231 std::unique_ptr
<llvm::MemoryBuffer
> MemBuf
) {
232 auto [It
, New
] = FromToMappings
.insert({File
, nullptr});
234 resetTarget(It
->second
);
235 It
->second
= MemBuf
.release();
238 void FileRemapper::remap(FileEntryRef File
, FileEntryRef NewFile
) {
239 auto [It
, New
] = FromToMappings
.insert({File
, nullptr});
241 resetTarget(It
->second
);
242 It
->second
= NewFile
;
243 ToFromMappings
.insert({NewFile
, File
});
246 OptionalFileEntryRef
FileRemapper::getOriginalFile(StringRef filePath
) {
247 OptionalFileEntryRef File
= FileMgr
->getOptionalFileRef(filePath
);
250 // If we are updating a file that overridden an original file,
251 // actually update the original file.
252 auto I
= ToFromMappings
.find(*File
);
253 if (I
!= ToFromMappings
.end()) {
255 assert(FromToMappings
.contains(*File
) && "Original file not in mappings!");
260 void FileRemapper::resetTarget(Target
&targ
) {
261 if (std::holds_alternative
<llvm::MemoryBuffer
*>(targ
)) {
262 llvm::MemoryBuffer
*oldmem
= std::get
<llvm::MemoryBuffer
*>(targ
);
265 FileEntryRef toFE
= std::get
<FileEntryRef
>(targ
);
266 ToFromMappings
.erase(toFE
);
270 bool FileRemapper::report(const Twine
&err
, DiagnosticsEngine
&Diag
) {
271 Diag
.Report(Diag
.getCustomDiagID(DiagnosticsEngine::Error
, "%0"))