-
Notifications
You must be signed in to change notification settings - Fork 14.4k
[clang-include-cleaner] Fix incorrect directory issue for writing files #111375
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
23b90bb
eef397c
2409d6f
0c9477d
dec9be8
b39f36e
3a0f02e
c65d828
11f78a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -173,9 +173,11 @@ class Action : public clang::ASTFrontendAction { | |
if (!HTMLReportPath.empty()) | ||
writeHTML(); | ||
|
||
llvm::StringRef Path = | ||
SM.getFileEntryRefForID(SM.getMainFileID())->getName(); | ||
assert(!Path.empty() && "Main file path not known?"); | ||
// Source File's path of compiler invocation, converted to absolute path. | ||
llvm::SmallString<256> AbsPath( | ||
SM.getFileEntryRefForID(SM.getMainFileID())->getName()); | ||
assert(!AbsPath.empty() && "Main file path not known?"); | ||
SM.getFileManager().makeAbsolutePath(AbsPath); | ||
llvm::StringRef Code = SM.getBufferData(SM.getMainFileID()); | ||
|
||
auto Results = | ||
|
@@ -185,7 +187,7 @@ class Action : public clang::ASTFrontendAction { | |
Results.Missing.clear(); | ||
if (!Remove) | ||
Results.Unused.clear(); | ||
std::string Final = fixIncludes(Results, Path, Code, getStyle(Path)); | ||
std::string Final = fixIncludes(Results, AbsPath, Code, getStyle(AbsPath)); | ||
|
||
if (Print.getNumOccurrences()) { | ||
switch (Print) { | ||
|
@@ -202,7 +204,7 @@ class Action : public clang::ASTFrontendAction { | |
} | ||
|
||
if (!Results.Missing.empty() || !Results.Unused.empty()) | ||
EditedFiles.try_emplace(Path, Final); | ||
EditedFiles.try_emplace(AbsPath, Final); | ||
} | ||
|
||
void writeHTML() { | ||
|
@@ -305,8 +307,84 @@ int main(int argc, const char **argv) { | |
} | ||
} | ||
|
||
clang::tooling::ClangTool Tool(OptionsParser->getCompilations(), | ||
OptionsParser->getSourcePathList()); | ||
auto VFS = llvm::vfs::getRealFileSystem(); | ||
auto &CDB = OptionsParser->getCompilations(); | ||
// CDBToAbsPaths is a map from the path in the compilation database to the | ||
// writable absolute path of the file. | ||
std::map<std::string, std::string> CDBToAbsPaths; | ||
if (Edit) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I don't think it saves much to do this only when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. Removed the check for Edit mode. |
||
// if Edit is enabled, `Factory.editedFiles()` will contain the final code, | ||
// along with the path given in the compilation database. That path can be | ||
// absolute or relative, and if it is relative, it is relative to the | ||
// "Directory" field in the compilation database. We need to make it | ||
// absolute to write the final code to the correct path. | ||
// There are several cases to consider: | ||
// 1. The "Directory" field isn't same as the current working directory. | ||
// 2. The file path resolved from the "Directory" field is not writable. | ||
// For these cases, we need to find a writable path for the file. | ||
// To effectively handle these cases, we only need to consider | ||
// the files from `getSourcePathList()` that are present in the compilation | ||
// database. | ||
for (auto &Source : OptionsParser->getSourcePathList()) { | ||
llvm::SmallString<256> AbsPath(Source); | ||
if (auto Err = VFS->makeAbsolute(AbsPath)) { | ||
llvm::errs() << "Failed to get absolute path for " << Source << " : " | ||
<< Err.message() << '\n'; | ||
return 1; | ||
} | ||
std::vector<clang::tooling::CompileCommand> Cmds = | ||
CDB.getCompileCommands(AbsPath); | ||
if (Cmds.empty()) { | ||
// Try with the original path. | ||
Cmds = CDB.getCompileCommands(Source); | ||
if (Cmds.empty()) { | ||
continue; | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this fallback isn't necessary, clang-invocation underneath will also use the AbsPath as-is, and it'll skip the file if it couldn't find any compile flags for it. I think it's better to just fail early in this case, similar to above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. I checked, and even if we don't specify a compilation database, clang will create one using the |
||
// We only need the first command to get the directory. | ||
auto Cmd = Cmds[0]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. again, underlying clang-invocation will run the compiler for every compile command we received. hence it isn't enough to just do this for the first command, as each command can have a different There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
llvm::SmallString<256> CDBPath(Cmd.Filename); | ||
std::string Directory(Cmd.Directory); | ||
|
||
if (llvm::sys::path::is_absolute(CDBPath)) { | ||
// If the path in the compilation database is already absolute, we don't | ||
// need to do anything. | ||
CDBToAbsPaths[static_cast<std::string>(CDBPath)] = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we prefer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
static_cast<std::string>(AbsPath); | ||
} else { | ||
auto Sept = llvm::sys::path::get_separator(); | ||
// First, try to find the file based on the compilation database. | ||
llvm::Twine FilePathTwine = Directory + Sept + CDBPath; | ||
llvm::SmallString<256> FilePath; | ||
FilePathTwine.toVector(FilePath); | ||
// Check if it is writable. | ||
if (llvm::sys::fs::access(FilePath, llvm::sys::fs::AccessMode::Write) != | ||
std::error_code()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i don't see why we need to complicate the logic by checking for this. is there any reasons to not always map to file-path relative to current process? i am pretty convinced that the user intention is to almost always edit the file path as referred to during invocation, e.g. i can see how this might also work, but I prefer to maintain code that has as few special cases as possible. so unless something is indeed breaking with this simplification, can you please get rid of this case? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It turns out we don't need to check for this. I removed those checks. |
||
// If not, try to find the file based on the current working | ||
// directory, as some Bazel invocations may not set the compilation | ||
// invocation's filesystem as non-writable. In such cases, we can | ||
// find the file based on the current working directory. | ||
FilePath = Source; | ||
if (auto EC = VFS->makeAbsolute(CDBPath)) { | ||
llvm::errs() << "Failed to get absolute path for " << CDBPath | ||
<< " : " << EC.message() << '\n'; | ||
return 1; | ||
} | ||
if (llvm::sys::fs::access(FilePath, | ||
llvm::sys::fs::AccessMode::Write) != | ||
std::error_code()) { | ||
llvm::errs() << "Failed to find a writable path for " << Source | ||
<< '\n'; | ||
return 1; | ||
} | ||
} | ||
CDBToAbsPaths[static_cast<std::string>(CDBPath)] = | ||
static_cast<std::string>(FilePath); | ||
} | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
||
clang::tooling::ClangTool Tool(CDB, OptionsParser->getSourcePathList()); | ||
|
||
auto HeaderFilter = headerFilter(); | ||
if (!HeaderFilter) | ||
|
@@ -316,6 +394,10 @@ int main(int argc, const char **argv) { | |
if (Edit) { | ||
for (const auto &NameAndContent : Factory.editedFiles()) { | ||
llvm::StringRef FileName = NameAndContent.first(); | ||
if (auto It = CDBToAbsPaths.find(FileName.str()); | ||
It != CDBToAbsPaths.end()) | ||
FileName = It->second; | ||
|
||
const std::string &FinalCode = NameAndContent.second; | ||
if (auto Err = llvm::writeToOutput( | ||
FileName, [&](llvm::raw_ostream &OS) -> llvm::Error { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can use
%T
instead ofdirname %t
, you can find full list in https://llvm.org/docs/CommandGuide/lit.html#substitutions, same below.also it's safer to run a
rm -rf %T
beforehand to make sure we're starting cleanThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It turns out
%T
is also deprecated. I replaced my shell invocation with%t.dir
, as this is the recommended method, as described in https://reviews.llvm.org/D69572. I also slightly modified the test invocation so that it works on Windows as well. (Tested on a local Windows machine.)