Skip to content
73 changes: 72 additions & 1 deletion clang-tools-extra/include-cleaner/tool/IncludeCleaner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ class Action : public clang::ASTFrontendAction {
if (!HTMLReportPath.empty())
writeHTML();

// Source File's path relative to compilation database.
llvm::StringRef Path =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we keep reporting with abs-paths here, i.e:

llvm::SmallString<256> AbsPath(SM.getFileEntryRefForID(SM.getMainFileID())->getName());
SM.getFileManager().makeAbsolutePath(AbsPath);
...

This way we can recognize these paths in main and perform the mapping accordingly.

SM.getFileEntryRefForID(SM.getMainFileID())->getName();
assert(!Path.empty() && "Main file path not known?");
Expand Down Expand Up @@ -280,6 +281,42 @@ std::function<bool(llvm::StringRef)> headerFilter() {
};
}

llvm::ErrorOr<std::string> getCurrentWorkingDirectory() {
llvm::SmallString<256> CurrentPath;
if (const std::error_code EC = llvm::sys::fs::current_path(CurrentPath))
return llvm::ErrorOr<std::string>(EC);
return std::string(CurrentPath.str());
}

std::optional<std::string>
adjustCompilationPath(const std::string &Filename, const std::string &Directory,
const std::string &CurrentWorkingDirectory) {
if (llvm::sys::path::is_absolute(Filename)) {
// If the file path is already absolute, use it as is.
return Filename;
}

auto Sept = llvm::sys::path::get_separator().str();
// First, try to find the file based on the compilation database.
std::string FilePath = Directory + Sept + Filename;
// Check if it is writable.
if (llvm::sys::fs::access(FilePath, llvm::sys::fs::AccessMode::Write) !=
std::error_code()) {
// 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 =
static_cast<std::string>(CurrentWorkingDirectory) + Sept + Filename;
if (llvm::sys::fs::access(FilePath, llvm::sys::fs::AccessMode::Write) !=
std::error_code()) {
llvm::errs() << "Failed to find a writable path for " << Filename << "\n";
return std::nullopt;
}
}
return FilePath;
}

} // namespace
} // namespace include_cleaner
} // namespace clang
Expand All @@ -305,7 +342,32 @@ int main(int argc, const char **argv) {
}
}

clang::tooling::ClangTool Tool(OptionsParser->getCompilations(),
auto &CompilationDatabase = OptionsParser->getCompilations();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this can be simplified with something like:

auto VFS = llvm::vfs::getRealFileSystem();
auto &CDB = OptionsParser->getCompilations();
std::map<std::string, std::string> CDBToAbsPaths;
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;
  }
  for(auto Cmd : CDB.getCompilecommands(AbsPath)) {
    llvm::SmallString<256> CDBPath(Cmd.Filename);
    llvm::sys::fs::make_absolute(Cmd.Directory, CDBPath);
    CDBToAbsPaths[CDBPath] = AbsPath;
  }
}
....
if (Edit) {
  for (const auto &NameAndContent : Factory.editedFiles()) {
     llvm::StringRef FileName = NameAndContent.first();
     if (auto It = CDBToAbsPaths.find(FileName); It != CDBToAbsPaths.end())
         FileName = It->second;
     ...
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't simplify it much, but I tried to explain my intent as clearly as possible. However, I feel the code is somewhat verbose.

// Mapping of edited file paths to their actual paths.
std::map<std::string, std::string> EditedFilesToActualFiles;
if (Edit) {
Copy link
Member

Choose a reason for hiding this comment

The 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 Edit is on, parsing C++ is way slower than anything we can do here over a couple of filenames.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Removed the check for Edit mode.

std::vector<clang::tooling::CompileCommand> Commands =
CompilationDatabase.getAllCompileCommands();
auto CurrentWorkingDirectory = getCurrentWorkingDirectory();
// if (CurrentWorkingDirectory.get()
if (auto EC = CurrentWorkingDirectory.getError()) {
llvm::errs() << "Failed to get current working directory: "
<< EC.message() << "\n";
return 1;
}

for (const auto &Command : Commands) {
auto AdjustedFilePath = adjustCompilationPath(
Command.Filename, Command.Directory, CurrentWorkingDirectory.get());
if (!AdjustedFilePath.has_value()) {
// We already printed an error message.
return 1;
}
EditedFilesToActualFiles[Command.Filename] = AdjustedFilePath.value();
}
}

clang::tooling::ClangTool Tool(CompilationDatabase,
OptionsParser->getSourcePathList());

auto HeaderFilter = headerFilter();
Expand All @@ -316,6 +378,15 @@ int main(int argc, const char **argv) {
if (Edit) {
for (const auto &NameAndContent : Factory.editedFiles()) {
llvm::StringRef FileName = NameAndContent.first();
auto AdjustedFilePathIter = EditedFilesToActualFiles.find(FileName.str());
if (AdjustedFilePathIter != EditedFilesToActualFiles.end()) {
FileName = AdjustedFilePathIter->second;
} else {
llvm::errs() << "Failed to find the actual path for " << FileName
<< "\n";
++Errors;
}

const std::string &FinalCode = NameAndContent.second;
if (auto Err = llvm::writeToOutput(
FileName, [&](llvm::raw_ostream &OS) -> llvm::Error {
Expand Down
Loading