Chromium Code Reviews| 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 FileChangeReason reason, | |
|
Nico
2014/11/03 18:12:54
clang::FileChangeReason (not really needed because
Adrian Kuegel
2014/11/07 14:44:10
Done.
| |
| 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 FileChangeReason reason, | |
|
Nico
2014/11/03 18:12:54
clang::
Adrian Kuegel
2014/11/07 14:44:10
Done.
| |
| 79 clang::SrcMgr::CharacteristicKind /*file_type*/, | |
| 80 clang::FileID /*prev_fid*/) { | |
| 81 if (reason == EnterFile) { | |
|
Nico
2014/11/03 18:12:54
clang::
Adrian Kuegel
2014/11/07 14:44:10
Done.
| |
| 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 |