Skip to content

Commit 4cda28c

Browse files
authored
[clang-include-cleaner] Fix incorrect directory issue for writing files (#111375)
If the current working directory of `clang-include-cleaner` differs from the directory of the input files specified in the compilation database, it doesn't adjust the input file paths properly. As a result, `clang-include-cleaner` either writes files to the wrong directory or fails to write files altogether. This pull request fixes the issue by adjusting the input file paths based on the directory specified in the compilation database. If that directory is not writable, `clang-include-cleaner` will write the output relative to the current working directory. Fixes #110843.
1 parent 9d5cecc commit 4cda28c

File tree

2 files changed

+73
-7
lines changed

2 files changed

+73
-7
lines changed

clang-tools-extra/include-cleaner/test/tool.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,13 @@ int x = foo();
4848
// RUN: clang-include-cleaner -edit --ignore-headers="foobar\.h,foo\.h" %t.cpp -- -I%S/Inputs/
4949
// RUN: FileCheck --match-full-lines --check-prefix=EDIT2 %s < %t.cpp
5050
// EDIT2-NOT: {{^}}#include "foo.h"{{$}}
51+
52+
// RUN: rm -rf %t.dir && mkdir -p %t.dir
53+
// RUN: cp %s %t.cpp
54+
// RUN: echo "[{\"directory\":\"%t.dir\",\"file\":\"../%{t:stem}.tmp.cpp\",\"command\":\":clang++ -I%S/Inputs/ ../%{t:stem}.tmp.cpp\"}]" | sed -e 's/\\/\\\\/g' > %t.dir/compile_commands.json
55+
// RUN: pushd %t.dir
56+
// RUN: clang-include-cleaner -p %{t:stem}.tmp.dir -edit ../%{t:stem}.tmp.cpp
57+
// RUN: popd
58+
// RUN: FileCheck --match-full-lines --check-prefix=EDIT3 %s < %t.cpp
59+
// EDIT3: #include "foo.h"
60+
// EDIT3-NOT: {{^}}#include "foobar.h"{{$}}

clang-tools-extra/include-cleaner/tool/IncludeCleaner.cpp

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,11 @@ class Action : public clang::ASTFrontendAction {
173173
if (!HTMLReportPath.empty())
174174
writeHTML();
175175

176-
llvm::StringRef Path =
177-
SM.getFileEntryRefForID(SM.getMainFileID())->getName();
178-
assert(!Path.empty() && "Main file path not known?");
176+
// Source File's path of compiler invocation, converted to absolute path.
177+
llvm::SmallString<256> AbsPath(
178+
SM.getFileEntryRefForID(SM.getMainFileID())->getName());
179+
assert(!AbsPath.empty() && "Main file path not known?");
180+
SM.getFileManager().makeAbsolutePath(AbsPath);
179181
llvm::StringRef Code = SM.getBufferData(SM.getMainFileID());
180182

181183
auto Results =
@@ -185,7 +187,7 @@ class Action : public clang::ASTFrontendAction {
185187
Results.Missing.clear();
186188
if (!Remove)
187189
Results.Unused.clear();
188-
std::string Final = fixIncludes(Results, Path, Code, getStyle(Path));
190+
std::string Final = fixIncludes(Results, AbsPath, Code, getStyle(AbsPath));
189191

190192
if (Print.getNumOccurrences()) {
191193
switch (Print) {
@@ -202,7 +204,7 @@ class Action : public clang::ASTFrontendAction {
202204
}
203205

204206
if (!Results.Missing.empty() || !Results.Unused.empty())
205-
EditedFiles.try_emplace(Path, Final);
207+
EditedFiles.try_emplace(AbsPath, Final);
206208
}
207209

208210
void writeHTML() {
@@ -280,6 +282,48 @@ std::function<bool(llvm::StringRef)> headerFilter() {
280282
};
281283
}
282284

285+
// Maps absolute path of each files of each compilation commands to the
286+
// absolute path of the input file.
287+
llvm::Expected<std::map<std::string, std::string>>
288+
mapInputsToAbsPaths(clang::tooling::CompilationDatabase &CDB,
289+
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS,
290+
const std::vector<std::string> &Inputs) {
291+
std::map<std::string, std::string> CDBToAbsPaths;
292+
// Factory.editedFiles()` will contain the final code, along with the
293+
// path given in the compilation database. That path can be
294+
// absolute or relative, and if it is relative, it is relative to the
295+
// "Directory" field in the compilation database. We need to make it
296+
// absolute to write the final code to the correct path.
297+
for (auto &Source : Inputs) {
298+
llvm::SmallString<256> AbsPath(Source);
299+
if (auto Err = VFS->makeAbsolute(AbsPath)) {
300+
llvm::errs() << "Failed to get absolute path for " << Source << " : "
301+
<< Err.message() << '\n';
302+
return std::move(llvm::errorCodeToError(Err));
303+
}
304+
std::vector<clang::tooling::CompileCommand> Cmds =
305+
CDB.getCompileCommands(AbsPath);
306+
if (Cmds.empty()) {
307+
// It should be found in the compilation database, even user didn't
308+
// specify the compilation database, the `FixedCompilationDatabase` will
309+
// create an entry from the arguments. So it is an error if we can't
310+
// find the compile commands.
311+
std::string ErrorMsg =
312+
llvm::formatv("No compile commands found for {0}", AbsPath).str();
313+
llvm::errs() << ErrorMsg << '\n';
314+
return llvm::make_error<llvm::StringError>(
315+
ErrorMsg, llvm::inconvertibleErrorCode());
316+
}
317+
for (const auto &Cmd : Cmds) {
318+
llvm::SmallString<256> CDBPath(Cmd.Filename);
319+
std::string Directory(Cmd.Directory);
320+
llvm::sys::fs::make_absolute(Cmd.Directory, CDBPath);
321+
CDBToAbsPaths[std::string(CDBPath)] = std::string(AbsPath);
322+
}
323+
}
324+
return CDBToAbsPaths;
325+
}
326+
283327
} // namespace
284328
} // namespace include_cleaner
285329
} // namespace clang
@@ -305,8 +349,16 @@ int main(int argc, const char **argv) {
305349
}
306350
}
307351

308-
clang::tooling::ClangTool Tool(OptionsParser->getCompilations(),
309-
OptionsParser->getSourcePathList());
352+
auto VFS = llvm::vfs::getRealFileSystem();
353+
auto &CDB = OptionsParser->getCompilations();
354+
// CDBToAbsPaths is a map from the path in the compilation database to the
355+
// writable absolute path of the file.
356+
auto CDBToAbsPaths =
357+
mapInputsToAbsPaths(CDB, VFS, OptionsParser->getSourcePathList());
358+
if (!CDBToAbsPaths)
359+
return 1;
360+
361+
clang::tooling::ClangTool Tool(CDB, OptionsParser->getSourcePathList());
310362

311363
auto HeaderFilter = headerFilter();
312364
if (!HeaderFilter)
@@ -316,6 +368,10 @@ int main(int argc, const char **argv) {
316368
if (Edit) {
317369
for (const auto &NameAndContent : Factory.editedFiles()) {
318370
llvm::StringRef FileName = NameAndContent.first();
371+
if (auto It = CDBToAbsPaths->find(FileName.str());
372+
It != CDBToAbsPaths->end())
373+
FileName = It->second;
374+
319375
const std::string &FinalCode = NameAndContent.second;
320376
if (auto Err = llvm::writeToOutput(
321377
FileName, [&](llvm::raw_ostream &OS) -> llvm::Error {

0 commit comments

Comments
 (0)