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 | |
44 | using namespace clang::ast_matchers; |
45 | using namespace clang::tooling; |
46 | using namespace clang; |
47 | |
48 | static llvm::cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); |
49 | static llvm::cl::OptionCategory ClangDocCategory("clang-doc options"); |
50 | |
51 | static llvm::cl::opt<std::string> |
52 | ProjectName("project-name", llvm::cl::desc( "Name of project."), |
53 | llvm::cl::cat(ClangDocCategory)); |
54 | |
55 | static 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 | |
60 | static 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 | |
65 | static llvm::cl::opt<std::string> |
66 | BaseDirectory("base", |
67 | llvm::cl::desc(R"(Base Directory for generated documentation. |
68 | URLs will be rooted at this directory for HTML links.)"), |
69 | llvm::cl::init(Val: ""), llvm::cl::cat(ClangDocCategory)); |
70 | |
71 | static 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 | |
75 | static 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 | |
80 | static 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 | |
85 | static 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 | |
91 | static llvm::cl::opt<std::string> SourceRoot("source-root", llvm::cl::desc( R"( |
92 | Directory where processed files are stored. |
93 | Links to definition locations will only be |
94 | generated if the file is in this dir.)"), |
95 | llvm::cl::cat(ClangDocCategory)); |
96 | |
97 | static llvm::cl::opt<std::string> |
98 | RepositoryUrl("repository", llvm::cl::desc( R"( |
99 | URL of repository that hosts code. |
100 | Used for links to definition locations.)"), |
101 | llvm::cl::cat(ClangDocCategory)); |
102 | |
103 | static 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 | |
108 | static llvm::cl::opt<bool> FTimeTrace("ftime-trace", llvm::cl::desc( R"( |
109 | Turn on time profiler. Generates clang-doc-tracing.json)"), |
110 | llvm::cl::init(Val: false), |
111 | llvm::cl::cat(ClangDocCategory)); |
112 | |
113 | enum OutputFormatTy { md, yaml, html, mustache, json }; |
114 | |
115 | static 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 | |
129 | static llvm::ExitOnError ExitOnErr; |
130 | |
131 | static 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). |
152 | static std::string getExecutablePath(const char *Argv0, void *MainAddr) { |
153 | return llvm::sys::fs::getMainExecutable(argv0: Argv0, MainExecAddr: MainAddr); |
154 | } |
155 | |
156 | static 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 | |
176 | static 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 | |
208 | static 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 | |
219 | static 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. |
245 | static void |
246 | sortUsrToInfo(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 | |
260 | static 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 | |
272 | static 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 | |
279 | int 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 | |
288 | Example usage for files without flags (default): |
289 | |
290 | $ clang-doc File1.cpp File2.cpp ... FileN.cpp |
291 | |
292 | Example 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 |
Definitions
- CommonHelp
- ClangDocCategory
- ProjectName
- IgnoreMappingFailures
- OutDirectory
- BaseDirectory
- PublicOnly
- DoxygenOnly
- UserStylesheets
- UserAssetPath
- SourceRoot
- RepositoryUrl
- RepositoryCodeLinePrefix
- FTimeTrace
- OutputFormatTy
- FormatEnum
- ExitOnErr
- getFormatString
- getExecutablePath
- getAssetFiles
- getDefaultAssetFiles
- getHtmlAssetFiles
- getMustacheHtmlFiles
- sortUsrToInfo
- handleMappingFailures
- createDirectories
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more