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.
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
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"
34 using clang::tooling::CommonOptionsParser
;
41 // Set of preprocessor callbacks used to record files included.
42 class IncludeFinderPPCallbacks
: public clang::PPCallbacks
{
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
,
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
;
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_
;
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_
);
87 source_manager_
->getFileEntryForID(source_manager_
->getMainFileID())
90 } else if (reason
== ExitFile
) {
93 // Other reasons are not interesting for us.
96 void IncludeFinderPPCallbacks::AddFile(const string
& path
) {
97 source_file_paths_
->insert(path
);
100 void IncludeFinderPPCallbacks::InclusionDirective(
101 clang::SourceLocation hash_loc
,
102 const clang::Token
& include_tok
,
103 llvm::StringRef file_name
,
105 clang::CharSourceRange range
,
106 const clang::FileEntry
* file
,
107 llvm::StringRef search_path
,
108 llvm::StringRef relative_path
,
109 const clang::Module
* imported
) {
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())
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
) {
129 llvm::sys::path::parent_path(current_files_
.top().c_str()).str();
131 // If the file is a top level file ("file.cc"), we normalize to a path
133 if (parent
.empty() || parent
== "/")
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
) ? "//" : "/") +
148 last_inclusion_directive_
= file_name
.str();
150 AddFile(last_inclusion_directive_
);
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());
160 class CompilationIndexerAction
: public clang::PreprocessorFrontendAction
{
162 CompilationIndexerAction() {}
163 void ExecuteAction() override
;
165 // Runs the preprocessor over the translation unit.
166 // This triggers the PPCallbacks we register to intercept all required
167 // files for the compilation.
169 void EndSourceFileAction() override
;
172 // Set up the state extracted during the compilation, and run Clang over the
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_
;
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();
188 void CompilationIndexerAction::Preprocess() {
189 clang::Preprocessor
& preprocessor
= getCompilerInstance().getPreprocessor();
190 preprocessor
.addPPCallbacks(llvm::make_unique
<IncludeFinderPPCallbacks
>(
191 &getCompilerInstance().getSourceManager(),
193 &source_file_paths_
));
194 preprocessor
.getDiagnostics().setIgnoreAllWarnings(true);
195 preprocessor
.SetSuppressIncludeNotFoundError(true);
196 preprocessor
.EnterMainSourceFile();
199 preprocessor
.Lex(token
);
200 } while (token
.isNot(clang::tok::eof
));
203 void CompilationIndexerAction::EndSourceFileAction() {
204 std::ofstream
out(main_source_file_
+ ".filepaths");
205 for (string path
: source_file_paths_
) {
206 out
<< path
<< std::endl
;
211 static llvm::cl::extrahelp
common_help(CommonOptionsParser::HelpMessage
);
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());