clang-tools 22.0.0git
ClangDocMain.cpp
Go to the documentation of this file.
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-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This tool for generating C and C++ documentation from source code
10// and comments. Generally, it runs a LibTooling FrontendAction on source files,
11// mapping each declaration in those files to its USR and serializing relevant
12// information into LLVM bitcode. It then runs a pass over the collected
13// declaration information, reducing by USR. There is an option to dump this
14// intermediate result to bitcode. Finally, it hands the reduced information
15// off to a generator, which does the final parsing from the intermediate
16// 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>
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));
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));
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));
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));
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 be
92generated 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));
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) {
132 return "yaml";
134 return "md";
136 return "html";
138 return "mustache";
140 return "json";
141 }
142 llvm_unreachable("Unknown OutputFormatTy");
144
145// This function isn't referenced outside its translation unit, but it
146// can't use the "static" keyword because its address is used for
147// GetMainExecutable (since some platforms don't support taking the
148// address of main, and some platforms can't implement GetMainExecutable
149// 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());
168 }
169 if (FileErr)
170 return llvm::createFileError(FilePath, FileErr);
171 return llvm::Error::success();
172}
173
174static llvm::Error getDefaultAssetFiles(const char *Argv0,
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,
208 if (!UserAssetPath.empty() &&
209 !llvm::sys::fs::is_directory(std::string(UserAssetPath)))
210 llvm::outs() << "Asset path supply is not a directory: " << UserAssetPath
211 << " 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,
219 bool IsDir = llvm::sys::fs::is_directory(UserAssetPath);
220 if (!UserAssetPath.empty() && !IsDir)
221 llvm::outs() << "Asset path supply is not a directory: " << UserAssetPath
222 << " falling back to default\n";
223 if (IsDir) {
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);
238 return llvm::Error::success();
239}
240
241/// Make the output of clang-doc deterministic by sorting the children of
242/// namespaces and records.
243static void
244sortUsrToInfo(llvm::StringMap<std::unique_ptr<doc::Info>> &USRToInfo) {
245 for (auto &I : USRToInfo) {
246 auto &Info = I.second;
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();
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.cpp
289
290Example usage for a project using a compile commands database:
291
292 $ clang-doc --executor=all-TUs compile_commands.json
293)";
294
295 auto Executor = ExitOnErr(clang::tooling::createExecutorFromCommandLineArgs(
296 argc, argv, ClangDocCategory, Overview));
297
298 // turns on ftime trace profiling
299 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
317 Executor->getExecutionContext(),
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 phase
336 llvm::outs() << "Mapping decls...\n";
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 the
343 // 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 added
354 // 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.h
364 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 bitcode
370 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 bitcode
389
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 bitcode
402
403 // Add a reference to this Info in the Index
404 {
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 reduce
423
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.
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 main
445
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 } else
454 return 1;
455 }
456 return 0;
457}
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::extrahelp CommonHelp(CommonOptionsParser::HelpMessage)
static std::string getExecutablePath(const char *Argv0, void *MainAddr)
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< std::string > RepositoryCodeLinePrefix("repository-line-prefix", llvm::cl::desc("Prefix of line code for repository."), llvm::cl::cat(ClangDocCategory))
int main(int argc, const char **argv)
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"), clEnumValN(OutputFormatTy::json, "json", "Documentation in JSON format")), llvm::cl::init(OutputFormatTy::yaml), llvm::cl::cat(ClangDocCategory))
static void sortUsrToInfo(llvm::StringMap< std::unique_ptr< doc::Info > > &USRToInfo)
Make the output of clang-doc deterministic by sorting the children of namespaces and records.
static llvm::cl::opt< std::string > ProjectName("project-name", llvm::cl::desc("Name of project."), llvm::cl::cat(ClangDocCategory))
static llvm::Error handleMappingFailures(llvm::Error Err)
static llvm::Error getHtmlAssetFiles(const char *Argv0, clang::doc::ClangDocContext &CDCtx)
static llvm::Error createDirectories(llvm::StringRef OutDirectory)
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))
OutputFormatTy
@ json
@ yaml
@ html
@ md
@ mustache
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::ExitOnError ExitOnErr
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 std::string getFormatString()
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 > 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::Error getMustacheHtmlFiles(const char *Argv0, clang::doc::ClangDocContext &CDCtx)
static llvm::Error getAssetFiles(clang::doc::ClangDocContext &CDCtx)
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::Error getDefaultAssetFiles(const char *Argv0, clang::doc::ClangDocContext &CDCtx)
static llvm::cl::OptionCategory ClangDocCategory("clang-doc options")
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))
const char * Argv0
SmallString< 128 > appendPathNative(StringRef Base, StringRef Path)
Definition Utils.cpp:17
static void addInfoToIndex(Index &Idx, const doc::Info *Info)
@ Info
An information message.
Definition Protocol.h:738
@ Error
An error message.
Definition Protocol.h:734
llvm::Expected< std::unique_ptr< Info > > mergeInfos(std::vector< std::unique_ptr< Info > > &Values)
std::unique_ptr< tooling::FrontendActionFactory > newMapperActionFactory(ClangDocContext CDCtx)
Definition ClangDoc.cpp:52
llvm::Expected< std::unique_ptr< Generator > > findGeneratorByName(llvm::StringRef Format)
bool Merge(llvm::StringRef MergeDir, llvm::StringRef OutputFile)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
std::vector< std::string > UserStylesheets
std::vector< std::string > JsScripts