1 | /**************************************************************************** |
2 | * Copyright (C) 2012-2016 Woboq GmbH |
3 | * Olivier Goffart <contact at woboq.com> |
4 | * https://woboq.com/codebrowser.html |
5 | * |
6 | * This file is part of the Woboq Code Browser. |
7 | * |
8 | * Commercial License Usage: |
9 | * Licensees holding valid commercial licenses provided by Woboq may use |
10 | * this file in accordance with the terms contained in a written agreement |
11 | * between the licensee and Woboq. |
12 | * For further information see https://woboq.com/codebrowser.html |
13 | * |
14 | * Alternatively, this work may be used under a Creative Commons |
15 | * Attribution-NonCommercial-ShareAlike 3.0 (CC-BY-NC-SA 3.0) License. |
16 | * http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US |
17 | * This license does not allow you to use the code browser to assist the |
18 | * development of your commercial software. If you intent to do so, consider |
19 | * purchasing a commercial licence. |
20 | ****************************************************************************/ |
21 | |
22 | |
23 | #include "clang/AST/ASTContext.h" |
24 | #include "clang/Frontend/FrontendActions.h" |
25 | #include "clang/Tooling/JSONCompilationDatabase.h" |
26 | #include "clang/Tooling/Tooling.h" |
27 | #include "llvm/Support/CommandLine.h" |
28 | |
29 | #include <clang/Frontend/CompilerInstance.h> |
30 | #include <llvm/ADT/StringSwitch.h> |
31 | #include <llvm/Support/Path.h> |
32 | |
33 | #include "annotator.h" |
34 | #include "browserastvisitor.h" |
35 | #include "compat.h" |
36 | #include "filesystem.h" |
37 | #include "preprocessorcallback.h" |
38 | #include "projectmanager.h" |
39 | #include "stringbuilder.h" |
40 | #include <ctime> |
41 | #include <fstream> |
42 | #include <iostream> |
43 | #include <limits> |
44 | #include <stdexcept> |
45 | |
46 | #include "embedded_includes.h" |
47 | |
48 | namespace cl = llvm::cl; |
49 | |
50 | cl::opt<std::string> BuildPath( |
51 | "b" , cl::value_desc("build_path" ), |
52 | cl::desc( |
53 | "Build path containing compilation database (compile_commands.json) If this argument is " |
54 | "not passed, the compilation arguments can be passed on the command line after '--'" ), |
55 | cl::Optional); |
56 | |
57 | cl::list<std::string> SourcePaths(cl::Positional, cl::desc("<sources>* [-- <compile command>]" ), |
58 | cl::ZeroOrMore); |
59 | |
60 | cl::opt<std::string> OutputPath("o" , cl::value_desc("output path" ), |
61 | cl::desc("Output directory where the generated files will be put" ), |
62 | cl::Required); |
63 | |
64 | cl::list<std::string> ProjectPaths( |
65 | "p" , cl::value_desc("<project>:<path>[:<revision>]" ), |
66 | cl::desc( |
67 | "Project specification: The name of the project, the absolute path of the source code, and " |
68 | "the revision separated by colons. Example: -p projectname:/path/to/source/code:0.3beta" ), |
69 | cl::ZeroOrMore); |
70 | |
71 | |
72 | cl::list<std::string> ExternalProjectPaths( |
73 | "e" , cl::value_desc("<project>:<path>:<url>" ), |
74 | cl::desc("Reference to an external project. Example: -e " |
75 | "clang/include/clang:/opt/llvm/include/clang/:https://code.woboq.org/llvm" ), |
76 | cl::ZeroOrMore); |
77 | |
78 | cl::opt<std::string> |
79 | DataPath("d" , cl::value_desc("data path" ), |
80 | cl::desc("Data url where all the javascript and css files are found. Can be absolute, " |
81 | "or relative to the output directory. Defaults to ../data" ), |
82 | cl::Optional); |
83 | |
84 | cl::opt<bool> |
85 | ProcessAllSources("a" , |
86 | cl::desc("Process all files from the compile_commands.json. If this argument " |
87 | "is passed, the list of sources does not need to be passed" )); |
88 | |
89 | cl::extrahelp ( |
90 | |
91 | R"( |
92 | |
93 | EXAMPLES: |
94 | |
95 | Simple generation without compile command or project (compile command specified inline) |
96 | codebrowser_generator -o ~/public_html/code -d https://code.woboq.org/data $PWD -- -std=c++14 -I/opt/llvm/include |
97 | |
98 | With a project |
99 | codebrowser_generator -b $PWD/build -a -p codebrowser:$PWD -o ~/public_html/code |
100 | )" ); |
101 | |
102 | #if 1 |
103 | std::string locationToString(clang::SourceLocation loc, clang::SourceManager &sm) |
104 | { |
105 | clang::PresumedLoc fixed = sm.getPresumedLoc(Loc: loc); |
106 | if (!fixed.isValid()) |
107 | return "???" ; |
108 | return (llvm::Twine(fixed.getFilename()) + ":" + llvm::Twine(fixed.getLine())).str(); |
109 | } |
110 | #endif |
111 | |
112 | enum class DatabaseType { |
113 | InDatabase, |
114 | NotInDatabase, |
115 | ProcessFullDirectory |
116 | }; |
117 | |
118 | struct BrowserDiagnosticClient : clang::DiagnosticConsumer |
119 | { |
120 | Annotator &annotator; |
121 | BrowserDiagnosticClient(Annotator &fm) |
122 | : annotator(fm) |
123 | { |
124 | } |
125 | |
126 | static bool isImmintrinDotH(const clang::PresumedLoc &loc) |
127 | { |
128 | return llvm::StringRef(loc.getFilename()).contains(Other: "immintrin.h" ); |
129 | } |
130 | |
131 | virtual void HandleDiagnostic(clang::DiagnosticsEngine::Level DiagLevel, |
132 | const clang::Diagnostic &Info) override |
133 | { |
134 | std::string clas; |
135 | llvm::SmallString<1000> diag; |
136 | Info.FormatDiagnostic(OutStr&: diag); |
137 | |
138 | switch (DiagLevel) { |
139 | case clang::DiagnosticsEngine::Fatal: |
140 | // ignore tons of errors in immintrin.h |
141 | if (isImmintrinDotH(loc: annotator.getSourceMgr().getPresumedLoc(Loc: Info.getLocation()))) |
142 | return; |
143 | std::cerr << "FATAL " ; |
144 | LLVM_FALLTHROUGH; |
145 | case clang::DiagnosticsEngine::Error: |
146 | std::cerr << "Error: " << locationToString(loc: Info.getLocation(), sm&: annotator.getSourceMgr()) |
147 | << ": " << diag.c_str() << std::endl; |
148 | clas = "error" ; |
149 | break; |
150 | case clang::DiagnosticsEngine::Warning: |
151 | clas = "warning" ; |
152 | break; |
153 | default: |
154 | return; |
155 | } |
156 | clang::SourceRange Range = Info.getLocation(); |
157 | annotator.reportDiagnostic(range: Range, msg: diag.c_str(), clas); |
158 | } |
159 | }; |
160 | |
161 | class BrowserASTConsumer : public clang::ASTConsumer |
162 | { |
163 | clang::CompilerInstance &ci; |
164 | Annotator annotator; |
165 | DatabaseType WasInDatabase; |
166 | |
167 | public: |
168 | BrowserASTConsumer(clang::CompilerInstance &ci, ProjectManager &projectManager, |
169 | DatabaseType WasInDatabase) |
170 | : clang::ASTConsumer() |
171 | , ci(ci) |
172 | , annotator(projectManager) |
173 | , WasInDatabase(WasInDatabase) |
174 | { |
175 | // ci.getLangOpts().DelayedTemplateParsing = (true); |
176 | #if CLANG_VERSION_MAJOR < 16 |
177 | // the meaning of this function has changed which causes |
178 | // a lot of issues in clang 16 |
179 | ci.getPreprocessor().enableIncrementalProcessing(); |
180 | #endif |
181 | } |
182 | virtual ~BrowserASTConsumer() |
183 | { |
184 | ci.getDiagnostics().setClient(client: new clang::IgnoringDiagConsumer, ShouldOwnClient: true); |
185 | } |
186 | |
187 | virtual void Initialize(clang::ASTContext &Ctx) override |
188 | { |
189 | annotator.setSourceMgr(sm&: Ctx.getSourceManager(), lo: Ctx.getLangOpts()); |
190 | annotator.setMangleContext(Ctx.createMangleContext()); |
191 | ci.getPreprocessor().addPPCallbacks(C: maybe_unique(val: new PreprocessorCallback( |
192 | annotator, ci.getPreprocessor(), WasInDatabase == DatabaseType::ProcessFullDirectory))); |
193 | ci.getDiagnostics().setClient(client: new BrowserDiagnosticClient(annotator), ShouldOwnClient: true); |
194 | ci.getDiagnostics().setErrorLimit(0); |
195 | } |
196 | |
197 | virtual bool HandleTopLevelDecl(clang::DeclGroupRef D) override |
198 | { |
199 | if (ci.getDiagnostics().hasFatalErrorOccurred()) { |
200 | // Reset errors: (Hack to ignore the fatal errors.) |
201 | ci.getDiagnostics().Reset(); |
202 | // When there was fatal error, processing the warnings may cause crashes |
203 | ci.getDiagnostics().setIgnoreAllWarnings(true); |
204 | } |
205 | return true; |
206 | } |
207 | |
208 | virtual void HandleTranslationUnit(clang::ASTContext &Ctx) override |
209 | { |
210 | |
211 | /* if (PP.getDiagnostics().hasErrorOccurred()) |
212 | return;*/ |
213 | ci.getPreprocessor().getDiagnostics().getClient(); |
214 | |
215 | |
216 | BrowserASTVisitor v(annotator); |
217 | v.TraverseDecl(d: Ctx.getTranslationUnitDecl()); |
218 | |
219 | |
220 | annotator.generate(ci.getSema(), WasInDatabase: WasInDatabase != DatabaseType::NotInDatabase); |
221 | } |
222 | |
223 | virtual bool shouldSkipFunctionBody(clang::Decl *D) override |
224 | { |
225 | return !annotator.shouldProcess( |
226 | clang::FullSourceLoc(D->getLocation(), annotator.getSourceMgr()) |
227 | .getExpansionLoc() |
228 | .getFileID()); |
229 | } |
230 | }; |
231 | |
232 | class BrowserAction : public clang::ASTFrontendAction |
233 | { |
234 | static std::set<std::string> processed; |
235 | DatabaseType WasInDatabase; |
236 | |
237 | protected: |
238 | #if CLANG_VERSION_MAJOR == 3 && CLANG_VERSION_MINOR <= 5 |
239 | virtual clang::ASTConsumer * |
240 | #else |
241 | virtual std::unique_ptr<clang::ASTConsumer> |
242 | #endif |
243 | CreateASTConsumer(clang::CompilerInstance &CI, llvm::StringRef InFile) override |
244 | { |
245 | if (processed.count(x: InFile.str())) { |
246 | std::cerr << "Skipping already processed " << InFile.str() << std::endl; |
247 | return nullptr; |
248 | } |
249 | processed.insert(x: InFile.str()); |
250 | |
251 | CI.getFrontendOpts().SkipFunctionBodies = true; |
252 | |
253 | return maybe_unique(val: new BrowserASTConsumer(CI, *projectManager, WasInDatabase)); |
254 | } |
255 | |
256 | public: |
257 | BrowserAction(DatabaseType WasInDatabase = DatabaseType::InDatabase) |
258 | : WasInDatabase(WasInDatabase) |
259 | { |
260 | } |
261 | virtual bool hasCodeCompletionSupport() const override |
262 | { |
263 | return true; |
264 | } |
265 | static ProjectManager *projectManager; |
266 | }; |
267 | |
268 | |
269 | std::set<std::string> BrowserAction::processed; |
270 | ProjectManager *BrowserAction::projectManager = nullptr; |
271 | |
272 | static bool proceedCommand(std::vector<std::string> command, llvm::StringRef Directory, |
273 | llvm::StringRef file, clang::FileManager *FM, DatabaseType WasInDatabase) |
274 | { |
275 | // This code change all the paths to be absolute paths |
276 | // FIXME: it is a bit fragile. |
277 | bool previousIsDashI = false; |
278 | bool previousNeedsMacro = false; |
279 | bool hasNoStdInc = false; |
280 | for (std::string &A : command) { |
281 | if (previousIsDashI && !A.empty() && A[0] != '/') { |
282 | A = Directory % "/" % A; |
283 | previousIsDashI = false; |
284 | continue; |
285 | } else if (A == "-I" ) { |
286 | previousIsDashI = true; |
287 | continue; |
288 | } else if (A == "-nostdinc" || A == "-nostdinc++" ) { |
289 | hasNoStdInc = true; |
290 | continue; |
291 | } else if (A == "-U" || A == "-D" ) { |
292 | previousNeedsMacro = true; |
293 | continue; |
294 | } |
295 | if (previousNeedsMacro) { |
296 | previousNeedsMacro = false; |
297 | continue; |
298 | } |
299 | previousIsDashI = false; |
300 | if (A.empty()) |
301 | continue; |
302 | if (llvm::StringRef(A).startswith(Prefix: "-I" ) && A[2] != '/') { |
303 | A = "-I" % Directory % "/" % llvm::StringRef(A).substr(Start: 2); |
304 | continue; |
305 | } |
306 | if (A[0] == '-' || A[0] == '/') |
307 | continue; |
308 | std::string PossiblePath = Directory % "/" % A; |
309 | if (llvm::sys::fs::exists(Path: PossiblePath)) |
310 | A = PossiblePath; |
311 | } |
312 | |
313 | #if CLANG_VERSION_MAJOR == 3 && CLANG_VERSION_MINOR < 6 |
314 | auto Ajust = [&](clang::tooling::ArgumentsAdjuster &&aj) { command = aj.Adjust(command); }; |
315 | Ajust(clang::tooling::ClangSyntaxOnlyAdjuster()); |
316 | Ajust(clang::tooling::ClangStripOutputAdjuster()); |
317 | #elif CLANG_VERSION_MAJOR == 3 && CLANG_VERSION_MINOR < 8 |
318 | command = clang::tooling::getClangSyntaxOnlyAdjuster()(command); |
319 | command = clang::tooling::getClangStripOutputAdjuster()(command); |
320 | #else |
321 | command = clang::tooling::getClangSyntaxOnlyAdjuster()(command, file); |
322 | command = clang::tooling::getClangStripOutputAdjuster()(command, file); |
323 | #endif |
324 | |
325 | if (!hasNoStdInc) { |
326 | #ifndef _WIN32 |
327 | command.push_back(x: "-isystem" ); |
328 | #else |
329 | command.push_back("-I" ); |
330 | #endif |
331 | |
332 | command.push_back(x: "/builtins" ); |
333 | } |
334 | |
335 | command.push_back(x: "-Qunused-arguments" ); |
336 | command.push_back(x: "-Wno-unknown-warning-option" ); |
337 | clang::tooling::ToolInvocation Inv(command, maybe_unique(val: new BrowserAction(WasInDatabase)), FM); |
338 | |
339 | #if CLANG_VERSION_MAJOR <= 10 |
340 | if (!hasNoStdInc) { |
341 | // Map the builtins includes |
342 | const EmbeddedFile *f = EmbeddedFiles; |
343 | while (f->filename) { |
344 | Inv.mapVirtualFile(f->filename, { f->content, f->size }); |
345 | f++; |
346 | } |
347 | } |
348 | #endif |
349 | |
350 | bool result = Inv.run(); |
351 | if (!result) { |
352 | std::cerr << "Error: The file was not recognized as source code: " << file.str() |
353 | << std::endl; |
354 | } |
355 | return result; |
356 | } |
357 | |
358 | int main(int argc, const char **argv) |
359 | { |
360 | std::string ErrorMessage; |
361 | std::unique_ptr<clang::tooling::CompilationDatabase> Compilations( |
362 | clang::tooling::FixedCompilationDatabase::loadFromCommandLine(Argc&: argc, Argv: argv |
363 | #if CLANG_VERSION_MAJOR >= 5 |
364 | , |
365 | ErrorMsg&: ErrorMessage |
366 | #endif |
367 | )); |
368 | if (!ErrorMessage.empty()) { |
369 | std::cerr << ErrorMessage << std::endl; |
370 | ErrorMessage = {}; |
371 | } |
372 | |
373 | llvm::cl::ParseCommandLineOptions(argc, argv); |
374 | |
375 | #ifdef _WIN32 |
376 | make_forward_slashes(OutputPath._Get_data()._Myptr()); |
377 | #endif |
378 | |
379 | ProjectManager projectManager(OutputPath, DataPath); |
380 | for (std::string &s : ProjectPaths) { |
381 | auto colonPos = s.find(c: ':'); |
382 | if (colonPos >= s.size()) { |
383 | std::cerr << "fail to parse project option : " << s << std::endl; |
384 | continue; |
385 | } |
386 | auto secondColonPos = s.find(c: ':', pos: colonPos + 1); |
387 | ProjectInfo info { s.substr(pos: 0, n: colonPos), |
388 | s.substr(pos: colonPos + 1, n: secondColonPos - colonPos - 1), |
389 | secondColonPos < s.size() ? s.substr(pos: secondColonPos + 1) |
390 | : std::string() }; |
391 | if (!projectManager.addProject(info: std::move(info))) { |
392 | std::cerr << "invalid project directory for : " << s << std::endl; |
393 | } |
394 | } |
395 | for (std::string &s : ExternalProjectPaths) { |
396 | auto colonPos = s.find(c: ':'); |
397 | if (colonPos >= s.size()) { |
398 | std::cerr << "fail to parse project option : " << s << std::endl; |
399 | continue; |
400 | } |
401 | auto secondColonPos = s.find(c: ':', pos: colonPos + 1); |
402 | if (secondColonPos >= s.size()) { |
403 | std::cerr << "fail to parse project option : " << s << std::endl; |
404 | continue; |
405 | } |
406 | ProjectInfo info { s.substr(pos: 0, n: colonPos), |
407 | s.substr(pos: colonPos + 1, n: secondColonPos - colonPos - 1), |
408 | ProjectInfo::External }; |
409 | info.external_root_url = s.substr(pos: secondColonPos + 1); |
410 | if (!projectManager.addProject(info: std::move(info))) { |
411 | std::cerr << "invalid project directory for : " << s << std::endl; |
412 | } |
413 | } |
414 | BrowserAction::projectManager = &projectManager; |
415 | |
416 | |
417 | if (!Compilations && llvm::sys::fs::exists(Path: BuildPath)) { |
418 | if (llvm::sys::fs::is_directory(Path: BuildPath)) { |
419 | Compilations = std::unique_ptr<clang::tooling::CompilationDatabase>( |
420 | clang::tooling::CompilationDatabase::loadFromDirectory(BuildDirectory: BuildPath, ErrorMessage)); |
421 | } else { |
422 | Compilations = std::unique_ptr<clang::tooling::CompilationDatabase>( |
423 | clang::tooling::JSONCompilationDatabase::loadFromFile( |
424 | FilePath: BuildPath, ErrorMessage |
425 | #if CLANG_VERSION_MAJOR >= 4 |
426 | , |
427 | Syntax: clang::tooling::JSONCommandLineSyntax::AutoDetect |
428 | #endif |
429 | )); |
430 | } |
431 | if (!Compilations && !ErrorMessage.empty()) { |
432 | std::cerr << ErrorMessage << std::endl; |
433 | } |
434 | } |
435 | |
436 | if (!Compilations) { |
437 | std::cerr |
438 | << "Could not load compilationdatabase. " |
439 | "Please use the -b option to a path containing a compile_commands.json, or use " |
440 | "'--' followed by the compilation commands." |
441 | << std::endl; |
442 | return EXIT_FAILURE; |
443 | } |
444 | |
445 | bool IsProcessingAllDirectory = false; |
446 | std::vector<std::string> DirContents; |
447 | std::vector<std::string> AllFiles = Compilations->getAllFiles(); |
448 | std::sort(first: AllFiles.begin(), last: AllFiles.end()); |
449 | llvm::ArrayRef<std::string> Sources = SourcePaths; |
450 | if (Sources.empty() && ProcessAllSources) { |
451 | // Because else the order is too random |
452 | Sources = AllFiles; |
453 | } else if (ProcessAllSources) { |
454 | std::cerr << "Cannot use both sources and '-a'" << std::endl; |
455 | return EXIT_FAILURE; |
456 | } else if (Sources.size() == 1 && llvm::sys::fs::is_directory(Path: Sources.front())) { |
457 | #if CLANG_VERSION_MAJOR != 3 || CLANG_VERSION_MINOR >= 5 |
458 | // A directory was passed, process all the files in that directory |
459 | llvm::SmallString<128> DirName; |
460 | llvm::sys::path::native(path: Sources.front(), result&: DirName); |
461 | while (DirName.endswith(Suffix: "/" )) |
462 | DirName.pop_back(); |
463 | std::error_code EC; |
464 | for (llvm::sys::fs::recursive_directory_iterator it(DirName.str(), EC), DirEnd; |
465 | it != DirEnd && !EC; it.increment(ec&: EC)) { |
466 | if (llvm::sys::path::filename(path: it->path()).startswith(Prefix: "." )) { |
467 | it.no_push(); |
468 | continue; |
469 | } |
470 | DirContents.push_back(x: it->path()); |
471 | } |
472 | Sources = DirContents; |
473 | IsProcessingAllDirectory = true; |
474 | if (EC) { |
475 | std::cerr << "Error reading the directory: " << EC.message() << std::endl; |
476 | return EXIT_FAILURE; |
477 | } |
478 | |
479 | if (ProjectPaths.empty()) { |
480 | ProjectInfo info { std::string(llvm::sys::path::filename(path: DirName)), |
481 | std::string(DirName.str()) }; |
482 | projectManager.addProject(info: std::move(info)); |
483 | } |
484 | #else |
485 | std::cerr << "Passing directory is only implemented with llvm >= 3.5" << std::endl; |
486 | return EXIT_FAILURE; |
487 | #endif |
488 | } |
489 | |
490 | if (Sources.empty()) { |
491 | std::cerr << "No source files. Please pass source files as argument, or use '-a'" |
492 | << std::endl; |
493 | return EXIT_FAILURE; |
494 | } |
495 | if (ProjectPaths.empty() && !IsProcessingAllDirectory) { |
496 | std::cerr << "You must specify a project name and directory with '-p name:directory'" |
497 | << std::endl; |
498 | return EXIT_FAILURE; |
499 | } |
500 | |
501 | #if CLANG_VERSION_MAJOR >= 12 |
502 | llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> VFS( |
503 | new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); |
504 | clang::FileManager FM({ .WorkingDir: "." }, VFS); |
505 | #else |
506 | clang::FileManager FM({ "." }); |
507 | #endif |
508 | FM.Retain(); |
509 | |
510 | #if CLANG_VERSION_MAJOR >= 12 |
511 | // Map virtual files |
512 | { |
513 | auto InMemoryFS = llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>( |
514 | new llvm::vfs::InMemoryFileSystem); |
515 | VFS->pushOverlay(FS: InMemoryFS); |
516 | // Map the builtins includes |
517 | const EmbeddedFile *f = EmbeddedFiles; |
518 | while (f->filename) { |
519 | InMemoryFS->addFile(Path: f->filename, ModificationTime: 0, Buffer: llvm::MemoryBuffer::getMemBufferCopy(InputData: f->content)); |
520 | f++; |
521 | } |
522 | } |
523 | #endif |
524 | |
525 | int Progress = 0; |
526 | |
527 | std::vector<std::string> NotInDB; |
528 | |
529 | for (const auto &it : Sources) { |
530 | std::string file = clang::tooling::getAbsolutePath(File: it); |
531 | Progress++; |
532 | |
533 | if (it.empty() || it == "-" ) |
534 | continue; |
535 | |
536 | llvm::SmallString<256> filename; |
537 | canonicalize(path: file, result&: filename); |
538 | |
539 | if (auto project = projectManager.projectForFile(filename)) { |
540 | if (!projectManager.shouldProcess(filename, project)) { |
541 | std::cerr << "Sources: Skipping already processed " << filename.c_str() |
542 | << std::endl; |
543 | continue; |
544 | } |
545 | } else { |
546 | std::cerr << "Sources: Skipping file not included by any project " << filename.c_str() |
547 | << std::endl; |
548 | continue; |
549 | } |
550 | |
551 | bool = llvm::StringSwitch<bool>(llvm::sys::path::extension(path: filename)) |
552 | .Cases(S0: ".h" , S1: ".H" , S2: ".hh" , S3: ".hpp" , Value: true) |
553 | .Default(Value: false); |
554 | |
555 | auto compileCommandsForFile = Compilations->getCompileCommands(FilePath: file); |
556 | if (!compileCommandsForFile.empty() && !isHeader) { |
557 | std::cerr << '[' << (100 * Progress / Sources.size()) << "%] Processing " << file |
558 | << "\n" ; |
559 | proceedCommand(command: compileCommandsForFile.front().CommandLine, |
560 | Directory: compileCommandsForFile.front().Directory, file, FM: &FM, |
561 | WasInDatabase: IsProcessingAllDirectory ? DatabaseType::ProcessFullDirectory |
562 | : DatabaseType::InDatabase); |
563 | } else { |
564 | // TODO: Try to find a command line for a file in the same path |
565 | std::cerr << "Delayed " << file << "\n" ; |
566 | Progress--; |
567 | NotInDB.push_back(x: std::string(filename.str())); |
568 | continue; |
569 | } |
570 | } |
571 | |
572 | for (const auto &it : NotInDB) { |
573 | std::string file = clang::tooling::getAbsolutePath(File: it); |
574 | Progress++; |
575 | |
576 | if (auto project = projectManager.projectForFile(filename: file)) { |
577 | if (!projectManager.shouldProcess(filename: file, project)) { |
578 | std::cerr << "NotInDB: Skipping already processed " << file.c_str() << std::endl; |
579 | continue; |
580 | } |
581 | } else { |
582 | std::cerr << "NotInDB: Skipping file not included by any project " << file.c_str() |
583 | << std::endl; |
584 | continue; |
585 | } |
586 | |
587 | llvm::StringRef similar; |
588 | |
589 | auto compileCommandsForFile = Compilations->getCompileCommands(FilePath: file); |
590 | std::string fileForCommands = file; |
591 | if (compileCommandsForFile.empty()) { |
592 | // Find the element with the bigger prefix |
593 | auto lower = std::lower_bound(first: AllFiles.cbegin(), last: AllFiles.cend(), val: file); |
594 | if (lower == AllFiles.cend()) |
595 | lower = AllFiles.cbegin(); |
596 | compileCommandsForFile = Compilations->getCompileCommands(FilePath: *lower); |
597 | fileForCommands = *lower; |
598 | } |
599 | |
600 | bool success = false; |
601 | if (!compileCommandsForFile.empty()) { |
602 | std::cerr << '[' << (100 * Progress / Sources.size()) << "%] Processing " << file |
603 | << "\n" ; |
604 | auto command = compileCommandsForFile.front().CommandLine; |
605 | std::replace(first: command.begin(), last: command.end(), old_value: fileForCommands, new_value: it); |
606 | if (llvm::StringRef(file).endswith(Suffix: ".qdoc" )) { |
607 | command.insert(position: command.begin() + 1, x: "-xc++" ); |
608 | // include the header for this .qdoc file |
609 | command.push_back(x: "-include" ); |
610 | command.push_back(x: llvm::StringRef(file).substr(Start: 0, N: file.size() - 5) % ".h" ); |
611 | } |
612 | success = proceedCommand(command: std::move(command), Directory: compileCommandsForFile.front().Directory, |
613 | file, FM: &FM, |
614 | WasInDatabase: IsProcessingAllDirectory ? DatabaseType::ProcessFullDirectory |
615 | : DatabaseType::NotInDatabase); |
616 | } else { |
617 | std::cerr << "Could not find commands for " << file << "\n" ; |
618 | } |
619 | |
620 | if (!success && !IsProcessingAllDirectory) { |
621 | ProjectInfo *projectinfo = projectManager.projectForFile(filename: file); |
622 | if (!projectinfo) |
623 | continue; |
624 | if (!projectManager.shouldProcess(filename: file, project: projectinfo)) |
625 | continue; |
626 | |
627 | auto now = std::time(timer: 0); |
628 | auto tm = localtime(timer: &now); |
629 | char buf[80]; |
630 | std::strftime(s: buf, maxsize: sizeof(buf), format: "%Y-%b-%d" , tp: tm); |
631 | |
632 | std::string = "Generated on <em>" % std::string(buf) % "</em>" % " from project " |
633 | % projectinfo->name % "</a>" ; |
634 | if (!projectinfo->revision.empty()) |
635 | footer %= " revision <em>" % projectinfo->revision % "</em>" ; |
636 | |
637 | #if CLANG_VERSION_MAJOR == 3 && CLANG_VERSION_MINOR <= 4 |
638 | llvm::OwningPtr<llvm::MemoryBuffer> Buf; |
639 | if (!llvm::MemoryBuffer::getFile(file, Buf)) |
640 | continue; |
641 | #else |
642 | auto B = llvm::MemoryBuffer::getFile(Filename: file); |
643 | if (!B) |
644 | continue; |
645 | std::unique_ptr<llvm::MemoryBuffer> Buf = std::move(B.get()); |
646 | #endif |
647 | |
648 | std::string fn = projectinfo->name % "/" |
649 | % llvm::StringRef(file).substr(Start: projectinfo->source_path.size()); |
650 | |
651 | Generator g; |
652 | g.generate(outputPrefix: projectManager.outputPrefix, dataPath: projectManager.dataPath, filename: fn, |
653 | begin: Buf->getBufferStart(), end: Buf->getBufferEnd(), footer, |
654 | warningMessage: "Warning: This file is not a C or C++ file. It does not have highlighting." , |
655 | interestingDefitions: std::set<std::string>()); |
656 | |
657 | std::ofstream fileIndex; |
658 | fileIndex.open(s: projectManager.outputPrefix + "/otherIndex" , mode: std::ios::app); |
659 | if (!fileIndex) |
660 | continue; |
661 | fileIndex << fn << '\n'; |
662 | } |
663 | } |
664 | } |
665 | |