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 | |
49 | using namespace clang::ast_matchers; |
50 | using namespace clang::tooling; |
51 | using namespace clang; |
52 | |
53 | static llvm::cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); |
54 | static llvm::cl::OptionCategory ClangDocCategory("clang-doc options" ); |
55 | |
56 | static llvm::cl::opt<std::string> |
57 | ProjectName("project-name" , llvm::cl::desc("Name of project." ), |
58 | llvm::cl::cat(ClangDocCategory)); |
59 | |
60 | static 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 | |
65 | static 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 | |
70 | static 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 | |
74 | static 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 | |
79 | static 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 | |
84 | static llvm::cl::opt<std::string> SourceRoot("source-root" , llvm::cl::desc(R"( |
85 | Directory where processed files are stored. |
86 | Links to definition locations will only be |
87 | generated if the file is in this dir.)" ), |
88 | llvm::cl::cat(ClangDocCategory)); |
89 | |
90 | static llvm::cl::opt<std::string> |
91 | RepositoryUrl("repository" , llvm::cl::desc(R"( |
92 | URL of repository that hosts code. |
93 | Used for links to definition locations.)" ), |
94 | llvm::cl::cat(ClangDocCategory)); |
95 | |
96 | enum OutputFormatTy { |
97 | md, |
98 | yaml, |
99 | html, |
100 | }; |
101 | |
102 | static 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 | |
113 | std::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). |
130 | std::string GetExecutablePath(const char *Argv0, void *MainAddr) { |
131 | return llvm::sys::fs::getMainExecutable(argv0: Argv0, MainExecAddr: MainAddr); |
132 | } |
133 | |
134 | int 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 | |
141 | Example usage for files without flags (default): |
142 | |
143 | $ clang-doc File1.cpp File2.cpp ... FileN.cpp |
144 | |
145 | Example 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 | |