Index: tools/clang/translation_unit/TranslationUnitGenerator.cpp |
diff --git a/tools/clang/translation_unit/TranslationUnitGenerator.cpp b/tools/clang/translation_unit/TranslationUnitGenerator.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..df9eaebb7cb08b280786e0aa784b95cdef245018 |
--- /dev/null |
+++ b/tools/clang/translation_unit/TranslationUnitGenerator.cpp |
@@ -0,0 +1,218 @@ |
+// Copyright (c) 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+// |
+// This implements a Clang tool to generate compilation information that is |
+// sufficient to recompile the code with clang. For each compilation unit, all |
+// source files which are necessary for compiling it are determined. For each |
+// compilation unit, a file is created containing a list of all file paths of |
+// included files. |
+ |
+#include <assert.h> |
+#include <unistd.h> |
+#include <fstream> |
+#include <iostream> |
+#include <memory> |
+#include <set> |
+#include <stack> |
+#include <string> |
+#include <vector> |
+ |
+#include "clang/Basic/FileManager.h" |
+#include "clang/Basic/SourceManager.h" |
+#include "clang/Frontend/CompilerInstance.h" |
+#include "clang/Frontend/FrontendActions.h" |
+#include "clang/Lex/PPCallbacks.h" |
+#include "clang/Lex/Preprocessor.h" |
+#include "clang/Tooling/CommonOptionsParser.h" |
+#include "clang/Tooling/CompilationDatabase.h" |
+#include "clang/Tooling/Refactoring.h" |
+#include "clang/Tooling/Tooling.h" |
+#include "llvm/Support/CommandLine.h" |
+ |
+using clang::tooling::CommonOptionsParser; |
+using std::set; |
+using std::stack; |
+using std::string; |
+using std::vector; |
+ |
+namespace { |
+// Set of preprocessor callbacks used to record files included. |
+class IncludeFinderPPCallbacks : public clang::PPCallbacks { |
+ public: |
+ IncludeFinderPPCallbacks(clang::SourceManager* source_manager, |
+ string* main_source_file, |
+ set<string>* source_file_paths) |
+ : source_manager_(source_manager), |
+ main_source_file_(main_source_file), |
+ source_file_paths_(source_file_paths) {} |
+ void FileChanged(clang::SourceLocation /*loc*/, |
+ clang::PPCallbacks::FileChangeReason reason, |
+ clang::SrcMgr::CharacteristicKind /*file_type*/, |
+ clang::FileID /*prev_fid*/) override; |
+ void AddFile(const string& path); |
+ void InclusionDirective(clang::SourceLocation hash_loc, |
+ const clang::Token& include_tok, |
+ llvm::StringRef file_name, |
+ bool is_angled, |
+ clang::CharSourceRange range, |
+ const clang::FileEntry* file, |
+ llvm::StringRef search_path, |
+ llvm::StringRef relative_path, |
+ const clang::Module* imported) override; |
+ void EndOfMainFile() override; |
+ |
+ private: |
+ clang::SourceManager* const source_manager_; |
+ string* const main_source_file_; |
+ set<string>* const source_file_paths_; |
+ // The path of the file that was last referenced by an inclusion directive, |
+ // normalized for includes that are relative to a different source file. |
+ string last_inclusion_directive_; |
+ // The stack of currently parsed files. top() gives the current file. |
+ stack<string> current_files_; |
+}; |
+ |
+void IncludeFinderPPCallbacks::FileChanged( |
+ clang::SourceLocation /*loc*/, |
+ clang::PPCallbacks::FileChangeReason reason, |
+ clang::SrcMgr::CharacteristicKind /*file_type*/, |
+ clang::FileID /*prev_fid*/) { |
+ if (reason == clang::PPCallbacks::EnterFile) { |
+ if (!last_inclusion_directive_.empty()) { |
+ current_files_.push(last_inclusion_directive_); |
+ } else { |
+ current_files_.push( |
+ source_manager_->getFileEntryForID(source_manager_->getMainFileID()) |
+ ->getName()); |
+ } |
+ } else if (reason == ExitFile) { |
+ current_files_.pop(); |
+ } |
+ // Other reasons are not interesting for us. |
+} |
+ |
+void IncludeFinderPPCallbacks::AddFile(const string& path) { |
+ source_file_paths_->insert(path); |
+} |
+ |
+void IncludeFinderPPCallbacks::InclusionDirective( |
+ clang::SourceLocation hash_loc, |
+ const clang::Token& include_tok, |
+ llvm::StringRef file_name, |
+ bool is_angled, |
+ clang::CharSourceRange range, |
+ const clang::FileEntry* file, |
+ llvm::StringRef search_path, |
+ llvm::StringRef relative_path, |
+ const clang::Module* imported) { |
+ if (!file) |
+ return; |
+ |
+ assert(!current_files_.top().empty()); |
+ const clang::DirectoryEntry* const search_path_entry = |
+ source_manager_->getFileManager().getDirectory(search_path); |
+ const clang::DirectoryEntry* const current_file_parent_entry = |
+ source_manager_->getFileManager() |
+ .getFile(current_files_.top().c_str()) |
+ ->getDir(); |
+ |
+ // If the include file was found relatively to the current file's parent |
+ // directory or a search path, we need to normalize it. This is necessary |
+ // because llvm internalizes the path by which an inode was first accessed, |
+ // and always returns that path afterwards. If we do not normalize this |
+ // we will get an error when we replay the compilation, as the virtual |
+ // file system is not aware of inodes. |
+ if (search_path_entry == current_file_parent_entry) { |
+ string parent = |
+ llvm::sys::path::parent_path(current_files_.top().c_str()).str(); |
+ |
+ // If the file is a top level file ("file.cc"), we normalize to a path |
+ // relative to "./". |
+ if (parent.empty() || parent == "/") |
+ parent = "."; |
+ |
+ // Otherwise we take the literal path as we stored it for the current |
+ // file, and append the relative path. |
+ last_inclusion_directive_ = parent + "/" + relative_path.str(); |
+ } else if (!search_path.empty()) { |
+ last_inclusion_directive_ = string(search_path) + "/" + relative_path.str(); |
+ } else { |
+ last_inclusion_directive_ = file_name.str(); |
+ } |
+ AddFile(last_inclusion_directive_); |
+} |
+ |
+void IncludeFinderPPCallbacks::EndOfMainFile() { |
+ const clang::FileEntry* main_file = |
+ source_manager_->getFileEntryForID(source_manager_->getMainFileID()); |
+ assert(*main_source_file_ == main_file->getName()); |
+ AddFile(main_file->getName()); |
+} |
+ |
+class CompilationIndexerAction : public clang::PreprocessorFrontendAction { |
+ public: |
+ CompilationIndexerAction() {} |
+ void ExecuteAction() override; |
+ |
+ // Runs the preprocessor over the translation unit. |
+ // This triggers the PPCallbacks we register to intercept all required |
+ // files for the compilation. |
+ void Preprocess(); |
+ void EndSourceFileAction() override; |
+ |
+ private: |
+ // Set up the state extracted during the compilation, and run Clang over the |
+ // input. |
+ string main_source_file_; |
+ // Maps file names to their contents as read by Clang's source manager. |
+ set<string> source_file_paths_; |
+}; |
+ |
+void CompilationIndexerAction::ExecuteAction() { |
+ vector<clang::FrontendInputFile> inputs = |
+ getCompilerInstance().getFrontendOpts().Inputs; |
+ assert(inputs.size() == 1); |
+ main_source_file_ = inputs[0].getFile(); |
+ |
+ Preprocess(); |
+} |
+ |
+void CompilationIndexerAction::Preprocess() { |
+ clang::Preprocessor& preprocessor = getCompilerInstance().getPreprocessor(); |
+ preprocessor.addPPCallbacks(llvm::make_unique<IncludeFinderPPCallbacks>( |
+ &getCompilerInstance().getSourceManager(), |
+ &main_source_file_, |
+ &source_file_paths_)); |
+ preprocessor.IgnorePragmas(); |
+ preprocessor.SetSuppressIncludeNotFoundError(true); |
+ preprocessor.EnterMainSourceFile(); |
+ clang::Token token; |
+ do { |
+ preprocessor.Lex(token); |
+ } while (token.isNot(clang::tok::eof)); |
+} |
+ |
+void CompilationIndexerAction::EndSourceFileAction() { |
+ std::ofstream out(main_source_file_ + ".filepaths"); |
+ for (string path : source_file_paths_) { |
+ out << path << std::endl; |
+ } |
+} |
+} // namespace |
+ |
+static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage); |
+ |
+int main(int argc, const char* argv[]) { |
+ llvm::cl::OptionCategory category("TranslationUnitGenerator Tool"); |
+ CommonOptionsParser options(argc, argv, category); |
+ std::unique_ptr<clang::tooling::FrontendActionFactory> frontend_factory = |
+ clang::tooling::newFrontendActionFactory<CompilationIndexerAction>(); |
+ clang::tooling::ClangTool tool(options.getCompilations(), |
+ options.getSourcePathList()); |
+ // This clang tool does not actually produce edits, but run_tool.py expects |
+ // this. So we just print an empty edit block. |
+ llvm::outs() << "==== BEGIN EDITS ====\n"; |
+ llvm::outs() << "==== END EDITS ====\n"; |
+ return tool.run(frontend_factory.get()); |
+} |