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 |