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

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang-tools-extra/clang-doc/tool/ClangDocMain.cpp