1 | /**************************************************************************** |
2 | * Copyright (C) 2013-2016 Woboq GmbH |
3 | * Olivier Goffart <contact at woboq.com> |
4 | * https://woboq.com/ |
5 | * |
6 | * This program is free software: you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by |
8 | * the Free Software Foundation, either version 3 of the License, or |
9 | * (at your option) any later version. |
10 | * |
11 | * This program is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | * GNU General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU General Public License |
17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
18 | */ |
19 | |
20 | #include <clang/Frontend/FrontendAction.h> |
21 | #include <clang/Frontend/FrontendActions.h> |
22 | #include <clang/Tooling/Tooling.h> |
23 | #include <clang/Driver/Driver.h> |
24 | #include <clang/Driver/Compilation.h> |
25 | #include <clang/Driver/Tool.h> |
26 | #include <clang/Basic/DiagnosticIDs.h> |
27 | #include <clang/Lex/LexDiagnostic.h> |
28 | |
29 | #include <clang/Driver/Job.h> |
30 | #include "clang/Frontend/TextDiagnosticPrinter.h" |
31 | #include <clang/Lex/Preprocessor.h> |
32 | #include <clang/AST/ASTContext.h> |
33 | #include <clang/AST/DeclCXX.h> |
34 | #include <llvm/Support/Host.h> |
35 | |
36 | #include <vector> |
37 | #include <iostream> |
38 | |
39 | #include "mocastconsumer.h" |
40 | #include "generator.h" |
41 | #include "mocppcallbacks.h" |
42 | #include "embedded_includes.h" |
43 | |
44 | struct MocOptions { |
45 | bool NoInclude = false; |
46 | std::vector<std::string> Includes; |
47 | std::string Output; |
48 | std::string ; |
49 | std::vector<std::pair<llvm::StringRef, llvm::StringRef>> MetaData; |
50 | void addOutput(llvm::StringRef); |
51 | } Options; |
52 | |
53 | void MocOptions::addOutput(llvm::StringRef Out) |
54 | { |
55 | if (Output.empty()) { |
56 | Output = Out.str(); |
57 | } else if (OutputTemplateHeader.empty()) { |
58 | OutputTemplateHeader = Out.str(); |
59 | } else { |
60 | std::cerr << "moc-ng: Too many output file specified" << std::endl; |
61 | } |
62 | } |
63 | |
64 | |
65 | /* Proxy that changes some errors into warnings */ |
66 | struct MocDiagConsumer : clang::DiagnosticConsumer { |
67 | std::unique_ptr<DiagnosticConsumer> Proxy; |
68 | MocDiagConsumer(std::unique_ptr<DiagnosticConsumer> Previous) : Proxy(std::move(Previous)) {} |
69 | |
70 | int HadRealError = 0; |
71 | |
72 | #if CLANG_VERSION_MAJOR == 3 && CLANG_VERSION_MINOR <= 2 |
73 | DiagnosticConsumer* clone(clang::DiagnosticsEngine& Diags) const override { |
74 | return new MocDiagConsumer { Proxy->clone(Diags) }; |
75 | } |
76 | #endif |
77 | void BeginSourceFile(const clang::LangOptions& LangOpts, const clang::Preprocessor* PP = 0) override { |
78 | Proxy->BeginSourceFile(LangOpts, PP); |
79 | } |
80 | void clear() override { |
81 | Proxy->clear(); |
82 | } |
83 | void EndSourceFile() override { |
84 | Proxy->EndSourceFile(); |
85 | } |
86 | void finish() override { |
87 | Proxy->finish(); |
88 | } |
89 | void HandleDiagnostic(clang::DiagnosticsEngine::Level DiagLevel, const clang::Diagnostic& Info) override { |
90 | |
91 | /* Moc ignores most of the errors since it even can operate on non self-contained headers. |
92 | * So try to change errors into warning. |
93 | */ |
94 | |
95 | auto DiagId = Info.getID(); |
96 | auto Cat = Info.getDiags()->getDiagnosticIDs()->getCategoryNumberForDiag(DiagId); |
97 | |
98 | bool ShouldReset = false; |
99 | |
100 | if (DiagLevel >= clang::DiagnosticsEngine::Error ) { |
101 | if (Cat == 2 || Cat == 4 |
102 | || DiagId == clang::diag::err_param_redefinition |
103 | || DiagId == clang::diag::err_pp_expr_bad_token_binop ) { |
104 | if (!HadRealError) |
105 | ShouldReset = true; |
106 | DiagLevel = clang::DiagnosticsEngine::Warning; |
107 | } else { |
108 | HadRealError++; |
109 | } |
110 | } |
111 | |
112 | DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); |
113 | Proxy->HandleDiagnostic(DiagLevel, Info); |
114 | |
115 | if (ShouldReset) { |
116 | // FIXME: is there another way to ignore errors? |
117 | const_cast<clang::DiagnosticsEngine *>(Info.getDiags())->Reset(); |
118 | } |
119 | } |
120 | }; |
121 | |
122 | |
123 | |
124 | struct MocNGASTConsumer : public MocASTConsumer { |
125 | std::string InFile; |
126 | MocNGASTConsumer(clang::CompilerInstance& ci, llvm::StringRef InFile) : MocASTConsumer(ci), InFile(InFile) { } |
127 | |
128 | |
129 | #if CLANG_VERSION_MAJOR == 3 && CLANG_VERSION_MINOR < 8 |
130 | // Clang 3.8 changed when Initialize is called. It is now called before the main file has been entered. |
131 | // But with Clang < 3.8 it is called after, and PPCallbacks::FileChanged is not called when entering the main file |
132 | void Initialize(clang::ASTContext& Ctx) override { |
133 | MocASTConsumer::Initialize(Ctx); |
134 | PPCallbacks->EnterMainFile(InFile); |
135 | } |
136 | #endif |
137 | |
138 | bool shouldParseDecl(clang::Decl * D) override { |
139 | // We only want to parse the Qt macro in classes that are in the main file. |
140 | auto SL = D->getSourceRange().getBegin(); |
141 | SL = ci.getSourceManager().getExpansionLoc(SL); |
142 | if (ci.getSourceManager().getFileID(SL) != ci.getSourceManager().getMainFileID()) |
143 | return false; |
144 | return true; |
145 | } |
146 | |
147 | void HandleTranslationUnit(clang::ASTContext& Ctx) override { |
148 | |
149 | if (ci.getDiagnostics().hasErrorOccurred()) |
150 | return; |
151 | |
152 | if (!objects.size() && !namespaces.size()) { |
153 | ci.getDiagnostics().Report(ci.getSourceManager().getLocForStartOfFile(ci.getSourceManager().getMainFileID()), |
154 | ci.getDiagnostics().getCustomDiagID(clang::DiagnosticsEngine::Warning, |
155 | "No relevant classes found. No output generated" )); |
156 | //actually still create an empty file like moc does. |
157 | ci.createOutputFile(Options.Output, false, true, "" , "" , false, false); |
158 | return; |
159 | } |
160 | |
161 | // createOutputFile returns a raw_pwrite_stream* before Clang 3.9, and a std::unique_ptr<raw_pwrite_stream> after |
162 | auto OS = ci.createOutputFile(Options.Output, false, true, "" , "" , false, false); |
163 | |
164 | if (!OS) return; |
165 | llvm::raw_ostream &Out = *OS; |
166 | |
167 | auto = [&](llvm::raw_ostream & Out) { |
168 | Out << "/****************************************************************************\n" |
169 | "** Meta object code from reading C++ file '" << InFile << "'\n" |
170 | "**\n" |
171 | "** Created by MOC-NG version " MOCNG_VERSION_STR " by Woboq [https://woboq.com]\n" |
172 | "** WARNING! All changes made in this file will be lost!\n" |
173 | "*****************************************************************************/\n\n" ; |
174 | }; |
175 | WriteHeader(Out); |
176 | |
177 | if (!Options.NoInclude) { |
178 | for (auto &s : Options.Includes) { |
179 | Out << s << "\n" ; |
180 | } |
181 | |
182 | auto spos = InFile.rfind('/'); |
183 | auto ppos = InFile.rfind('.'); |
184 | // If it finished by .h, or if there is no dot after a slash, we should include the source file |
185 | if (ppos == std::string::npos || (spos != std::string::npos && spos > ppos) || llvm::StringRef(InFile).endswith(".h" )) |
186 | Out << "#include \"" << InFile << "\"\n" ; |
187 | if (Moc.HasPlugin) |
188 | Out << "#include <QtCore/qplugin.h>\n" ; |
189 | } |
190 | |
191 | Out << "#include <QtCore/qbytearray.h>\n" ; |
192 | |
193 | Out << "#if !defined(Q_MOC_OUTPUT_REVISION)\n" |
194 | "#error \"The header file '" << InFile << "' doesn't include <QObject>.\"\n" |
195 | "#elif Q_MOC_OUTPUT_REVISION != " << mocOutputRevision << "\n" |
196 | "#error \"This file was generated using MOC-NG " MOCNG_VERSION_STR ".\"\n" |
197 | "#error \"It cannot be used with the include files from this version of Qt.\"\n" |
198 | "#endif\n\n" |
199 | "QT_BEGIN_MOC_NAMESPACE\n" |
200 | "#ifdef QT_WARNING_DISABLE_DEPRECATED\n" |
201 | "QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED\n" |
202 | "#endif\n" ; |
203 | |
204 | decltype(OS) = nullptr; |
205 | if (!Options.OutputTemplateHeader.empty()) { |
206 | OS_TemplateHeader = |
207 | ci.createOutputFile(Options.OutputTemplateHeader, false, true, "" , "" , false, false); |
208 | if (!OS_TemplateHeader) |
209 | return; |
210 | WriteHeader(*OS_TemplateHeader); |
211 | (*OS_TemplateHeader) << "QT_BEGIN_MOC_NAMESPACE\n" |
212 | "#ifdef QT_WARNING_DISABLE_DEPRECATED\n" |
213 | "QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED\n" |
214 | "#endif\n" ; |
215 | } |
216 | |
217 | for (const ClassDef &Def : objects ) { |
218 | Generator G(&Def, Out, Ctx, &Moc, |
219 | Def.Record->getDescribedClassTemplate() ? &*OS_TemplateHeader : nullptr); |
220 | G.MetaData = Options.MetaData; |
221 | if (llvm::StringRef(InFile).endswith("global/qnamespace.h" )) |
222 | G.IsQtNamespace = true; |
223 | G.GenerateCode(); |
224 | }; |
225 | for (const NamespaceDef &Def : namespaces) { |
226 | Generator G(&Def, Out, Ctx, &Moc); |
227 | G.MetaData = Options.MetaData; |
228 | G.GenerateCode(); |
229 | }; |
230 | |
231 | llvm::StringRef = |
232 | "QT_END_MOC_NAMESPACE\n" |
233 | "#ifdef QT_WARNING_DISABLE_DEPRECATED\n" |
234 | "QT_WARNING_POP\n" |
235 | "#endif\n" ; |
236 | Out << footer; |
237 | if (OS_TemplateHeader) { |
238 | (*OS_TemplateHeader) << footer; |
239 | } |
240 | } |
241 | }; |
242 | |
243 | class MocAction : public clang::ASTFrontendAction { |
244 | protected: |
245 | #if CLANG_VERSION_MAJOR == 3 && CLANG_VERSION_MINOR <= 5 |
246 | clang::ASTConsumer * |
247 | #else |
248 | std::unique_ptr<clang::ASTConsumer> |
249 | #endif |
250 | CreateASTConsumer(clang::CompilerInstance &CI, llvm::StringRef InFile) override { |
251 | |
252 | CI.getFrontendOpts().SkipFunctionBodies = true; |
253 | CI.getPreprocessor().enableIncrementalProcessing(true); |
254 | CI.getPreprocessor().SetSuppressIncludeNotFoundError(true); |
255 | CI.getLangOpts().DelayedTemplateParsing = true; |
256 | |
257 | //enable all the extension |
258 | CI.getLangOpts().MicrosoftExt = true; |
259 | CI.getLangOpts().DollarIdents = true; |
260 | CI.getLangOpts().CPlusPlus11 = true; |
261 | #if CLANG_VERSION_MAJOR == 3 && CLANG_VERSION_MINOR <= 5 |
262 | CI.getLangOpts().CPlusPlus1y = true; |
263 | #else |
264 | CI.getLangOpts().CPlusPlus14 = true; |
265 | #endif |
266 | CI.getLangOpts().GNUMode = true; |
267 | |
268 | CI.getDiagnostics().setClient(new MocDiagConsumer( |
269 | std::unique_ptr<clang::DiagnosticConsumer>(CI.getDiagnostics().takeClient()))); |
270 | |
271 | return maybe_unique(new MocNGASTConsumer(CI, InFile)); |
272 | } |
273 | |
274 | public: |
275 | // CHECK |
276 | virtual bool hasCodeCompletionSupport() const { return true; } |
277 | }; |
278 | |
279 | static void showVersion(bool /*Long*/) { |
280 | std::cerr << "moc-ng version " MOCNG_VERSION_STR " by Woboq [https://woboq.com]" << std::endl; |
281 | } |
282 | |
283 | static void showHelp() { |
284 | std::cerr << "Usage moc: [options] <header-file>\n" |
285 | " -o<file> write output to file rather than stdout\n" |
286 | " -I<dir> add dir to the include path for header files\n" |
287 | " -E preprocess only; do not generate meta object code\n" |
288 | " -D<macro>[=<def>] define macro, with optional definition\n" |
289 | " -U<macro> undefine macro\n" |
290 | " -M<key=valye> add key/value pair to plugin meta data\n" |
291 | " -i do not generate an #include statement\n" |
292 | // " -p<path> path prefix for included file\n" |
293 | // " -f[<file>] force #include, optional file name\n" |
294 | // " -nn do not display notes\n" |
295 | // " -nw do not display warnings\n" |
296 | // " @<file> read additional options from file\n" |
297 | " -v display version of moc-ng\n" |
298 | " -include <file> Adds an implicit #include into the predefines buffer which is read before the source file is preprocessed\n" |
299 | |
300 | /* undocumented options |
301 | " -W<warnings> Enable the specified warning\n" |
302 | " -f<option> clang option\n" |
303 | " -X<ext> <arg> extensions arguments\n" |
304 | */ |
305 | |
306 | |
307 | |
308 | << std::endl; |
309 | |
310 | |
311 | showVersion(false); |
312 | } |
313 | |
314 | |
315 | int main(int argc, const char **argv) |
316 | { |
317 | bool PreprocessorOnly = false; |
318 | std::vector<std::string> Argv; |
319 | Argv.push_back(argv[0]); |
320 | Argv.push_back("-x" ); // Type need to go first |
321 | Argv.push_back("c++" ); |
322 | Argv.push_back("-fPIE" ); |
323 | Argv.push_back("-fPIC" ); |
324 | Argv.push_back("-Wno-microsoft" ); // get rid of a warning in qtextdocument.h |
325 | Argv.push_back("-Wno-pragma-once-outside-header" ); |
326 | #if CLANG_VERSION_MAJOR == 3 && CLANG_VERSION_MINOR <= 5 |
327 | Argv.push_back("-std=c++11" ); |
328 | #else |
329 | Argv.push_back("-std=c++14" ); |
330 | #endif |
331 | |
332 | bool NextArgNotInput = false; |
333 | bool HasInput = false; |
334 | llvm::StringRef InputFile; |
335 | |
336 | for (int I = 1 ; I < argc; ++I) { |
337 | if (argv[I][0] == '-') { |
338 | NextArgNotInput = false; |
339 | switch (argv[I][1]) { |
340 | case 'h': |
341 | case '?': |
342 | showHelp(); |
343 | return EXIT_SUCCESS; |
344 | case 'v': |
345 | showVersion(true); |
346 | return EXIT_SUCCESS; |
347 | case 'o': |
348 | if (argv[I][2]) Options.addOutput(&argv[I][2]); |
349 | else if ((++I) < argc) Options.addOutput(argv[I]); |
350 | continue; |
351 | case 'i': |
352 | if (argv[I] == llvm::StringRef("-i" )) { |
353 | Options.NoInclude = true; |
354 | continue; |
355 | } else if (argv[I] == llvm::StringRef("-include" )) { |
356 | NextArgNotInput = true; |
357 | break; |
358 | } |
359 | goto invalidArg; |
360 | case 'M': { |
361 | llvm::StringRef Arg; |
362 | if (argv[I][2]) Arg = &argv[I][2]; |
363 | else if ((++I) < argc) Arg = argv[I]; |
364 | size_t Eq = Arg.find('='); |
365 | if (Eq == llvm::StringRef::npos) { |
366 | std::cerr << "moc-ng: missing key or value for option '-M'" << std::endl; |
367 | return EXIT_FAILURE; |
368 | } |
369 | Options.MetaData.push_back({Arg.substr(0, Eq), Arg.substr(Eq+1)}); |
370 | continue; |
371 | } |
372 | case 'E': |
373 | PreprocessorOnly = true; |
374 | break; |
375 | case 'I': |
376 | case 'U': |
377 | case 'D': |
378 | NextArgNotInput = (argv[I][2] == '\0'); |
379 | break; |
380 | case 'X': |
381 | NextArgNotInput = true; |
382 | break; |
383 | case 'f': //this is understood as compiler option rather than moc -f |
384 | case 'W': // same |
385 | break; |
386 | case 'n': //not implemented, silently ignored |
387 | continue; |
388 | case '-': |
389 | if (llvm::StringRef(argv[I]) == "--include" || |
390 | llvm::StringRef(argv[I]).startswith("--include=" )) { |
391 | llvm::StringRef File; |
392 | if (llvm::StringRef(argv[I]).startswith("--include=" )) { |
393 | File = llvm::StringRef(argv[I]).substr(llvm::StringRef("--include=" ).size()); |
394 | } else if (I + 1 < argc) { |
395 | File = llvm::StringRef(argv[++I]); |
396 | } |
397 | if (File.endswith("/moc_predefs.h" )) { |
398 | // qmake generates moc_predefs with compiler defined stuff. |
399 | // We can't support it because everything is already pre-defined. |
400 | // So skip it. |
401 | continue; |
402 | } |
403 | Argv.push_back("-include" ); |
404 | Argv.push_back(File); |
405 | continue; |
406 | } |
407 | if (llvm::StringRef(argv[I]).startswith("--compiler-flavor" )) { |
408 | if (llvm::StringRef(argv[I]) == "--compiler-flavor" ) |
409 | ++I; |
410 | // MSVC flavor not yet implemented |
411 | continue; |
412 | } |
413 | LLVM_FALLTHROUGH; |
414 | default: |
415 | invalidArg: |
416 | std::cerr << "moc-ng: Invalid argument '" << argv[I] << "'" << std::endl; |
417 | showHelp(); |
418 | return EXIT_FAILURE; |
419 | } |
420 | } else if (!NextArgNotInput) { |
421 | if (HasInput) { |
422 | std::cerr << "error: Too many input files specified" << std::endl; |
423 | return EXIT_FAILURE; |
424 | } |
425 | HasInput = true; |
426 | InputFile = argv[I]; |
427 | } |
428 | Argv.push_back(argv[I]); |
429 | } |
430 | |
431 | if (Options.Output.empty()) |
432 | Options.Output = "-" ; |
433 | |
434 | if (!HasInput) |
435 | Argv.push_back("-" ); |
436 | |
437 | //FIXME |
438 | Argv.push_back("-I/usr/include/qt5" ); |
439 | Argv.push_back("-I/usr/include/qt5/QtCore" ); |
440 | Argv.push_back("-I/usr/include/qt" ); |
441 | Argv.push_back("-I/usr/include/qt/QtCore" ); |
442 | Argv.push_back("-I.uic" ); // workaround the fact that the uic generated code cannot be found |
443 | |
444 | Argv.push_back("-I/builtins" ); |
445 | |
446 | clang::FileManager FM({"." }); |
447 | FM.Retain(); |
448 | |
449 | if (PreprocessorOnly) { |
450 | Argv.push_back("-P" ); |
451 | clang::tooling::ToolInvocation Inv(Argv, new clang::PrintPreprocessedAction, &FM); |
452 | return !Inv.run(); |
453 | } |
454 | |
455 | Argv.push_back("-fsyntax-only" ); |
456 | |
457 | if (!InputFile.endswith("qobject.h" ) && !InputFile.endswith("qnamespace.h" )) { |
458 | // QObject should always be included |
459 | // But not not for qobject.h (or we would not detect the main file correctly) or |
460 | // qnamespace.h (that would break the Q_MOC_RUN workaround from MocPPCallbacks::EnterMainFile) |
461 | Argv.push_back("-include" ); |
462 | Argv.push_back("QtCore/qobject.h" ); |
463 | } |
464 | |
465 | clang::tooling::ToolInvocation Inv(Argv, new MocAction, &FM); |
466 | |
467 | const EmbeddedFile *f = EmbeddedFiles; |
468 | while (f->filename) { |
469 | Inv.mapVirtualFile(f->filename, {f->content , f->size } ); |
470 | f++; |
471 | } |
472 | |
473 | return !Inv.run(); |
474 | } |
475 | |
476 | |
477 | |
478 | |
479 | |