OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 // |
| 5 // This implements a Clang tool to generate compilation information that is |
| 6 // sufficient to recompile the code with clang. For each compilation unit, all |
| 7 // source files which are necessary for compiling it are determined. For each |
| 8 // compilation unit, a file is created containing a list of all file paths of |
| 9 // included files. |
| 10 |
| 11 #include <assert.h> |
| 12 #include <unistd.h> |
| 13 #include <fstream> |
| 14 #include <iostream> |
| 15 #include <memory> |
| 16 #include <set> |
| 17 #include <stack> |
| 18 #include <string> |
| 19 #include <vector> |
| 20 |
| 21 #include "clang/Basic/FileManager.h" |
| 22 #include "clang/Basic/SourceManager.h" |
| 23 #include "clang/Frontend/CompilerInstance.h" |
| 24 #include "clang/Frontend/FrontendActions.h" |
| 25 #include "clang/Lex/PPCallbacks.h" |
| 26 #include "clang/Lex/Preprocessor.h" |
| 27 #include "clang/Tooling/CommonOptionsParser.h" |
| 28 #include "clang/Tooling/CompilationDatabase.h" |
| 29 #include "clang/Tooling/Refactoring.h" |
| 30 #include "clang/Tooling/Tooling.h" |
| 31 #include "llvm/Support/CommandLine.h" |
| 32 |
| 33 using clang::tooling::CommonOptionsParser; |
| 34 using std::set; |
| 35 using std::stack; |
| 36 using std::string; |
| 37 using std::vector; |
| 38 |
| 39 namespace { |
| 40 // Set of preprocessor callbacks used to record files included. |
| 41 class IncludeFinderPPCallbacks : public clang::PPCallbacks { |
| 42 public: |
| 43 IncludeFinderPPCallbacks(clang::SourceManager* source_manager, |
| 44 string* main_source_file, |
| 45 set<string>* source_file_paths) |
| 46 : source_manager_(source_manager), |
| 47 main_source_file_(main_source_file), |
| 48 source_file_paths_(source_file_paths) {} |
| 49 void FileChanged(clang::SourceLocation /*loc*/, |
| 50 clang::PPCallbacks::FileChangeReason reason, |
| 51 clang::SrcMgr::CharacteristicKind /*file_type*/, |
| 52 clang::FileID /*prev_fid*/) override; |
| 53 void AddFile(const string& path); |
| 54 void InclusionDirective(clang::SourceLocation hash_loc, |
| 55 const clang::Token& include_tok, |
| 56 llvm::StringRef file_name, |
| 57 bool is_angled, |
| 58 clang::CharSourceRange range, |
| 59 const clang::FileEntry* file, |
| 60 llvm::StringRef search_path, |
| 61 llvm::StringRef relative_path, |
| 62 const clang::Module* imported) override; |
| 63 void EndOfMainFile() override; |
| 64 |
| 65 private: |
| 66 clang::SourceManager* const source_manager_; |
| 67 string* const main_source_file_; |
| 68 set<string>* const source_file_paths_; |
| 69 // The path of the file that was last referenced by an inclusion directive, |
| 70 // normalized for includes that are relative to a different source file. |
| 71 string last_inclusion_directive_; |
| 72 // The stack of currently parsed files. top() gives the current file. |
| 73 stack<string> current_files_; |
| 74 }; |
| 75 |
| 76 void IncludeFinderPPCallbacks::FileChanged( |
| 77 clang::SourceLocation /*loc*/, |
| 78 clang::PPCallbacks::FileChangeReason reason, |
| 79 clang::SrcMgr::CharacteristicKind /*file_type*/, |
| 80 clang::FileID /*prev_fid*/) { |
| 81 if (reason == clang::PPCallbacks::EnterFile) { |
| 82 if (!last_inclusion_directive_.empty()) { |
| 83 current_files_.push(last_inclusion_directive_); |
| 84 } else { |
| 85 current_files_.push( |
| 86 source_manager_->getFileEntryForID(source_manager_->getMainFileID()) |
| 87 ->getName()); |
| 88 } |
| 89 } else if (reason == ExitFile) { |
| 90 current_files_.pop(); |
| 91 } |
| 92 // Other reasons are not interesting for us. |
| 93 } |
| 94 |
| 95 void IncludeFinderPPCallbacks::AddFile(const string& path) { |
| 96 source_file_paths_->insert(path); |
| 97 } |
| 98 |
| 99 void IncludeFinderPPCallbacks::InclusionDirective( |
| 100 clang::SourceLocation hash_loc, |
| 101 const clang::Token& include_tok, |
| 102 llvm::StringRef file_name, |
| 103 bool is_angled, |
| 104 clang::CharSourceRange range, |
| 105 const clang::FileEntry* file, |
| 106 llvm::StringRef search_path, |
| 107 llvm::StringRef relative_path, |
| 108 const clang::Module* imported) { |
| 109 if (!file) |
| 110 return; |
| 111 |
| 112 assert(!current_files_.top().empty()); |
| 113 const clang::DirectoryEntry* const search_path_entry = |
| 114 source_manager_->getFileManager().getDirectory(search_path); |
| 115 const clang::DirectoryEntry* const current_file_parent_entry = |
| 116 source_manager_->getFileManager() |
| 117 .getFile(current_files_.top().c_str()) |
| 118 ->getDir(); |
| 119 |
| 120 // If the include file was found relatively to the current file's parent |
| 121 // directory or a search path, we need to normalize it. This is necessary |
| 122 // because llvm internalizes the path by which an inode was first accessed, |
| 123 // and always returns that path afterwards. If we do not normalize this |
| 124 // we will get an error when we replay the compilation, as the virtual |
| 125 // file system is not aware of inodes. |
| 126 if (search_path_entry == current_file_parent_entry) { |
| 127 string parent = |
| 128 llvm::sys::path::parent_path(current_files_.top().c_str()).str(); |
| 129 |
| 130 // If the file is a top level file ("file.cc"), we normalize to a path |
| 131 // relative to "./". |
| 132 if (parent.empty() || parent == "/") |
| 133 parent = "."; |
| 134 |
| 135 // Otherwise we take the literal path as we stored it for the current |
| 136 // file, and append the relative path. |
| 137 last_inclusion_directive_ = parent + "/" + relative_path.str(); |
| 138 } else if (!search_path.empty()) { |
| 139 last_inclusion_directive_ = string(search_path) + "/" + relative_path.str(); |
| 140 } else { |
| 141 last_inclusion_directive_ = file_name.str(); |
| 142 } |
| 143 AddFile(last_inclusion_directive_); |
| 144 } |
| 145 |
| 146 void IncludeFinderPPCallbacks::EndOfMainFile() { |
| 147 const clang::FileEntry* main_file = |
| 148 source_manager_->getFileEntryForID(source_manager_->getMainFileID()); |
| 149 assert(*main_source_file_ == main_file->getName()); |
| 150 AddFile(main_file->getName()); |
| 151 } |
| 152 |
| 153 class CompilationIndexerAction : public clang::PreprocessorFrontendAction { |
| 154 public: |
| 155 CompilationIndexerAction() {} |
| 156 void ExecuteAction() override; |
| 157 |
| 158 // Runs the preprocessor over the translation unit. |
| 159 // This triggers the PPCallbacks we register to intercept all required |
| 160 // files for the compilation. |
| 161 void Preprocess(); |
| 162 void EndSourceFileAction() override; |
| 163 |
| 164 private: |
| 165 // Set up the state extracted during the compilation, and run Clang over the |
| 166 // input. |
| 167 string main_source_file_; |
| 168 // Maps file names to their contents as read by Clang's source manager. |
| 169 set<string> source_file_paths_; |
| 170 }; |
| 171 |
| 172 void CompilationIndexerAction::ExecuteAction() { |
| 173 vector<clang::FrontendInputFile> inputs = |
| 174 getCompilerInstance().getFrontendOpts().Inputs; |
| 175 assert(inputs.size() == 1); |
| 176 main_source_file_ = inputs[0].getFile(); |
| 177 |
| 178 Preprocess(); |
| 179 } |
| 180 |
| 181 void CompilationIndexerAction::Preprocess() { |
| 182 clang::Preprocessor& preprocessor = getCompilerInstance().getPreprocessor(); |
| 183 preprocessor.addPPCallbacks(llvm::make_unique<IncludeFinderPPCallbacks>( |
| 184 &getCompilerInstance().getSourceManager(), |
| 185 &main_source_file_, |
| 186 &source_file_paths_)); |
| 187 preprocessor.IgnorePragmas(); |
| 188 preprocessor.SetSuppressIncludeNotFoundError(true); |
| 189 preprocessor.EnterMainSourceFile(); |
| 190 clang::Token token; |
| 191 do { |
| 192 preprocessor.Lex(token); |
| 193 } while (token.isNot(clang::tok::eof)); |
| 194 } |
| 195 |
| 196 void CompilationIndexerAction::EndSourceFileAction() { |
| 197 std::ofstream out(main_source_file_ + ".filepaths"); |
| 198 for (string path : source_file_paths_) { |
| 199 out << path << std::endl; |
| 200 } |
| 201 } |
| 202 } // namespace |
| 203 |
| 204 static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage); |
| 205 |
| 206 int main(int argc, const char* argv[]) { |
| 207 llvm::cl::OptionCategory category("TranslationUnitGenerator Tool"); |
| 208 CommonOptionsParser options(argc, argv, category); |
| 209 std::unique_ptr<clang::tooling::FrontendActionFactory> frontend_factory = |
| 210 clang::tooling::newFrontendActionFactory<CompilationIndexerAction>(); |
| 211 clang::tooling::ClangTool tool(options.getCompilations(), |
| 212 options.getSourcePathList()); |
| 213 // This clang tool does not actually produce edits, but run_tool.py expects |
| 214 // this. So we just print an empty edit block. |
| 215 llvm::outs() << "==== BEGIN EDITS ====\n"; |
| 216 llvm::outs() << "==== END EDITS ====\n"; |
| 217 return tool.run(frontend_factory.get()); |
| 218 } |
OLD | NEW |