| 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 | |