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