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 "BitcodeWriter.h"
22#include "ClangDoc.h"
23#include "Generators.h"
24#include "Representation.h"
25#include "clang/AST/AST.h"
26#include "clang/AST/Decl.h"
27#include "clang/ASTMatchers/ASTMatchFinder.h"
28#include "clang/ASTMatchers/ASTMatchersInternal.h"
29#include "clang/Driver/Options.h"
30#include "clang/Frontend/FrontendActions.h"
31#include "clang/Tooling/AllTUsExecution.h"
32#include "clang/Tooling/CommonOptionsParser.h"
33#include "clang/Tooling/Execution.h"
34#include "clang/Tooling/Tooling.h"
35#include "llvm/ADT/APFloat.h"
36#include "llvm/Support/CommandLine.h"
37#include "llvm/Support/Error.h"
38#include "llvm/Support/FileSystem.h"
39#include "llvm/Support/Mutex.h"
40#include "llvm/Support/Path.h"
41#include "llvm/Support/Process.h"
42#include "llvm/Support/Signals.h"
43#include "llvm/Support/ThreadPool.h"
44#include "llvm/Support/raw_ostream.h"
45#include <atomic>
46#include <mutex>
47#include <string>
48
49using namespace clang::ast_matchers;
50using namespace clang::tooling;
51using namespace clang;
52
53static llvm::cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage);
54static llvm::cl::OptionCategory ClangDocCategory("clang-doc options");
55
56static llvm::cl::opt<std::string>
57 ProjectName("project-name", llvm::cl::desc("Name of project."),
58 llvm::cl::cat(ClangDocCategory));
59
60static llvm::cl::opt<bool> IgnoreMappingFailures(
61 "ignore-map-errors",
62 llvm::cl::desc("Continue if files are not mapped correctly."),
63 llvm::cl::init(Val: true), llvm::cl::cat(ClangDocCategory));
64
65static llvm::cl::opt<std::string>
66 OutDirectory("output",
67 llvm::cl::desc("Directory for outputting generated files."),
68 llvm::cl::init(Val: "docs"), llvm::cl::cat(ClangDocCategory));
69
70static llvm::cl::opt<bool>
71 PublicOnly("public", llvm::cl::desc("Document only public declarations."),
72 llvm::cl::init(Val: false), llvm::cl::cat(ClangDocCategory));
73
74static llvm::cl::opt<bool> DoxygenOnly(
75 "doxygen",
76 llvm::cl::desc("Use only doxygen-style comments to generate docs."),
77 llvm::cl::init(Val: false), llvm::cl::cat(ClangDocCategory));
78
79static llvm::cl::list<std::string> UserStylesheets(
80 "stylesheets", llvm::cl::CommaSeparated,
81 llvm::cl::desc("CSS stylesheets to extend the default styles."),
82 llvm::cl::cat(ClangDocCategory));
83
84static llvm::cl::opt<std::string> SourceRoot("source-root", llvm::cl::desc(R"(
85Directory where processed files are stored.
86Links to definition locations will only be
87generated if the file is in this dir.)"),
88 llvm::cl::cat(ClangDocCategory));
89
90static llvm::cl::opt<std::string>
91 RepositoryUrl("repository", llvm::cl::desc(R"(
92URL of repository that hosts code.
93Used for links to definition locations.)"),
94 llvm::cl::cat(ClangDocCategory));
95
96enum OutputFormatTy {
97 md,
98 yaml,
99 html,
100};
101
102static llvm::cl::opt<OutputFormatTy>
103 FormatEnum("format", llvm::cl::desc("Format for outputted docs."),
104 llvm::cl::values(clEnumValN(OutputFormatTy::yaml, "yaml",
105 "Documentation in YAML format."),
106 clEnumValN(OutputFormatTy::md, "md",
107 "Documentation in MD format."),
108 clEnumValN(OutputFormatTy::html, "html",
109 "Documentation in HTML format.")),
110 llvm::cl::init(Val: OutputFormatTy::yaml),
111 llvm::cl::cat(ClangDocCategory));
112
113std::string getFormatString() {
114 switch (FormatEnum) {
115 case OutputFormatTy::yaml:
116 return "yaml";
117 case OutputFormatTy::md:
118 return "md";
119 case OutputFormatTy::html:
120 return "html";
121 }
122 llvm_unreachable("Unknown OutputFormatTy");
123}
124
125// This function isn't referenced outside its translation unit, but it
126// can't use the "static" keyword because its address is used for
127// GetMainExecutable (since some platforms don't support taking the
128// address of main, and some platforms can't implement GetMainExecutable
129// without being given the address of a function in the main executable).
130std::string GetExecutablePath(const char *Argv0, void *MainAddr) {
131 return llvm::sys::fs::getMainExecutable(argv0: Argv0, MainExecAddr: MainAddr);
132}
133
134int main(int argc, const char **argv) {
135 llvm::sys::PrintStackTraceOnErrorSignal(Argv0: argv[0]);
136 std::error_code OK;
137
138 const char *Overview =
139 R"(Generates documentation from source code and comments.
140
141Example usage for files without flags (default):
142
143 $ clang-doc File1.cpp File2.cpp ... FileN.cpp
144
145Example usage for a project using a compile commands database:
146
147 $ clang-doc --executor=all-TUs compile_commands.json
148)";
149
150 auto Executor = clang::tooling::createExecutorFromCommandLineArgs(
151 argc, argv, Category&: ClangDocCategory, Overview);
152
153 if (!Executor) {
154 llvm::errs() << toString(E: Executor.takeError()) << "\n";
155 return 1;
156 }
157
158 // Fail early if an invalid format was provided.
159 std::string Format = getFormatString();
160 llvm::outs() << "Emiting docs in " << Format << " format.\n";
161 auto G = doc::findGeneratorByName(Format);
162 if (!G) {
163 llvm::errs() << toString(E: G.takeError()) << "\n";
164 return 1;
165 }
166
167 ArgumentsAdjuster ArgAdjuster;
168 if (!DoxygenOnly)
169 ArgAdjuster = combineAdjusters(
170 First: getInsertArgumentAdjuster(Extra: "-fparse-all-comments",
171 Pos: tooling::ArgumentInsertPosition::END),
172 Second: ArgAdjuster);
173
174 clang::doc::ClangDocContext CDCtx = {
175 Executor->get()->getExecutionContext(),
176 ProjectName,
177 PublicOnly,
178 OutDirectory,
179 SourceRoot,
180 RepositoryUrl,
181 {UserStylesheets.begin(), UserStylesheets.end()},
182 {"index.js", "index_json.js"}};
183
184 if (Format == "html") {
185 void *MainAddr = (void *)(intptr_t)GetExecutablePath;
186 std::string ClangDocPath = GetExecutablePath(Argv0: argv[0], MainAddr);
187 llvm::SmallString<128> NativeClangDocPath;
188 llvm::sys::path::native(path: ClangDocPath, result&: NativeClangDocPath);
189 llvm::SmallString<128> AssetsPath;
190 AssetsPath = llvm::sys::path::parent_path(path: NativeClangDocPath);
191 llvm::sys::path::append(path&: AssetsPath, a: "..", b: "share", c: "clang");
192 llvm::SmallString<128> DefaultStylesheet;
193 llvm::sys::path::native(path: AssetsPath, result&: DefaultStylesheet);
194 llvm::sys::path::append(path&: DefaultStylesheet,
195 a: "clang-doc-default-stylesheet.css");
196 llvm::SmallString<128> IndexJS;
197 llvm::sys::path::native(path: AssetsPath, result&: IndexJS);
198 llvm::sys::path::append(path&: IndexJS, a: "index.js");
199 CDCtx.UserStylesheets.insert(position: CDCtx.UserStylesheets.begin(),
200 x: std::string(DefaultStylesheet));
201 CDCtx.FilesToCopy.emplace_back(args: IndexJS.str());
202 }
203
204 // Mapping phase
205 llvm::outs() << "Mapping decls...\n";
206 auto Err =
207 Executor->get()->execute(Action: doc::newMapperActionFactory(CDCtx), Adjuster: ArgAdjuster);
208 if (Err) {
209 if (IgnoreMappingFailures)
210 llvm::errs() << "Error mapping decls in files. Clang-doc will ignore "
211 "these files and continue:\n"
212 << toString(E: std::move(Err)) << "\n";
213 else {
214 llvm::errs() << toString(E: std::move(Err)) << "\n";
215 return 1;
216 }
217 }
218
219 // Collect values into output by key.
220 // In ToolResults, the Key is the hashed USR and the value is the
221 // bitcode-encoded representation of the Info object.
222 llvm::outs() << "Collecting infos...\n";
223 llvm::StringMap<std::vector<StringRef>> USRToBitcode;
224 Executor->get()->getToolResults()->forEachResult(
225 Callback: [&](StringRef Key, StringRef Value) {
226 auto R = USRToBitcode.try_emplace(Key, Args: std::vector<StringRef>());
227 R.first->second.emplace_back(args&: Value);
228 });
229
230 // Collects all Infos according to their unique USR value. This map is added
231 // to from the thread pool below and is protected by the USRToInfoMutex.
232 llvm::sys::Mutex USRToInfoMutex;
233 llvm::StringMap<std::unique_ptr<doc::Info>> USRToInfo;
234
235 // First reducing phase (reduce all decls into one info per decl).
236 llvm::outs() << "Reducing " << USRToBitcode.size() << " infos...\n";
237 std::atomic<bool> Error;
238 Error = false;
239 llvm::sys::Mutex IndexMutex;
240 // ExecutorConcurrency is a flag exposed by AllTUsExecution.h
241 llvm::DefaultThreadPool Pool(llvm::hardware_concurrency(ThreadCount: ExecutorConcurrency));
242 for (auto &Group : USRToBitcode) {
243 Pool.async(F: [&]() {
244 std::vector<std::unique_ptr<doc::Info>> Infos;
245
246 for (auto &Bitcode : Group.getValue()) {
247 llvm::BitstreamCursor Stream(Bitcode);
248 doc::ClangDocBitcodeReader Reader(Stream);
249 auto ReadInfos = Reader.readBitcode();
250 if (!ReadInfos) {
251 llvm::errs() << toString(E: ReadInfos.takeError()) << "\n";
252 Error = true;
253 return;
254 }
255 std::move(first: ReadInfos->begin(), last: ReadInfos->end(),
256 result: std::back_inserter(x&: Infos));
257 }
258
259 auto Reduced = doc::mergeInfos(Values&: Infos);
260 if (!Reduced) {
261 llvm::errs() << llvm::toString(E: Reduced.takeError());
262 return;
263 }
264
265 // Add a reference to this Info in the Index
266 {
267 std::lock_guard<llvm::sys::Mutex> Guard(IndexMutex);
268 clang::doc::Generator::addInfoToIndex(Idx&: CDCtx.Idx, Info: Reduced.get().get());
269 }
270
271 // Save in the result map (needs a lock due to threaded access).
272 {
273 std::lock_guard<llvm::sys::Mutex> Guard(USRToInfoMutex);
274 USRToInfo[Group.getKey()] = std::move(Reduced.get());
275 }
276 });
277 }
278
279 Pool.wait();
280
281 if (Error)
282 return 1;
283
284 // Ensure the root output directory exists.
285 if (std::error_code Err = llvm::sys::fs::create_directories(path: OutDirectory);
286 Err != std::error_code()) {
287 llvm::errs() << "Failed to create directory '" << OutDirectory << "'\n";
288 return 1;
289 }
290
291 // Run the generator.
292 llvm::outs() << "Generating docs...\n";
293 if (auto Err =
294 G->get()->generateDocs(RootDir: OutDirectory, Infos: std::move(USRToInfo), CDCtx)) {
295 llvm::errs() << toString(E: std::move(Err)) << "\n";
296 return 1;
297 }
298
299 llvm::outs() << "Generating assets for docs...\n";
300 Err = G->get()->createResources(CDCtx);
301 if (Err) {
302 llvm::errs() << toString(E: std::move(Err)) << "\n";
303 return 1;
304 }
305
306 return 0;
307}
308

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