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/Basic/Diagnostic.h"
26#include "clang/Basic/DiagnosticOptions.h"
27#include "clang/Frontend/TextDiagnosticPrinter.h"
28#include "clang/Tooling/AllTUsExecution.h"
29#include "clang/Tooling/CommonOptionsParser.h"
30#include "clang/Tooling/Execution.h"
31#include "llvm/ADT/APFloat.h"
32#include "llvm/Support/CommandLine.h"
33#include "llvm/Support/Error.h"
34#include "llvm/Support/FileSystem.h"
35#include "llvm/Support/Mutex.h"
36#include "llvm/Support/Path.h"
37#include "llvm/Support/Process.h"
38#include "llvm/Support/Signals.h"
39#include "llvm/Support/ThreadPool.h"
40#include "llvm/Support/TimeProfiler.h"
41#include "llvm/Support/raw_ostream.h"
42#include <atomic>
43#include <mutex>
44#include <string>
45
46using namespace clang::tooling;
47using namespace clang;
48
49static llvm::cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage);
50static llvm::cl::OptionCategory ClangDocCategory("clang-doc options");
51
52static llvm::cl::opt<std::string>
53 ProjectName("project-name", llvm::cl::desc("Name of project."),
54 llvm::cl::cat(ClangDocCategory));
55
56static llvm::cl::opt<bool> IgnoreMappingFailures(
57 "ignore-map-errors",
58 llvm::cl::desc("Continue if files are not mapped correctly."),
59 llvm::cl::init(true), llvm::cl::cat(ClangDocCategory));
60
61static llvm::cl::opt<std::string>
62 OutDirectory("output",
63 llvm::cl::desc("Directory for outputting generated files."),
64 llvm::cl::init("docs"), llvm::cl::cat(ClangDocCategory));
65
66static llvm::cl::opt<std::string>
68 llvm::cl::desc(R"(Base Directory for generated documentation.
69URLs will be rooted at this directory for HTML links.)"),
70 llvm::cl::init(""), llvm::cl::cat(ClangDocCategory));
71
72static llvm::cl::opt<bool>
73 PublicOnly("public", llvm::cl::desc("Document only public declarations."),
74 llvm::cl::init(false), llvm::cl::cat(ClangDocCategory));
76static llvm::cl::opt<bool> DoxygenOnly(
77 "doxygen",
78 llvm::cl::desc("Use only doxygen-style comments to generate docs."),
79 llvm::cl::init(false), llvm::cl::cat(ClangDocCategory));
81static llvm::cl::list<std::string> UserStylesheets(
82 "stylesheets", llvm::cl::CommaSeparated,
83 llvm::cl::desc("CSS stylesheets to extend the default styles."),
84 llvm::cl::cat(ClangDocCategory));
86static llvm::cl::opt<std::string> UserAssetPath(
87 "asset",
88 llvm::cl::desc("User supplied asset path to "
89 "override the default css and js files for html output"),
90 llvm::cl::cat(ClangDocCategory));
92static llvm::cl::opt<std::string> SourceRoot("source-root", llvm::cl::desc(R"(
93Directory where processed files are stored.
94Links to definition locations will only be
95generated if the file is in this dir.)"),
96 llvm::cl::cat(ClangDocCategory));
97
98static llvm::cl::opt<std::string>
99 RepositoryUrl("repository", llvm::cl::desc(R"(
100URL of repository that hosts code.
101Used for links to definition locations.)"),
102 llvm::cl::cat(ClangDocCategory));
104static llvm::cl::opt<std::string> RepositoryCodeLinePrefix(
105 "repository-line-prefix",
106 llvm::cl::desc("Prefix of line code for repository."),
107 llvm::cl::cat(ClangDocCategory));
108
109static llvm::cl::opt<bool> FTimeTrace("ftime-trace", llvm::cl::desc(R"(
110Turn on time profiler. Generates clang-doc-tracing.json)"),
111 llvm::cl::init(false),
112 llvm::cl::cat(ClangDocCategory));
113
114enum OutputFormatTy { md, yaml, html, json };
115
116static llvm::cl::opt<OutputFormatTy>
117 FormatEnum("format", llvm::cl::desc("Format for outputted docs."),
118 llvm::cl::values(clEnumValN(OutputFormatTy::yaml, "yaml",
119 "Documentation in YAML format."),
120 clEnumValN(OutputFormatTy::md, "md",
121 "Documentation in MD format."),
122 clEnumValN(OutputFormatTy::html, "html",
123 "Documentation in HTML format."),
124 clEnumValN(OutputFormatTy::json, "json",
125 "Documentation in JSON format")),
126 llvm::cl::init(OutputFormatTy::yaml),
127 llvm::cl::cat(ClangDocCategory));
128
129static llvm::ExitOnError ExitOnErr;
130
131static std::string getFormatString() {
132 switch (FormatEnum) {
134 return "yaml";
136 return "md";
138 return "html";
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
154// TODO: Rename this, since it only gets custom CSS/JS
155static llvm::Error getAssetFiles(clang::doc::ClangDocContext &CDCtx) {
156 using DirIt = llvm::sys::fs::directory_iterator;
157 std::error_code FileErr;
158 llvm::SmallString<128> FilePath(UserAssetPath);
159 for (DirIt DirStart = DirIt(UserAssetPath, FileErr), DirEnd;
160 !FileErr && DirStart != DirEnd; DirStart.increment(FileErr)) {
161 FilePath = DirStart->path();
162 if (llvm::sys::fs::is_regular_file(FilePath)) {
163 if (llvm::sys::path::extension(FilePath) == ".css")
164 CDCtx.UserStylesheets.insert(CDCtx.UserStylesheets.begin(),
165 std::string(FilePath));
166 else if (llvm::sys::path::extension(FilePath) == ".js")
167 CDCtx.JsScripts.emplace_back(FilePath.str());
169 }
170 if (FileErr)
171 return llvm::createFileError(FilePath, FileErr);
172 return llvm::Error::success();
173}
174
175static llvm::Error getHtmlFiles(const char *Argv0,
177 bool IsDir = llvm::sys::fs::is_directory(UserAssetPath);
178 if (!UserAssetPath.empty() && !IsDir)
179 llvm::outs() << "Asset path supply is not a directory: " << UserAssetPath
180 << " falling back to default\n";
181 if (IsDir) {
182 if (auto Err = getAssetFiles(CDCtx))
183 return Err;
184 }
185 void *MainAddr = (void *)(intptr_t)getExecutablePath;
186 std::string ClangDocPath = getExecutablePath(Argv0, MainAddr);
187 llvm::SmallString<128> NativeClangDocPath;
188 llvm::sys::path::native(ClangDocPath, NativeClangDocPath);
189
190 llvm::SmallString<128> AssetsPath;
191 AssetsPath = llvm::sys::path::parent_path(NativeClangDocPath);
192 llvm::sys::path::append(AssetsPath, "..", "share", "clang-doc");
193
194 getHtmlFiles(AssetsPath, CDCtx);
196 return llvm::Error::success();
197}
198
199/// Make the output of clang-doc deterministic by sorting the children of
200/// namespaces and records.
201static void
202sortUsrToInfo(llvm::StringMap<std::unique_ptr<doc::Info>> &USRToInfo) {
203 for (auto &I : USRToInfo) {
204 auto &Info = I.second;
206 auto *Namespace = static_cast<clang::doc::NamespaceInfo *>(Info.get());
207 Namespace->Children.sort();
208 }
209 if (Info->IT == doc::InfoType::IT_record) {
210 auto *Record = static_cast<clang::doc::RecordInfo *>(Info.get());
211 Record->Children.sort();
212 }
213 }
214}
215
216static llvm::Error handleMappingFailures(DiagnosticsEngine &Diags,
217 llvm::Error Err) {
218 if (!Err)
219 return llvm::Error::success();
221 unsigned ID = Diags.getCustomDiagID(
222 DiagnosticsEngine::Warning,
223 "Error mapping decls in files. Clang-doc will ignore these files and "
224 "continue:\n%0");
225 Diags.Report(ID) << toString(std::move(Err));
226 return llvm::Error::success();
227 }
228 return Err;
229}
230
231static llvm::Error createDirectories(llvm::StringRef OutDirectory) {
232 if (std::error_code Err = llvm::sys::fs::create_directories(OutDirectory))
233 return llvm::createFileError(OutDirectory, Err,
234 "failed to create directory.");
235 return llvm::Error::success();
236}
237
238int main(int argc, const char **argv) {
239 llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
240 std::error_code OK;
241
242 ExitOnErr.setBanner("clang-doc error: ");
243
244 const char *Overview =
245 R"(Generates documentation from source code and comments.
246
247Example usage for files without flags (default):
248
249 $ clang-doc File1.cpp File2.cpp ... FileN.cpp
250
251Example usage for a project using a compile commands database:
252
253 $ clang-doc --executor=all-TUs compile_commands.json
254)";
255
256 auto Executor = ExitOnErr(clang::tooling::createExecutorFromCommandLineArgs(
257 argc, argv, ClangDocCategory, Overview));
258
259 // turns on ftime trace profiling
260 if (FTimeTrace)
261 llvm::timeTraceProfilerInitialize(200, "clang-doc");
262 {
263 llvm::TimeTraceScope("main");
264
265 // Fail early if an invalid format was provided.
266 std::string Format = getFormatString();
267 llvm::outs() << "Emiting docs in " << Format << " format.\n";
268 auto G = ExitOnErr(doc::findGeneratorByName(Format));
269
270 ArgumentsAdjuster ArgAdjuster;
271 if (!DoxygenOnly)
272 ArgAdjuster = combineAdjusters(
273 getInsertArgumentAdjuster("-fparse-all-comments",
274 tooling::ArgumentInsertPosition::END),
275 ArgAdjuster);
276
277 auto DiagOpts = std::make_unique<DiagnosticOptions>();
278 TextDiagnosticPrinter *DiagClient =
279 new TextDiagnosticPrinter(llvm::errs(), *DiagOpts);
280 IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
281 DiagnosticsEngine Diags(DiagID, *DiagOpts, DiagClient);
282
284 Executor->getExecutionContext(), ProjectName, PublicOnly, OutDirectory,
286 {UserStylesheets.begin(), UserStylesheets.end()}, Diags, FTimeTrace);
287
288 if (Format == "html")
289 ExitOnErr(getHtmlFiles(argv[0], CDCtx));
290
291 llvm::timeTraceProfilerBegin("Executor Launch", "total runtime");
292 // Mapping phase
293 llvm::outs() << "Mapping decls...\n";
295 Diags,
296 Executor->execute(doc::newMapperActionFactory(CDCtx), ArgAdjuster)));
297 llvm::timeTraceProfilerEnd();
298
299 // Collect values into output by key.
300 // In ToolResults, the Key is the hashed USR and the value is the
301 // bitcode-encoded representation of the Info object.
302 llvm::timeTraceProfilerBegin("Collect Info", "total runtime");
303 llvm::outs() << "Collecting infos...\n";
304 llvm::StringMap<std::vector<StringRef>> USRToBitcode;
305 Executor->getToolResults()->forEachResult(
306 [&](StringRef Key, StringRef Value) {
307 USRToBitcode[Key].emplace_back(Value);
308 });
309 llvm::timeTraceProfilerEnd();
310
311 // Collects all Infos according to their unique USR value. This map is added
312 // to from the thread pool below and is protected by the USRToInfoMutex.
313 llvm::sys::Mutex USRToInfoMutex;
314 llvm::StringMap<std::unique_ptr<doc::Info>> USRToInfo;
315
316 // First reducing phase (reduce all decls into one info per decl).
317 llvm::outs() << "Reducing " << USRToBitcode.size() << " infos...\n";
318 std::atomic<bool> Error;
319 Error = false;
320 llvm::sys::Mutex IndexMutex;
321 llvm::sys::Mutex DiagMutex;
322 unsigned DiagIDBitcodeReading = Diags.getCustomDiagID(
323 DiagnosticsEngine::Error, "error reading bitcode: %0");
324 unsigned DiagIDBitcodeMerging = Diags.getCustomDiagID(
325 DiagnosticsEngine::Error, "error merging bitcode: %0");
326 // ExecutorConcurrency is a flag exposed by AllTUsExecution.h
327 llvm::DefaultThreadPool Pool(
328 llvm::hardware_concurrency(ExecutorConcurrency));
329 {
330 llvm::TimeTraceScope TS("Reduce");
331 for (auto &Group : USRToBitcode) {
332 Pool.async([&, &Diags = Diags]() { // time trace decoding bitcode
333 if (FTimeTrace)
334 llvm::timeTraceProfilerInitialize(200, "clang-doc");
335
336 std::vector<std::unique_ptr<doc::Info>> Infos;
337 {
338 llvm::TimeTraceScope Red("decoding bitcode");
339 for (auto &Bitcode : Group.getValue()) {
340 llvm::BitstreamCursor Stream(Bitcode);
341 doc::ClangDocBitcodeReader Reader(Stream, Diags);
342 auto ReadInfos = Reader.readBitcode();
343 if (!ReadInfos) {
344 std::lock_guard<llvm::sys::Mutex> Guard(DiagMutex);
345
346 Diags.Report(DiagIDBitcodeReading)
347 << toString(ReadInfos.takeError());
348 Error = true;
349 return;
350 }
351 std::move(ReadInfos->begin(), ReadInfos->end(),
352 std::back_inserter(Infos));
353 }
354 } // time trace decoding bitcode
355
356 std::unique_ptr<doc::Info> Reduced;
357
358 {
359 llvm::TimeTraceScope Merge("merging bitcode");
360 auto ExpReduced = doc::mergeInfos(Infos);
361
362 if (!ExpReduced) {
363 std::lock_guard<llvm::sys::Mutex> Guard(DiagMutex);
364 Diags.Report(DiagIDBitcodeMerging)
365 << toString(ExpReduced.takeError());
366 return;
367 }
368 Reduced = std::move(*ExpReduced);
369 } // time trace merging bitcode
370
371 // Add a reference to this Info in the Index
372 {
373 llvm::TimeTraceScope Merge("addInfoToIndex");
374 std::lock_guard<llvm::sys::Mutex> Guard(IndexMutex);
375 clang::doc::Generator::addInfoToIndex(CDCtx.Idx, Reduced.get());
376 }
377 // Save in the result map (needs a lock due to threaded access).
378 {
379 llvm::TimeTraceScope Merge("USRToInfo");
380 std::lock_guard<llvm::sys::Mutex> Guard(USRToInfoMutex);
381 USRToInfo[Group.getKey()] = std::move(Reduced);
382 }
383
384 if (CDCtx.FTimeTrace)
385 llvm::timeTraceProfilerFinishThread();
386 });
387 }
388
389 Pool.wait();
390 } // time trace reduce
391
392 if (Error)
393 return 1;
394
395 {
396 llvm::TimeTraceScope Sort("Sort USRToInfo");
397 sortUsrToInfo(USRToInfo);
398 }
399
400 llvm::timeTraceProfilerBegin("Writing output", "total runtime");
401 // Ensure the root output directory exists.
403
404 // Run the generator.
405 llvm::outs() << "Generating docs...\n";
406
407 ExitOnErr(
408 G->generateDocumentation(OutDirectory, std::move(USRToInfo), CDCtx));
409 llvm::outs() << "Generating assets for docs...\n";
410 ExitOnErr(G->createResources(CDCtx));
411 llvm::timeTraceProfilerEnd();
412 } // time trace main
413
414 if (FTimeTrace) {
415 std::error_code EC;
416 llvm::raw_fd_ostream OS("clang-doc-tracing.json", EC,
417 llvm::sys::fs::OF_Text);
418 if (!EC) {
419 llvm::timeTraceProfilerWrite(OS);
420 llvm::timeTraceProfilerCleanup();
421 } else
422 return 1;
423 }
424 return 0;
425}
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 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::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::json, "json", "Documentation in JSON format")), llvm::cl::init(OutputFormatTy::yaml), llvm::cl::cat(ClangDocCategory))
static llvm::Error getHtmlFiles(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
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 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 handleMappingFailures(DiagnosticsEngine &Diags, llvm::Error Err)
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
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