| //===-- ClangDocMain.cpp - ClangDoc -----------------------------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This tool for generating C and C++ documentation from source code |
| // and comments. Generally, it runs a LibTooling FrontendAction on source files, |
| // mapping each declaration in those files to its USR and serializing relevant |
| // information into LLVM bitcode. It then runs a pass over the collected |
| // declaration information, reducing by USR. There is an option to dump this |
| // intermediate result to bitcode. Finally, it hands the reduced information |
| // off to a generator, which does the final parsing from the intermediate |
| // representation to the desired output format. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "BitcodeReader.h" |
| #include "ClangDoc.h" |
| #include "Generators.h" |
| #include "Representation.h" |
| #include "support/Utils.h" |
| #include "clang/ASTMatchers/ASTMatchersInternal.h" |
| #include "clang/Tooling/AllTUsExecution.h" |
| #include "clang/Tooling/CommonOptionsParser.h" |
| #include "clang/Tooling/Execution.h" |
| #include "llvm/ADT/APFloat.h" |
| #include "llvm/Support/CommandLine.h" |
| #include "llvm/Support/Error.h" |
| #include "llvm/Support/FileSystem.h" |
| #include "llvm/Support/Mutex.h" |
| #include "llvm/Support/Path.h" |
| #include "llvm/Support/Process.h" |
| #include "llvm/Support/Signals.h" |
| #include "llvm/Support/ThreadPool.h" |
| #include "llvm/Support/TimeProfiler.h" |
| #include "llvm/Support/raw_ostream.h" |
| #include <atomic> |
| #include <mutex> |
| #include <string> |
| |
| using namespace clang::ast_matchers; |
| using namespace clang::tooling; |
| using namespace clang; |
| |
| static llvm::cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); |
| static llvm::cl::OptionCategory ClangDocCategory("clang-doc options"); |
| |
| static llvm::cl::opt<std::string> |
| ProjectName("project-name", llvm::cl::desc("Name of project."), |
| llvm::cl::cat(ClangDocCategory)); |
| |
| static llvm::cl::opt<bool> IgnoreMappingFailures( |
| "ignore-map-errors", |
| llvm::cl::desc("Continue if files are not mapped correctly."), |
| llvm::cl::init(true), llvm::cl::cat(ClangDocCategory)); |
| |
| static llvm::cl::opt<std::string> |
| OutDirectory("output", |
| llvm::cl::desc("Directory for outputting generated files."), |
| llvm::cl::init("docs"), llvm::cl::cat(ClangDocCategory)); |
| |
| static llvm::cl::opt<std::string> |
| BaseDirectory("base", |
| llvm::cl::desc(R"(Base Directory for generated documentation. |
| URLs will be rooted at this directory for HTML links.)"), |
| llvm::cl::init(""), llvm::cl::cat(ClangDocCategory)); |
| |
| static llvm::cl::opt<bool> |
| PublicOnly("public", llvm::cl::desc("Document only public declarations."), |
| llvm::cl::init(false), llvm::cl::cat(ClangDocCategory)); |
| |
| static llvm::cl::opt<bool> DoxygenOnly( |
| "doxygen", |
| llvm::cl::desc("Use only doxygen-style comments to generate docs."), |
| llvm::cl::init(false), llvm::cl::cat(ClangDocCategory)); |
| |
| static llvm::cl::list<std::string> UserStylesheets( |
| "stylesheets", llvm::cl::CommaSeparated, |
| llvm::cl::desc("CSS stylesheets to extend the default styles."), |
| llvm::cl::cat(ClangDocCategory)); |
| |
| static llvm::cl::opt<std::string> UserAssetPath( |
| "asset", |
| llvm::cl::desc("User supplied asset path to " |
| "override the default css and js files for html output"), |
| llvm::cl::cat(ClangDocCategory)); |
| |
| static llvm::cl::opt<std::string> SourceRoot("source-root", llvm::cl::desc(R"( |
| Directory where processed files are stored. |
| Links to definition locations will only be |
| generated if the file is in this dir.)"), |
| llvm::cl::cat(ClangDocCategory)); |
| |
| static llvm::cl::opt<std::string> |
| RepositoryUrl("repository", llvm::cl::desc(R"( |
| URL of repository that hosts code. |
| Used for links to definition locations.)"), |
| llvm::cl::cat(ClangDocCategory)); |
| |
| static llvm::cl::opt<std::string> RepositoryCodeLinePrefix( |
| "repository-line-prefix", |
| llvm::cl::desc("Prefix of line code for repository."), |
| llvm::cl::cat(ClangDocCategory)); |
| |
| static llvm::cl::opt<bool> FTimeTrace("ftime-trace", llvm::cl::desc(R"( |
| Turn on time profiler. Generates clang-doc-tracing.json)"), |
| llvm::cl::init(false), |
| llvm::cl::cat(ClangDocCategory)); |
| |
| enum OutputFormatTy { md, yaml, html, mustache }; |
| |
| static llvm::cl::opt<OutputFormatTy> FormatEnum( |
| "format", llvm::cl::desc("Format for outputted docs."), |
| llvm::cl::values(clEnumValN(OutputFormatTy::yaml, "yaml", |
| "Documentation in YAML format."), |
| clEnumValN(OutputFormatTy::md, "md", |
| "Documentation in MD format."), |
| clEnumValN(OutputFormatTy::html, "html", |
| "Documentation in HTML format."), |
| clEnumValN(OutputFormatTy::mustache, "mustache", |
| "Documentation in mustache HTML format")), |
| llvm::cl::init(OutputFormatTy::yaml), llvm::cl::cat(ClangDocCategory)); |
| |
| static llvm::ExitOnError ExitOnErr; |
| |
| static std::string getFormatString() { |
| switch (FormatEnum) { |
| case OutputFormatTy::yaml: |
| return "yaml"; |
| case OutputFormatTy::md: |
| return "md"; |
| case OutputFormatTy::html: |
| return "html"; |
| case OutputFormatTy::mustache: |
| return "mustache"; |
| } |
| llvm_unreachable("Unknown OutputFormatTy"); |
| } |
| |
| // This function isn't referenced outside its translation unit, but it |
| // can't use the "static" keyword because its address is used for |
| // GetMainExecutable (since some platforms don't support taking the |
| // address of main, and some platforms can't implement GetMainExecutable |
| // without being given the address of a function in the main executable). |
| static std::string getExecutablePath(const char *Argv0, void *MainAddr) { |
| return llvm::sys::fs::getMainExecutable(Argv0, MainAddr); |
| } |
| |
| static llvm::Error getAssetFiles(clang::doc::ClangDocContext &CDCtx) { |
| using DirIt = llvm::sys::fs::directory_iterator; |
| std::error_code FileErr; |
| llvm::SmallString<128> FilePath(UserAssetPath); |
| for (DirIt DirStart = DirIt(UserAssetPath, FileErr), DirEnd; |
| !FileErr && DirStart != DirEnd; DirStart.increment(FileErr)) { |
| FilePath = DirStart->path(); |
| if (llvm::sys::fs::is_regular_file(FilePath)) { |
| if (llvm::sys::path::extension(FilePath) == ".css") |
| CDCtx.UserStylesheets.insert(CDCtx.UserStylesheets.begin(), |
| std::string(FilePath)); |
| else if (llvm::sys::path::extension(FilePath) == ".js") |
| CDCtx.JsScripts.emplace_back(FilePath.str()); |
| } |
| } |
| if (FileErr) |
| return llvm::createFileError(FilePath, FileErr); |
| return llvm::Error::success(); |
| } |
| |
| static llvm::Error getDefaultAssetFiles(const char *Argv0, |
| clang::doc::ClangDocContext &CDCtx) { |
| void *MainAddr = (void *)(intptr_t)getExecutablePath; |
| std::string ClangDocPath = getExecutablePath(Argv0, MainAddr); |
| llvm::SmallString<128> NativeClangDocPath; |
| llvm::sys::path::native(ClangDocPath, NativeClangDocPath); |
| |
| llvm::SmallString<128> AssetsPath; |
| AssetsPath = llvm::sys::path::parent_path(NativeClangDocPath); |
| llvm::sys::path::append(AssetsPath, "..", "share", "clang-doc"); |
| llvm::SmallString<128> DefaultStylesheet = |
| appendPathNative(AssetsPath, "clang-doc-default-stylesheet.css"); |
| llvm::SmallString<128> IndexJS = appendPathNative(AssetsPath, "index.js"); |
| |
| if (!llvm::sys::fs::is_regular_file(IndexJS)) |
| return llvm::createStringError(llvm::inconvertibleErrorCode(), |
| "default index.js file missing at " + |
| IndexJS + "\n"); |
| |
| if (!llvm::sys::fs::is_regular_file(DefaultStylesheet)) |
| return llvm::createStringError( |
| llvm::inconvertibleErrorCode(), |
| "default clang-doc-default-stylesheet.css file missing at " + |
| DefaultStylesheet + "\n"); |
| |
| CDCtx.UserStylesheets.insert(CDCtx.UserStylesheets.begin(), |
| std::string(DefaultStylesheet)); |
| CDCtx.JsScripts.emplace_back(IndexJS.str()); |
| |
| return llvm::Error::success(); |
| } |
| |
| static llvm::Error getHtmlAssetFiles(const char *Argv0, |
| clang::doc::ClangDocContext &CDCtx) { |
| if (!UserAssetPath.empty() && |
| !llvm::sys::fs::is_directory(std::string(UserAssetPath))) |
| llvm::outs() << "Asset path supply is not a directory: " << UserAssetPath |
| << " falling back to default\n"; |
| if (llvm::sys::fs::is_directory(std::string(UserAssetPath))) |
| return getAssetFiles(CDCtx); |
| return getDefaultAssetFiles(Argv0, CDCtx); |
| } |
| |
| static llvm::Error getMustacheHtmlFiles(const char *Argv0, |
| clang::doc::ClangDocContext &CDCtx) { |
| bool IsDir = llvm::sys::fs::is_directory(UserAssetPath); |
| if (!UserAssetPath.empty() && !IsDir) |
| llvm::outs() << "Asset path supply is not a directory: " << UserAssetPath |
| << " falling back to default\n"; |
| if (IsDir) { |
| getMustacheHtmlFiles(UserAssetPath, CDCtx); |
| return llvm::Error::success(); |
| } |
| void *MainAddr = (void *)(intptr_t)getExecutablePath; |
| std::string ClangDocPath = getExecutablePath(Argv0, MainAddr); |
| llvm::SmallString<128> NativeClangDocPath; |
| llvm::sys::path::native(ClangDocPath, NativeClangDocPath); |
| |
| llvm::SmallString<128> AssetsPath; |
| AssetsPath = llvm::sys::path::parent_path(NativeClangDocPath); |
| llvm::sys::path::append(AssetsPath, "..", "share", "clang-doc"); |
| |
| getMustacheHtmlFiles(AssetsPath, CDCtx); |
| |
| return llvm::Error::success(); |
| } |
| |
| /// Make the output of clang-doc deterministic by sorting the children of |
| /// namespaces and records. |
| static void |
| sortUsrToInfo(llvm::StringMap<std::unique_ptr<doc::Info>> &USRToInfo) { |
| for (auto &I : USRToInfo) { |
| auto &Info = I.second; |
| if (Info->IT == doc::InfoType::IT_namespace) { |
| auto *Namespace = static_cast<clang::doc::NamespaceInfo *>(Info.get()); |
| Namespace->Children.sort(); |
| } |
| if (Info->IT == doc::InfoType::IT_record) { |
| auto *Record = static_cast<clang::doc::RecordInfo *>(Info.get()); |
| Record->Children.sort(); |
| } |
| } |
| } |
| |
| static llvm::Error handleMappingFailures(llvm::Error Err) { |
| if (!Err) |
| return llvm::Error::success(); |
| if (IgnoreMappingFailures) { |
| llvm::errs() << "Error mapping decls in files. Clang-doc will ignore these " |
| "files and continue:\n" |
| << toString(std::move(Err)) << "\n"; |
| return llvm::Error::success(); |
| } |
| return Err; |
| } |
| |
| static llvm::Error createDirectories(llvm::StringRef OutDirectory) { |
| if (std::error_code Err = llvm::sys::fs::create_directories(OutDirectory)) |
| return llvm::createFileError(OutDirectory, Err, |
| "failed to create directory."); |
| return llvm::Error::success(); |
| } |
| |
| int main(int argc, const char **argv) { |
| llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); |
| std::error_code OK; |
| |
| ExitOnErr.setBanner("clang-doc error: "); |
| |
| const char *Overview = |
| R"(Generates documentation from source code and comments. |
| |
| Example usage for files without flags (default): |
| |
| $ clang-doc File1.cpp File2.cpp ... FileN.cpp |
| |
| Example usage for a project using a compile commands database: |
| |
| $ clang-doc --executor=all-TUs compile_commands.json |
| )"; |
| |
| auto Executor = ExitOnErr(clang::tooling::createExecutorFromCommandLineArgs( |
| argc, argv, ClangDocCategory, Overview)); |
| |
| // turns on ftime trace profiling |
| if (FTimeTrace) |
| llvm::timeTraceProfilerInitialize(200, "clang-doc"); |
| { |
| llvm::TimeTraceScope("main"); |
| |
| // Fail early if an invalid format was provided. |
| std::string Format = getFormatString(); |
| llvm::outs() << "Emiting docs in " << Format << " format.\n"; |
| auto G = ExitOnErr(doc::findGeneratorByName(Format)); |
| |
| ArgumentsAdjuster ArgAdjuster; |
| if (!DoxygenOnly) |
| ArgAdjuster = combineAdjusters( |
| getInsertArgumentAdjuster("-fparse-all-comments", |
| tooling::ArgumentInsertPosition::END), |
| ArgAdjuster); |
| |
| clang::doc::ClangDocContext CDCtx = { |
| Executor->getExecutionContext(), |
| ProjectName, |
| PublicOnly, |
| OutDirectory, |
| SourceRoot, |
| RepositoryUrl, |
| RepositoryCodeLinePrefix, |
| BaseDirectory, |
| {UserStylesheets.begin(), UserStylesheets.end()}, |
| FTimeTrace}; |
| |
| if (Format == "html") { |
| ExitOnErr(getHtmlAssetFiles(argv[0], CDCtx)); |
| } else if (Format == "mustache") { |
| ExitOnErr(getMustacheHtmlFiles(argv[0], CDCtx)); |
| } |
| |
| llvm::timeTraceProfilerBegin("Executor Launch", "total runtime"); |
| // Mapping phase |
| llvm::outs() << "Mapping decls...\n"; |
| ExitOnErr(handleMappingFailures( |
| Executor->execute(doc::newMapperActionFactory(CDCtx), ArgAdjuster))); |
| llvm::timeTraceProfilerEnd(); |
| |
| // Collect values into output by key. |
| // In ToolResults, the Key is the hashed USR and the value is the |
| // bitcode-encoded representation of the Info object. |
| llvm::timeTraceProfilerBegin("Collect Info", "total runtime"); |
| llvm::outs() << "Collecting infos...\n"; |
| llvm::StringMap<std::vector<StringRef>> USRToBitcode; |
| Executor->getToolResults()->forEachResult( |
| [&](StringRef Key, StringRef Value) { |
| USRToBitcode[Key].emplace_back(Value); |
| }); |
| llvm::timeTraceProfilerEnd(); |
| |
| // Collects all Infos according to their unique USR value. This map is added |
| // to from the thread pool below and is protected by the USRToInfoMutex. |
| llvm::sys::Mutex USRToInfoMutex; |
| llvm::StringMap<std::unique_ptr<doc::Info>> USRToInfo; |
| |
| // First reducing phase (reduce all decls into one info per decl). |
| llvm::outs() << "Reducing " << USRToBitcode.size() << " infos...\n"; |
| std::atomic<bool> Error; |
| Error = false; |
| llvm::sys::Mutex IndexMutex; |
| // ExecutorConcurrency is a flag exposed by AllTUsExecution.h |
| llvm::DefaultThreadPool Pool( |
| llvm::hardware_concurrency(ExecutorConcurrency)); |
| { |
| llvm::TimeTraceScope TS("Reduce"); |
| for (auto &Group : USRToBitcode) { |
| Pool.async([&]() { // time trace decoding bitcode |
| if (FTimeTrace) |
| llvm::timeTraceProfilerInitialize(200, "clang-doc"); |
| |
| std::vector<std::unique_ptr<doc::Info>> Infos; |
| { |
| llvm::TimeTraceScope Red("decoding bitcode"); |
| for (auto &Bitcode : Group.getValue()) { |
| llvm::BitstreamCursor Stream(Bitcode); |
| doc::ClangDocBitcodeReader Reader(Stream); |
| auto ReadInfos = Reader.readBitcode(); |
| if (!ReadInfos) { |
| llvm::errs() << toString(ReadInfos.takeError()) << "\n"; |
| Error = true; |
| return; |
| } |
| std::move(ReadInfos->begin(), ReadInfos->end(), |
| std::back_inserter(Infos)); |
| } |
| } // time trace decoding bitcode |
| |
| std::unique_ptr<doc::Info> Reduced; |
| |
| { |
| llvm::TimeTraceScope Merge("merging bitcode"); |
| auto ExpReduced = doc::mergeInfos(Infos); |
| |
| if (!ExpReduced) { |
| llvm::errs() << llvm::toString(ExpReduced.takeError()); |
| return; |
| } |
| Reduced = std::move(*ExpReduced); |
| } // time trace merging bitcode |
| |
| // Add a reference to this Info in the Index |
| { |
| llvm::TimeTraceScope Merge("addInfoToIndex"); |
| std::lock_guard<llvm::sys::Mutex> Guard(IndexMutex); |
| clang::doc::Generator::addInfoToIndex(CDCtx.Idx, Reduced.get()); |
| } |
| // Save in the result map (needs a lock due to threaded access). |
| { |
| llvm::TimeTraceScope Merge("USRToInfo"); |
| std::lock_guard<llvm::sys::Mutex> Guard(USRToInfoMutex); |
| USRToInfo[Group.getKey()] = std::move(Reduced); |
| } |
| |
| if (CDCtx.FTimeTrace) |
| llvm::timeTraceProfilerFinishThread(); |
| }); |
| } |
| |
| Pool.wait(); |
| } // time trace reduce |
| |
| if (Error) |
| return 1; |
| |
| { |
| llvm::TimeTraceScope Sort("Sort USRToInfo"); |
| sortUsrToInfo(USRToInfo); |
| } |
| |
| llvm::timeTraceProfilerBegin("Writing output", "total runtime"); |
| // Ensure the root output directory exists. |
| ExitOnErr(createDirectories(OutDirectory)); |
| |
| // Run the generator. |
| llvm::outs() << "Generating docs...\n"; |
| |
| ExitOnErr(G->generateDocs(OutDirectory, std::move(USRToInfo), CDCtx)); |
| llvm::outs() << "Generating assets for docs...\n"; |
| ExitOnErr(G->createResources(CDCtx)); |
| llvm::timeTraceProfilerEnd(); |
| } // time trace main |
| |
| if (FTimeTrace) { |
| std::error_code EC; |
| llvm::raw_fd_ostream OS("clang-doc-tracing.json", EC, |
| llvm::sys::fs::OF_Text); |
| if (!EC) { |
| llvm::timeTraceProfilerWrite(OS); |
| llvm::timeTraceProfilerCleanup(); |
| } else |
| return 1; |
| } |
| return 0; |
| } |