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