brintos

brintos / llvm-project-archived public Read only

0
0
Text · 17.0 KiB · 62fa6a1 Raw
458 lines · cpp
1//===-- ClangDocMain.cpp - ClangDoc -----------------------------*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// This tool for generating C and C++ documentation from source code10// and comments. Generally, it runs a LibTooling FrontendAction on source files,11// mapping each declaration in those files to its USR and serializing relevant12// information into LLVM bitcode. It then runs a pass over the collected13// declaration information, reducing by USR. There is an option to dump this14// intermediate result to bitcode. Finally, it hands the reduced information15// off to a generator, which does the final parsing from the intermediate16// representation to the desired output format.17//18//===----------------------------------------------------------------------===//19 20#include "BitcodeReader.h"21#include "ClangDoc.h"22#include "Generators.h"23#include "Representation.h"24#include "support/Utils.h"25#include "clang/Tooling/AllTUsExecution.h"26#include "clang/Tooling/CommonOptionsParser.h"27#include "clang/Tooling/Execution.h"28#include "llvm/ADT/APFloat.h"29#include "llvm/Support/CommandLine.h"30#include "llvm/Support/Error.h"31#include "llvm/Support/FileSystem.h"32#include "llvm/Support/Mutex.h"33#include "llvm/Support/Path.h"34#include "llvm/Support/Process.h"35#include "llvm/Support/Signals.h"36#include "llvm/Support/ThreadPool.h"37#include "llvm/Support/TimeProfiler.h"38#include "llvm/Support/raw_ostream.h"39#include <atomic>40#include <mutex>41#include <string>42 43using namespace clang::tooling;44using namespace clang;45 46static llvm::cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage);47static llvm::cl::OptionCategory ClangDocCategory("clang-doc options");48 49static llvm::cl::opt<std::string>50    ProjectName("project-name", llvm::cl::desc("Name of project."),51                llvm::cl::cat(ClangDocCategory));52 53static llvm::cl::opt<bool> IgnoreMappingFailures(54    "ignore-map-errors",55    llvm::cl::desc("Continue if files are not mapped correctly."),56    llvm::cl::init(true), llvm::cl::cat(ClangDocCategory));57 58static llvm::cl::opt<std::string>59    OutDirectory("output",60                 llvm::cl::desc("Directory for outputting generated files."),61                 llvm::cl::init("docs"), llvm::cl::cat(ClangDocCategory));62 63static llvm::cl::opt<std::string>64    BaseDirectory("base",65                  llvm::cl::desc(R"(Base Directory for generated documentation.66URLs will be rooted at this directory for HTML links.)"),67                  llvm::cl::init(""), llvm::cl::cat(ClangDocCategory));68 69static llvm::cl::opt<bool>70    PublicOnly("public", llvm::cl::desc("Document only public declarations."),71               llvm::cl::init(false), llvm::cl::cat(ClangDocCategory));72 73static llvm::cl::opt<bool> DoxygenOnly(74    "doxygen",75    llvm::cl::desc("Use only doxygen-style comments to generate docs."),76    llvm::cl::init(false), llvm::cl::cat(ClangDocCategory));77 78static llvm::cl::list<std::string> UserStylesheets(79    "stylesheets", llvm::cl::CommaSeparated,80    llvm::cl::desc("CSS stylesheets to extend the default styles."),81    llvm::cl::cat(ClangDocCategory));82 83static llvm::cl::opt<std::string> UserAssetPath(84    "asset",85    llvm::cl::desc("User supplied asset path to "86                   "override the default css and js files for html output"),87    llvm::cl::cat(ClangDocCategory));88 89static llvm::cl::opt<std::string> SourceRoot("source-root", llvm::cl::desc(R"(90Directory where processed files are stored.91Links to definition locations will only be92generated if the file is in this dir.)"),93                                             llvm::cl::cat(ClangDocCategory));94 95static llvm::cl::opt<std::string>96    RepositoryUrl("repository", llvm::cl::desc(R"(97URL of repository that hosts code.98Used for links to definition locations.)"),99                  llvm::cl::cat(ClangDocCategory));100 101static llvm::cl::opt<std::string> RepositoryCodeLinePrefix(102    "repository-line-prefix",103    llvm::cl::desc("Prefix of line code for repository."),104    llvm::cl::cat(ClangDocCategory));105 106static llvm::cl::opt<bool> FTimeTrace("ftime-trace", llvm::cl::desc(R"(107Turn on time profiler. Generates clang-doc-tracing.json)"),108                                      llvm::cl::init(false),109                                      llvm::cl::cat(ClangDocCategory));110 111enum OutputFormatTy { md, yaml, html, mustache, json };112 113static llvm::cl::opt<OutputFormatTy> FormatEnum(114    "format", llvm::cl::desc("Format for outputted docs."),115    llvm::cl::values(clEnumValN(OutputFormatTy::yaml, "yaml",116                                "Documentation in YAML format."),117                     clEnumValN(OutputFormatTy::md, "md",118                                "Documentation in MD format."),119                     clEnumValN(OutputFormatTy::html, "html",120                                "Documentation in HTML format."),121                     clEnumValN(OutputFormatTy::mustache, "mustache",122                                "Documentation in mustache HTML format"),123                     clEnumValN(OutputFormatTy::json, "json",124                                "Documentation in JSON format")),125    llvm::cl::init(OutputFormatTy::yaml), llvm::cl::cat(ClangDocCategory));126 127static llvm::ExitOnError ExitOnErr;128 129static std::string getFormatString() {130  switch (FormatEnum) {131  case OutputFormatTy::yaml:132    return "yaml";133  case OutputFormatTy::md:134    return "md";135  case OutputFormatTy::html:136    return "html";137  case OutputFormatTy::mustache:138    return "mustache";139  case OutputFormatTy::json:140    return "json";141  }142  llvm_unreachable("Unknown OutputFormatTy");143}144 145// This function isn't referenced outside its translation unit, but it146// can't use the "static" keyword because its address is used for147// GetMainExecutable (since some platforms don't support taking the148// address of main, and some platforms can't implement GetMainExecutable149// without being given the address of a function in the main executable).150static std::string getExecutablePath(const char *Argv0, void *MainAddr) {151  return llvm::sys::fs::getMainExecutable(Argv0, MainAddr);152}153 154static llvm::Error getAssetFiles(clang::doc::ClangDocContext &CDCtx) {155  using DirIt = llvm::sys::fs::directory_iterator;156  std::error_code FileErr;157  llvm::SmallString<128> FilePath(UserAssetPath);158  for (DirIt DirStart = DirIt(UserAssetPath, FileErr), DirEnd;159       !FileErr && DirStart != DirEnd; DirStart.increment(FileErr)) {160    FilePath = DirStart->path();161    if (llvm::sys::fs::is_regular_file(FilePath)) {162      if (llvm::sys::path::extension(FilePath) == ".css")163        CDCtx.UserStylesheets.insert(CDCtx.UserStylesheets.begin(),164                                     std::string(FilePath));165      else if (llvm::sys::path::extension(FilePath) == ".js")166        CDCtx.JsScripts.emplace_back(FilePath.str());167    }168  }169  if (FileErr)170    return llvm::createFileError(FilePath, FileErr);171  return llvm::Error::success();172}173 174static llvm::Error getDefaultAssetFiles(const char *Argv0,175                                        clang::doc::ClangDocContext &CDCtx) {176  void *MainAddr = (void *)(intptr_t)getExecutablePath;177  std::string ClangDocPath = getExecutablePath(Argv0, MainAddr);178  llvm::SmallString<128> NativeClangDocPath;179  llvm::sys::path::native(ClangDocPath, NativeClangDocPath);180 181  llvm::SmallString<128> AssetsPath;182  AssetsPath = llvm::sys::path::parent_path(NativeClangDocPath);183  llvm::sys::path::append(AssetsPath, "..", "share", "clang-doc");184  llvm::SmallString<128> DefaultStylesheet =185      appendPathNative(AssetsPath, "clang-doc-default-stylesheet.css");186  llvm::SmallString<128> IndexJS = appendPathNative(AssetsPath, "index.js");187 188  if (!llvm::sys::fs::is_regular_file(IndexJS))189    return llvm::createStringError(llvm::inconvertibleErrorCode(),190                                   "default index.js file missing at " +191                                       IndexJS + "\n");192 193  if (!llvm::sys::fs::is_regular_file(DefaultStylesheet))194    return llvm::createStringError(195        llvm::inconvertibleErrorCode(),196        "default clang-doc-default-stylesheet.css file missing at " +197            DefaultStylesheet + "\n");198 199  CDCtx.UserStylesheets.insert(CDCtx.UserStylesheets.begin(),200                               std::string(DefaultStylesheet));201  CDCtx.JsScripts.emplace_back(IndexJS.str());202 203  return llvm::Error::success();204}205 206static llvm::Error getHtmlAssetFiles(const char *Argv0,207                                     clang::doc::ClangDocContext &CDCtx) {208  if (!UserAssetPath.empty() &&209      !llvm::sys::fs::is_directory(std::string(UserAssetPath)))210    llvm::outs() << "Asset path supply is not a directory: " << UserAssetPath211                 << " falling back to default\n";212  if (llvm::sys::fs::is_directory(std::string(UserAssetPath)))213    return getAssetFiles(CDCtx);214  return getDefaultAssetFiles(Argv0, CDCtx);215}216 217static llvm::Error getMustacheHtmlFiles(const char *Argv0,218                                        clang::doc::ClangDocContext &CDCtx) {219  bool IsDir = llvm::sys::fs::is_directory(UserAssetPath);220  if (!UserAssetPath.empty() && !IsDir)221    llvm::outs() << "Asset path supply is not a directory: " << UserAssetPath222                 << " falling back to default\n";223  if (IsDir) {224    getMustacheHtmlFiles(UserAssetPath, CDCtx);225    return llvm::Error::success();226  }227  void *MainAddr = (void *)(intptr_t)getExecutablePath;228  std::string ClangDocPath = getExecutablePath(Argv0, MainAddr);229  llvm::SmallString<128> NativeClangDocPath;230  llvm::sys::path::native(ClangDocPath, NativeClangDocPath);231 232  llvm::SmallString<128> AssetsPath;233  AssetsPath = llvm::sys::path::parent_path(NativeClangDocPath);234  llvm::sys::path::append(AssetsPath, "..", "share", "clang-doc");235 236  getMustacheHtmlFiles(AssetsPath, CDCtx);237 238  return llvm::Error::success();239}240 241/// Make the output of clang-doc deterministic by sorting the children of242/// namespaces and records.243static void244sortUsrToInfo(llvm::StringMap<std::unique_ptr<doc::Info>> &USRToInfo) {245  for (auto &I : USRToInfo) {246    auto &Info = I.second;247    if (Info->IT == doc::InfoType::IT_namespace) {248      auto *Namespace = static_cast<clang::doc::NamespaceInfo *>(Info.get());249      Namespace->Children.sort();250    }251    if (Info->IT == doc::InfoType::IT_record) {252      auto *Record = static_cast<clang::doc::RecordInfo *>(Info.get());253      Record->Children.sort();254    }255  }256}257 258static llvm::Error handleMappingFailures(llvm::Error Err) {259  if (!Err)260    return llvm::Error::success();261  if (IgnoreMappingFailures) {262    llvm::errs() << "Error mapping decls in files. Clang-doc will ignore these "263                    "files and continue:\n"264                 << toString(std::move(Err)) << "\n";265    return llvm::Error::success();266  }267  return Err;268}269 270static llvm::Error createDirectories(llvm::StringRef OutDirectory) {271  if (std::error_code Err = llvm::sys::fs::create_directories(OutDirectory))272    return llvm::createFileError(OutDirectory, Err,273                                 "failed to create directory.");274  return llvm::Error::success();275}276 277int main(int argc, const char **argv) {278  llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);279  std::error_code OK;280 281  ExitOnErr.setBanner("clang-doc error: ");282 283  const char *Overview =284      R"(Generates documentation from source code and comments.285 286Example usage for files without flags (default):287 288  $ clang-doc File1.cpp File2.cpp ... FileN.cpp289 290Example usage for a project using a compile commands database:291 292  $ clang-doc --executor=all-TUs compile_commands.json293)";294 295  auto Executor = ExitOnErr(clang::tooling::createExecutorFromCommandLineArgs(296      argc, argv, ClangDocCategory, Overview));297 298  // turns on ftime trace profiling299  if (FTimeTrace)300    llvm::timeTraceProfilerInitialize(200, "clang-doc");301  {302    llvm::TimeTraceScope("main");303 304    // Fail early if an invalid format was provided.305    std::string Format = getFormatString();306    llvm::outs() << "Emiting docs in " << Format << " format.\n";307    auto G = ExitOnErr(doc::findGeneratorByName(Format));308 309    ArgumentsAdjuster ArgAdjuster;310    if (!DoxygenOnly)311      ArgAdjuster = combineAdjusters(312          getInsertArgumentAdjuster("-fparse-all-comments",313                                    tooling::ArgumentInsertPosition::END),314          ArgAdjuster);315 316    clang::doc::ClangDocContext CDCtx = {317        Executor->getExecutionContext(),318        ProjectName,319        PublicOnly,320        OutDirectory,321        SourceRoot,322        RepositoryUrl,323        RepositoryCodeLinePrefix,324        BaseDirectory,325        {UserStylesheets.begin(), UserStylesheets.end()},326        FTimeTrace};327 328    if (Format == "html") {329      ExitOnErr(getHtmlAssetFiles(argv[0], CDCtx));330    } else if (Format == "mustache") {331      ExitOnErr(getMustacheHtmlFiles(argv[0], CDCtx));332    }333 334    llvm::timeTraceProfilerBegin("Executor Launch", "total runtime");335    // Mapping phase336    llvm::outs() << "Mapping decls...\n";337    ExitOnErr(handleMappingFailures(338        Executor->execute(doc::newMapperActionFactory(CDCtx), ArgAdjuster)));339    llvm::timeTraceProfilerEnd();340 341    // Collect values into output by key.342    // In ToolResults, the Key is the hashed USR and the value is the343    // bitcode-encoded representation of the Info object.344    llvm::timeTraceProfilerBegin("Collect Info", "total runtime");345    llvm::outs() << "Collecting infos...\n";346    llvm::StringMap<std::vector<StringRef>> USRToBitcode;347    Executor->getToolResults()->forEachResult(348        [&](StringRef Key, StringRef Value) {349          USRToBitcode[Key].emplace_back(Value);350        });351    llvm::timeTraceProfilerEnd();352 353    // Collects all Infos according to their unique USR value. This map is added354    // to from the thread pool below and is protected by the USRToInfoMutex.355    llvm::sys::Mutex USRToInfoMutex;356    llvm::StringMap<std::unique_ptr<doc::Info>> USRToInfo;357 358    // First reducing phase (reduce all decls into one info per decl).359    llvm::outs() << "Reducing " << USRToBitcode.size() << " infos...\n";360    std::atomic<bool> Error;361    Error = false;362    llvm::sys::Mutex IndexMutex;363    // ExecutorConcurrency is a flag exposed by AllTUsExecution.h364    llvm::DefaultThreadPool Pool(365        llvm::hardware_concurrency(ExecutorConcurrency));366    {367      llvm::TimeTraceScope TS("Reduce");368      for (auto &Group : USRToBitcode) {369        Pool.async([&]() { // time trace decoding bitcode370          if (FTimeTrace)371            llvm::timeTraceProfilerInitialize(200, "clang-doc");372 373          std::vector<std::unique_ptr<doc::Info>> Infos;374          {375            llvm::TimeTraceScope Red("decoding bitcode");376            for (auto &Bitcode : Group.getValue()) {377              llvm::BitstreamCursor Stream(Bitcode);378              doc::ClangDocBitcodeReader Reader(Stream);379              auto ReadInfos = Reader.readBitcode();380              if (!ReadInfos) {381                llvm::errs() << toString(ReadInfos.takeError()) << "\n";382                Error = true;383                return;384              }385              std::move(ReadInfos->begin(), ReadInfos->end(),386                        std::back_inserter(Infos));387            }388          } // time trace decoding bitcode389 390          std::unique_ptr<doc::Info> Reduced;391 392          {393            llvm::TimeTraceScope Merge("merging bitcode");394            auto ExpReduced = doc::mergeInfos(Infos);395 396            if (!ExpReduced) {397              llvm::errs() << llvm::toString(ExpReduced.takeError());398              return;399            }400            Reduced = std::move(*ExpReduced);401          } // time trace merging bitcode402 403          // Add a reference to this Info in the Index404          {405            llvm::TimeTraceScope Merge("addInfoToIndex");406            std::lock_guard<llvm::sys::Mutex> Guard(IndexMutex);407            clang::doc::Generator::addInfoToIndex(CDCtx.Idx, Reduced.get());408          }409          // Save in the result map (needs a lock due to threaded access).410          {411            llvm::TimeTraceScope Merge("USRToInfo");412            std::lock_guard<llvm::sys::Mutex> Guard(USRToInfoMutex);413            USRToInfo[Group.getKey()] = std::move(Reduced);414          }415 416          if (CDCtx.FTimeTrace)417            llvm::timeTraceProfilerFinishThread();418        });419      }420 421      Pool.wait();422    } // time trace reduce423 424    if (Error)425      return 1;426 427    {428      llvm::TimeTraceScope Sort("Sort USRToInfo");429      sortUsrToInfo(USRToInfo);430    }431 432    llvm::timeTraceProfilerBegin("Writing output", "total runtime");433    // Ensure the root output directory exists.434    ExitOnErr(createDirectories(OutDirectory));435 436    // Run the generator.437    llvm::outs() << "Generating docs...\n";438 439    ExitOnErr(440        G->generateDocumentation(OutDirectory, std::move(USRToInfo), CDCtx));441    llvm::outs() << "Generating assets for docs...\n";442    ExitOnErr(G->createResources(CDCtx));443    llvm::timeTraceProfilerEnd();444  } // time trace main445 446  if (FTimeTrace) {447    std::error_code EC;448    llvm::raw_fd_ostream OS("clang-doc-tracing.json", EC,449                            llvm::sys::fs::OF_Text);450    if (!EC) {451      llvm::timeTraceProfilerWrite(OS);452      llvm::timeTraceProfilerCleanup();453    } else454      return 1;455  }456  return 0;457}458