1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Copyright (C) 2016 Intel Corporation.
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of the tools applications of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
10 ** Commercial License Usage
11 ** Licensees holding valid commercial Qt licenses may use this file in
12 ** accordance with the commercial license agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and The Qt Company. For licensing terms
15 ** and conditions see https://www.qt.io/terms-conditions. For further
16 ** information use the contact form at https://www.qt.io/contact-us.
17 **
18 ** GNU General Public License Usage
19 ** Alternatively, this file may be used under the terms of the GNU
20 ** General Public License version 3 as published by the Free Software
21 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
22 ** included in the packaging of this file. Please review the following
23 ** information to ensure the GNU General Public License requirements will
24 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25 **
26 ** $QT_END_LICENSE$
27 **
28 ****************************************************************************/
29 
30 #include "preprocessor.h"
31 #include "moc.h"
32 #include "outputrevision.h"
33 #include "collectjson.h"
34 
35 #include <qfile.h>
36 #include <qfileinfo.h>
37 #include <qdir.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <ctype.h>
41 #include <errno.h>
42 
43 #include <qcoreapplication.h>
44 #include <qcommandlineoption.h>
45 #include <qcommandlineparser.h>
46 #include <qscopedpointer.h>
47 
48 QT_BEGIN_NAMESPACE
49 
50 /*
51     This function looks at two file names and returns the name of the
52     infile with a path relative to outfile.
53 
54     Examples:
55 
56         /tmp/abc, /tmp/bcd -> abc
57         xyz/a/bc, xyz/b/ac -> ../a/bc
58         /tmp/abc, xyz/klm -> /tmp/abc
59  */
60 
combinePath(const QString & infile,const QString & outfile)61 static QByteArray combinePath(const QString &infile, const QString &outfile)
62 {
63     QFileInfo inFileInfo(QDir::current(), infile);
64     QFileInfo outFileInfo(QDir::current(), outfile);
65     const QByteArray relativePath = QFile::encodeName(outFileInfo.dir().relativeFilePath(inFileInfo.filePath()));
66 #ifdef Q_OS_WIN
67     // It's a system limitation.
68     // It depends on the Win API function which is used by the program to open files.
69     // cl apparently uses the functions that have the MAX_PATH limitation.
70     if (outFileInfo.dir().absolutePath().length() + relativePath.length() + 1 >= 260)
71         return QFile::encodeName(inFileInfo.absoluteFilePath());
72 #endif
73     return relativePath;
74 }
75 
76 
error(const char * msg="Invalid argument")77 void error(const char *msg = "Invalid argument")
78 {
79     if (msg)
80         fprintf(stderr, "moc: %s\n", msg);
81 }
82 
83 struct ScopedPointerFileCloser
84 {
cleanupScopedPointerFileCloser85     static inline void cleanup(FILE *handle) { if (handle) fclose(handle); }
86 };
87 
hasNext(const Symbols & symbols,int i)88 static inline bool hasNext(const Symbols &symbols, int i)
89 { return (i < symbols.size()); }
90 
next(const Symbols & symbols,int & i)91 static inline const Symbol &next(const Symbols &symbols, int &i)
92 { return symbols.at(i++); }
93 
94 
composePreprocessorOutput(const Symbols & symbols)95 QByteArray composePreprocessorOutput(const Symbols &symbols) {
96     QByteArray output;
97     int lineNum = 1;
98     Token last = PP_NOTOKEN;
99     Token secondlast = last;
100     int i = 0;
101     while (hasNext(symbols, i)) {
102         Symbol sym = next(symbols, i);
103         switch (sym.token) {
104         case PP_NEWLINE:
105         case PP_WHITESPACE:
106             if (last != PP_WHITESPACE) {
107                 secondlast = last;
108                 last = PP_WHITESPACE;
109                 output += ' ';
110             }
111             continue;
112         case PP_STRING_LITERAL:
113             if (last == PP_STRING_LITERAL)
114                 output.chop(1);
115             else if (secondlast == PP_STRING_LITERAL && last == PP_WHITESPACE)
116                 output.chop(2);
117             else
118                 break;
119             output += sym.lexem().mid(1);
120             secondlast = last;
121             last = PP_STRING_LITERAL;
122             continue;
123         case MOC_INCLUDE_BEGIN:
124             lineNum = 0;
125             continue;
126         case MOC_INCLUDE_END:
127             lineNum = sym.lineNum;
128             continue;
129         default:
130             break;
131         }
132         secondlast = last;
133         last = sym.token;
134 
135         const int padding = sym.lineNum - lineNum;
136         if (padding > 0) {
137             output.resize(output.size() + padding);
138             memset(output.data() + output.size() - padding, '\n', padding);
139             lineNum = sym.lineNum;
140         }
141 
142         output += sym.lexem();
143     }
144 
145     return output;
146 }
147 
argumentsFromCommandLineAndFile(const QStringList & arguments)148 static QStringList argumentsFromCommandLineAndFile(const QStringList &arguments)
149 {
150     QStringList allArguments;
151     allArguments.reserve(arguments.size());
152     for (const QString &argument : arguments) {
153         // "@file" doesn't start with a '-' so we can't use QCommandLineParser for it
154         if (argument.startsWith(QLatin1Char('@'))) {
155             QString optionsFile = argument;
156             optionsFile.remove(0, 1);
157             if (optionsFile.isEmpty()) {
158                 error("The @ option requires an input file");
159                 return QStringList();
160             }
161             QFile f(optionsFile);
162             if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
163                 error("Cannot open options file specified with @");
164                 return QStringList();
165             }
166             while (!f.atEnd()) {
167                 QString line = QString::fromLocal8Bit(f.readLine().trimmed());
168                 if (!line.isEmpty())
169                     allArguments << line;
170             }
171         } else {
172             allArguments << argument;
173         }
174     }
175     return allArguments;
176 }
177 
178 // Escape characters in given path. Dependency paths are Make-style, not NMake/Jom style.
179 // The paths can also be consumed by Ninja.
180 // "$" replaced by "$$"
181 // "#" replaced by "\#"
182 // " " replaced by "\ "
183 // "\#" replaced by "\\#"
184 // "\ " replaced by "\\\ "
185 //
186 // The escape rules are according to what clang / llvm escapes when generating a Make-style
187 // dependency file.
188 // Is a template function, because input param can be either a QString or a QByteArray.
189 template <typename T> struct CharType;
190 template <> struct CharType<QString> { using type = QLatin1Char; };
191 template <> struct CharType<QByteArray> { using type = char; };
192 template <typename StringType>
escapeDependencyPath(const StringType & path)193 StringType escapeDependencyPath(const StringType &path)
194 {
195     using CT = typename CharType<StringType>::type;
196     StringType escapedPath;
197     int size = path.size();
198     escapedPath.reserve(size);
199     for (int i = 0; i < size; ++i) {
200         if (path[i] == CT('$')) {
201             escapedPath.append(CT('$'));
202         } else if (path[i] == CT('#')) {
203             escapedPath.append(CT('\\'));
204         } else if (path[i] == CT(' ')) {
205             escapedPath.append(CT('\\'));
206             int backwards_it = i - 1;
207             while (backwards_it > 0 && path[backwards_it] == CT('\\')) {
208                 escapedPath.append(CT('\\'));
209                 --backwards_it;
210             }
211         }
212         escapedPath.append(path[i]);
213     }
214     return escapedPath;
215 }
216 
escapeAndEncodeDependencyPath(const QString & path)217 QByteArray escapeAndEncodeDependencyPath(const QString &path)
218 {
219     return QFile::encodeName(escapeDependencyPath(path));
220 }
221 
runMoc(int argc,char ** argv)222 int runMoc(int argc, char **argv)
223 {
224     QCoreApplication app(argc, argv);
225     QCoreApplication::setApplicationVersion(QString::fromLatin1(QT_VERSION_STR));
226 
227     bool autoInclude = true;
228     bool defaultInclude = true;
229     Preprocessor pp;
230     Moc moc;
231     pp.macros["Q_MOC_RUN"];
232     pp.macros["__cplusplus"];
233 
234     // Don't stumble over GCC extensions
235     Macro dummyVariadicFunctionMacro;
236     dummyVariadicFunctionMacro.isFunction = true;
237     dummyVariadicFunctionMacro.isVariadic = true;
238     dummyVariadicFunctionMacro.arguments += Symbol(0, PP_IDENTIFIER, "__VA_ARGS__");
239     pp.macros["__attribute__"] = dummyVariadicFunctionMacro;
240     pp.macros["__declspec"] = dummyVariadicFunctionMacro;
241 
242     QString filename;
243     QString output;
244     QFile in;
245     FILE *out = 0;
246 
247     // Note that moc isn't translated.
248     // If you use this code as an example for a translated app, make sure to translate the strings.
249     QCommandLineParser parser;
250     parser.setApplicationDescription(QStringLiteral("Qt Meta Object Compiler version %1 (Qt %2)")
251                                      .arg(mocOutputRevision).arg(QString::fromLatin1(QT_VERSION_STR)));
252     parser.addHelpOption();
253     parser.addVersionOption();
254     parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
255 
256     QCommandLineOption outputOption(QStringLiteral("o"));
257     outputOption.setDescription(QStringLiteral("Write output to file rather than stdout."));
258     outputOption.setValueName(QStringLiteral("file"));
259     outputOption.setFlags(QCommandLineOption::ShortOptionStyle);
260     parser.addOption(outputOption);
261 
262     QCommandLineOption includePathOption(QStringLiteral("I"));
263     includePathOption.setDescription(QStringLiteral("Add dir to the include path for header files."));
264     includePathOption.setValueName(QStringLiteral("dir"));
265     includePathOption.setFlags(QCommandLineOption::ShortOptionStyle);
266     parser.addOption(includePathOption);
267 
268     QCommandLineOption macFrameworkOption(QStringLiteral("F"));
269     macFrameworkOption.setDescription(QStringLiteral("Add Mac framework to the include path for header files."));
270     macFrameworkOption.setValueName(QStringLiteral("framework"));
271     macFrameworkOption.setFlags(QCommandLineOption::ShortOptionStyle);
272     parser.addOption(macFrameworkOption);
273 
274     QCommandLineOption preprocessOption(QStringLiteral("E"));
275     preprocessOption.setDescription(QStringLiteral("Preprocess only; do not generate meta object code."));
276     parser.addOption(preprocessOption);
277 
278     QCommandLineOption defineOption(QStringLiteral("D"));
279     defineOption.setDescription(QStringLiteral("Define macro, with optional definition."));
280     defineOption.setValueName(QStringLiteral("macro[=def]"));
281     defineOption.setFlags(QCommandLineOption::ShortOptionStyle);
282     parser.addOption(defineOption);
283 
284     QCommandLineOption undefineOption(QStringLiteral("U"));
285     undefineOption.setDescription(QStringLiteral("Undefine macro."));
286     undefineOption.setValueName(QStringLiteral("macro"));
287     undefineOption.setFlags(QCommandLineOption::ShortOptionStyle);
288     parser.addOption(undefineOption);
289 
290     QCommandLineOption metadataOption(QStringLiteral("M"));
291     metadataOption.setDescription(QStringLiteral("Add key/value pair to plugin meta data"));
292     metadataOption.setValueName(QStringLiteral("key=value"));
293     metadataOption.setFlags(QCommandLineOption::ShortOptionStyle);
294     parser.addOption(metadataOption);
295 
296     QCommandLineOption compilerFlavorOption(QStringLiteral("compiler-flavor"));
297     compilerFlavorOption.setDescription(QStringLiteral("Set the compiler flavor: either \"msvc\" or \"unix\"."));
298     compilerFlavorOption.setValueName(QStringLiteral("flavor"));
299     parser.addOption(compilerFlavorOption);
300 
301     QCommandLineOption noIncludeOption(QStringLiteral("i"));
302     noIncludeOption.setDescription(QStringLiteral("Do not generate an #include statement."));
303     parser.addOption(noIncludeOption);
304 
305     QCommandLineOption pathPrefixOption(QStringLiteral("p"));
306     pathPrefixOption.setDescription(QStringLiteral("Path prefix for included file."));
307     pathPrefixOption.setValueName(QStringLiteral("path"));
308     pathPrefixOption.setFlags(QCommandLineOption::ShortOptionStyle);
309     parser.addOption(pathPrefixOption);
310 
311     QCommandLineOption forceIncludeOption(QStringLiteral("f"));
312     forceIncludeOption.setDescription(QStringLiteral("Force #include <file> (overwrite default)."));
313     forceIncludeOption.setValueName(QStringLiteral("file"));
314     forceIncludeOption.setFlags(QCommandLineOption::ShortOptionStyle);
315     parser.addOption(forceIncludeOption);
316 
317     QCommandLineOption prependIncludeOption(QStringLiteral("b"));
318     prependIncludeOption.setDescription(QStringLiteral("Prepend #include <file> (preserve default include)."));
319     prependIncludeOption.setValueName(QStringLiteral("file"));
320     prependIncludeOption.setFlags(QCommandLineOption::ShortOptionStyle);
321     parser.addOption(prependIncludeOption);
322 
323     QCommandLineOption includeOption(QStringLiteral("include"));
324     includeOption.setDescription(QStringLiteral("Parse <file> as an #include before the main source(s)."));
325     includeOption.setValueName(QStringLiteral("file"));
326     parser.addOption(includeOption);
327 
328     QCommandLineOption noNotesWarningsCompatOption(QStringLiteral("n"));
329     noNotesWarningsCompatOption.setDescription(QStringLiteral("Do not display notes (-nn) or warnings (-nw). Compatibility option."));
330     noNotesWarningsCompatOption.setValueName(QStringLiteral("which"));
331     noNotesWarningsCompatOption.setFlags(QCommandLineOption::ShortOptionStyle);
332     parser.addOption(noNotesWarningsCompatOption);
333 
334     QCommandLineOption noNotesOption(QStringLiteral("no-notes"));
335     noNotesOption.setDescription(QStringLiteral("Do not display notes."));
336     parser.addOption(noNotesOption);
337 
338     QCommandLineOption noWarningsOption(QStringLiteral("no-warnings"));
339     noWarningsOption.setDescription(QStringLiteral("Do not display warnings (implies --no-notes)."));
340     parser.addOption(noWarningsOption);
341 
342     QCommandLineOption ignoreConflictsOption(QStringLiteral("ignore-option-clashes"));
343     ignoreConflictsOption.setDescription(QStringLiteral("Ignore all options that conflict with compilers, like -pthread conflicting with moc's -p option."));
344     parser.addOption(ignoreConflictsOption);
345 
346     QCommandLineOption jsonOption(QStringLiteral("output-json"));
347     jsonOption.setDescription(QStringLiteral("In addition to generating C++ code, create a machine-readable JSON file in a file that matches the output file and an extra .json extension."));
348     parser.addOption(jsonOption);
349 
350     QCommandLineOption collectOption(QStringLiteral("collect-json"));
351     collectOption.setDescription(QStringLiteral("Instead of processing C++ code, collect previously generated JSON output into a single file."));
352     parser.addOption(collectOption);
353 
354     QCommandLineOption depFileOption(QStringLiteral("output-dep-file"));
355     depFileOption.setDescription(
356                 QStringLiteral("Output a Make-style dep file for build system consumption."));
357     parser.addOption(depFileOption);
358 
359     QCommandLineOption depFilePathOption(QStringLiteral("dep-file-path"));
360     depFilePathOption.setDescription(QStringLiteral("Path where to write the dep file."));
361     depFilePathOption.setValueName(QStringLiteral("file"));
362     parser.addOption(depFilePathOption);
363 
364     QCommandLineOption depFileRuleNameOption(QStringLiteral("dep-file-rule-name"));
365     depFileRuleNameOption.setDescription(
366                 QStringLiteral("The rule name (first line) of the dep file."));
367     depFileRuleNameOption.setValueName(QStringLiteral("rule name"));
368     parser.addOption(depFileRuleNameOption);
369 
370     parser.addPositionalArgument(QStringLiteral("[header-file]"),
371             QStringLiteral("Header file to read from, otherwise stdin."));
372     parser.addPositionalArgument(QStringLiteral("[@option-file]"),
373             QStringLiteral("Read additional options from option-file."));
374     parser.addPositionalArgument(QStringLiteral("[MOC generated json file]"),
375                                  QStringLiteral("MOC generated json output"));
376 
377     const QStringList arguments = argumentsFromCommandLineAndFile(app.arguments());
378     if (arguments.isEmpty())
379         return 1;
380 
381     parser.process(arguments);
382 
383     const QStringList files = parser.positionalArguments();
384     output = parser.value(outputOption);
385     if (parser.isSet(collectOption))
386         return collectJson(files, output);
387 
388     if (files.count() > 1) {
389         error(qPrintable(QLatin1String("Too many input files specified: '") + files.join(QLatin1String("' '")) + QLatin1Char('\'')));
390         parser.showHelp(1);
391     } else if (!files.isEmpty()) {
392         filename = files.first();
393     }
394 
395     const bool ignoreConflictingOptions = parser.isSet(ignoreConflictsOption);
396     pp.preprocessOnly = parser.isSet(preprocessOption);
397     if (parser.isSet(noIncludeOption)) {
398         moc.noInclude = true;
399         autoInclude = false;
400     }
401     if (!ignoreConflictingOptions) {
402         if (parser.isSet(forceIncludeOption)) {
403             moc.noInclude = false;
404             autoInclude = false;
405             const auto forceIncludes = parser.values(forceIncludeOption);
406             for (const QString &include : forceIncludes) {
407                 moc.includeFiles.append(QFile::encodeName(include));
408                 defaultInclude = false;
409              }
410         }
411         const auto prependIncludes = parser.values(prependIncludeOption);
412         for (const QString &include : prependIncludes)
413             moc.includeFiles.prepend(QFile::encodeName(include));
414         if (parser.isSet(pathPrefixOption))
415             moc.includePath = QFile::encodeName(parser.value(pathPrefixOption));
416     }
417 
418     const auto includePaths = parser.values(includePathOption);
419     for (const QString &path : includePaths)
420         pp.includes += Preprocessor::IncludePath(QFile::encodeName(path));
421     QString compilerFlavor = parser.value(compilerFlavorOption);
422     if (compilerFlavor.isEmpty() || compilerFlavor == QLatin1String("unix")) {
423         // traditional Unix compilers use both CPATH and CPLUS_INCLUDE_PATH
424         // $CPATH feeds to #include <...> and #include "...", whereas
425         // CPLUS_INCLUDE_PATH is equivalent to GCC's -isystem, so we parse later
426         const auto cpath = qgetenv("CPATH").split(QDir::listSeparator().toLatin1());
427         for (const QByteArray &p : cpath)
428             pp.includes += Preprocessor::IncludePath(p);
429         const auto cplus_include_path = qgetenv("CPLUS_INCLUDE_PATH").split(QDir::listSeparator().toLatin1());
430         for (const QByteArray &p : cplus_include_path)
431             pp.includes += Preprocessor::IncludePath(p);
432     } else if (compilerFlavor == QLatin1String("msvc")) {
433         // MSVC uses one environment variable: INCLUDE
434         const auto include = qgetenv("INCLUDE").split(QDir::listSeparator().toLatin1());
435         for (const QByteArray &p : include)
436             pp.includes += Preprocessor::IncludePath(p);
437     } else {
438         error(qPrintable(QLatin1String("Unknown compiler flavor '") + compilerFlavor +
439                          QLatin1String("'; valid values are: msvc, unix.")));
440         parser.showHelp(1);
441     }
442 
443     const auto macFrameworks = parser.values(macFrameworkOption);
444     for (const QString &path : macFrameworks) {
445         // minimalistic framework support for the mac
446         Preprocessor::IncludePath p(QFile::encodeName(path));
447         p.isFrameworkPath = true;
448         pp.includes += p;
449     }
450     const auto defines = parser.values(defineOption);
451     for (const QString &arg : defines) {
452         QByteArray name = arg.toLocal8Bit();
453         QByteArray value("1");
454         int eq = name.indexOf('=');
455         if (eq >= 0) {
456             value = name.mid(eq + 1);
457             name = name.left(eq);
458         }
459         if (name.isEmpty()) {
460             error("Missing macro name");
461             parser.showHelp(1);
462         }
463         Macro macro;
464         macro.symbols = Preprocessor::tokenize(value, 1, Preprocessor::TokenizeDefine);
465         macro.symbols.removeLast(); // remove the EOF symbol
466         pp.macros.insert(name, macro);
467     }
468     const auto undefines = parser.values(undefineOption);
469     for (const QString &arg : undefines) {
470         QByteArray macro = arg.toLocal8Bit();
471         if (macro.isEmpty()) {
472             error("Missing macro name");
473             parser.showHelp(1);
474         }
475         pp.macros.remove(macro);
476     }
477     const QStringList noNotesCompatValues = parser.values(noNotesWarningsCompatOption);
478     if (parser.isSet(noNotesOption) || noNotesCompatValues.contains(QLatin1String("n")))
479         moc.displayNotes = false;
480     if (parser.isSet(noWarningsOption) || noNotesCompatValues.contains(QLatin1String("w")))
481         moc.displayWarnings = moc.displayNotes = false;
482 
483     if (autoInclude) {
484         int spos = filename.lastIndexOf(QDir::separator());
485         int ppos = filename.lastIndexOf(QLatin1Char('.'));
486         // spos >= -1 && ppos > spos => ppos >= 0
487         moc.noInclude = (ppos > spos && filename.at(ppos + 1).toLower() != QLatin1Char('h'));
488     }
489     if (defaultInclude) {
490         if (moc.includePath.isEmpty()) {
491             if (filename.size()) {
492                 if (output.size())
493                     moc.includeFiles.append(combinePath(filename, output));
494                 else
495                     moc.includeFiles.append(QFile::encodeName(filename));
496             }
497         } else {
498             moc.includeFiles.append(combinePath(filename, filename));
499         }
500     }
501 
502     if (filename.isEmpty()) {
503         filename = QStringLiteral("standard input");
504         in.open(stdin, QIODevice::ReadOnly);
505     } else {
506         in.setFileName(filename);
507         if (!in.open(QIODevice::ReadOnly)) {
508             fprintf(stderr, "moc: %s: No such file\n", qPrintable(filename));
509             return 1;
510         }
511         moc.filename = filename.toLocal8Bit();
512     }
513 
514     const auto metadata = parser.values(metadataOption);
515     for (const QString &md : metadata) {
516         int split = md.indexOf(QLatin1Char('='));
517         QString key = md.left(split);
518         QString value = md.mid(split + 1);
519 
520         if (split == -1 || key.isEmpty() || value.isEmpty()) {
521             error("missing key or value for option '-M'");
522         } else if (key.indexOf(QLatin1Char('.')) != -1) {
523             // Don't allow keys with '.' for now, since we might need this
524             // format later for more advanced meta data API
525             error("A key cannot contain the letter '.' for option '-M'");
526         } else {
527             QJsonArray array = moc.metaArgs.value(key);
528             array.append(value);
529             moc.metaArgs.insert(key, array);
530         }
531     }
532 
533     moc.currentFilenames.push(filename.toLocal8Bit());
534     moc.includes = pp.includes;
535 
536     // 1. preprocess
537     const auto includeFiles = parser.values(includeOption);
538     QStringList validIncludesFiles;
539     for (const QString &includeName : includeFiles) {
540         QByteArray rawName = pp.resolveInclude(QFile::encodeName(includeName), moc.filename);
541         if (rawName.isEmpty()) {
542             fprintf(stderr, "Warning: Failed to resolve include \"%s\" for moc file %s\n",
543                     includeName.toLocal8Bit().constData(),
544                     moc.filename.isEmpty() ? "<standard input>" : moc.filename.constData());
545         } else {
546             QFile f(QFile::decodeName(rawName));
547             if (f.open(QIODevice::ReadOnly)) {
548                 moc.symbols += Symbol(0, MOC_INCLUDE_BEGIN, rawName);
549                 moc.symbols += pp.preprocessed(rawName, &f);
550                 moc.symbols += Symbol(0, MOC_INCLUDE_END, rawName);
551                 validIncludesFiles.append(includeName);
552             } else {
553                 fprintf(stderr, "Warning: Cannot open %s included by moc file %s: %s\n",
554                         rawName.constData(),
555                         moc.filename.isEmpty() ? "<standard input>" : moc.filename.constData(),
556                         f.errorString().toLocal8Bit().constData());
557             }
558         }
559     }
560     moc.symbols += pp.preprocessed(moc.filename, &in);
561 
562     if (!pp.preprocessOnly) {
563         // 2. parse
564         moc.parse();
565     }
566 
567     // 3. and output meta object code
568 
569     QScopedPointer<FILE, ScopedPointerFileCloser> jsonOutput;
570 
571     bool outputToFile = true;
572     if (output.size()) { // output file specified
573 #if defined(_MSC_VER)
574         if (_wfopen_s(&out, reinterpret_cast<const wchar_t *>(output.utf16()), L"w") != 0)
575 #else
576         out = fopen(QFile::encodeName(output).constData(), "w"); // create output file
577         if (!out)
578 #endif
579         {
580             fprintf(stderr, "moc: Cannot create %s\n", QFile::encodeName(output).constData());
581             return 1;
582         }
583 
584         if (parser.isSet(jsonOption)) {
585             const QString jsonOutputFileName = output + QLatin1String(".json");
586             FILE *f;
587 #if defined(_MSC_VER)
588             if (_wfopen_s(&f, reinterpret_cast<const wchar_t *>(jsonOutputFileName.utf16()), L"w") != 0)
589 #else
590             f = fopen(QFile::encodeName(jsonOutputFileName).constData(), "w");
591             if (!f)
592 #endif
593                 fprintf(stderr, "moc: Cannot create JSON output file %s. %s\n",
594                         QFile::encodeName(jsonOutputFileName).constData(),
595                         strerror(errno));
596             jsonOutput.reset(f);
597         }
598     } else { // use stdout
599         out = stdout;
600         outputToFile = false;
601     }
602 
603     if (pp.preprocessOnly) {
604         fprintf(out, "%s\n", composePreprocessorOutput(moc.symbols).constData());
605     } else {
606         if (moc.classList.isEmpty())
607             moc.note("No relevant classes found. No output generated.");
608         else
609             moc.generate(out, jsonOutput.data());
610     }
611 
612     if (output.size())
613         fclose(out);
614 
615     if (parser.isSet(depFileOption)) {
616         // 4. write a Make-style dependency file (can also be consumed by Ninja).
617         QString depOutputFileName;
618         QString depRuleName = output;
619 
620         if (parser.isSet(depFileRuleNameOption))
621             depRuleName = parser.value(depFileRuleNameOption);
622 
623         if (parser.isSet(depFilePathOption)) {
624             depOutputFileName = parser.value(depFilePathOption);
625         } else if (outputToFile) {
626             depOutputFileName = output + QLatin1String(".d");
627         } else {
628             fprintf(stderr, "moc: Writing to stdout, but no depfile path specified.\n");
629         }
630 
631         QScopedPointer<FILE, ScopedPointerFileCloser> depFileHandle;
632         FILE *depFileHandleRaw;
633 #if defined(_MSC_VER)
634         if (_wfopen_s(&depFileHandleRaw,
635                       reinterpret_cast<const wchar_t *>(depOutputFileName.utf16()), L"w") != 0)
636 #else
637         depFileHandleRaw = fopen(QFile::encodeName(depOutputFileName).constData(), "w");
638         if (!depFileHandleRaw)
639 #endif
640             fprintf(stderr, "moc: Cannot create dep output file '%s'. %s\n",
641                     QFile::encodeName(depOutputFileName).constData(),
642                     strerror(errno));
643         depFileHandle.reset(depFileHandleRaw);
644 
645         if (!depFileHandle.isNull()) {
646             // First line is the path to the generated file.
647             fprintf(depFileHandle.data(), "%s: ",
648                     escapeAndEncodeDependencyPath(depRuleName).constData());
649 
650             QByteArrayList dependencies;
651 
652             // If there's an input file, it's the first dependency.
653             if (!filename.isEmpty()) {
654                 dependencies.append(escapeAndEncodeDependencyPath(filename).constData());
655             }
656 
657             // Additional passed-in includes are dependencies (like moc_predefs.h).
658             for (const QString &includeName : validIncludesFiles) {
659                 dependencies.append(escapeAndEncodeDependencyPath(includeName).constData());
660             }
661 
662             // Plugin metadata json files discovered via Q_PLUGIN_METADATA macros are also
663             // dependencies.
664             for (const QString &pluginMetadataFile : moc.parsedPluginMetadataFiles) {
665                 dependencies.append(escapeAndEncodeDependencyPath(pluginMetadataFile).constData());
666             }
667 
668             // All pre-processed includes are dependnecies.
669             // Sort the entries for easier human consumption.
670             auto includeList = pp.preprocessedIncludes.values();
671             std::sort(includeList.begin(), includeList.end());
672 
673             for (QByteArray &includeName : includeList) {
674                 dependencies.append(escapeDependencyPath(includeName));
675             }
676 
677             // Join dependencies, output them, and output a final new line.
678             const auto dependenciesJoined = dependencies.join(QByteArrayLiteral(" \\\n  "));
679             fprintf(depFileHandle.data(), "%s\n", dependenciesJoined.constData());
680         }
681     }
682 
683     return 0;
684 }
685 
686 QT_END_NAMESPACE
687 
main(int _argc,char ** _argv)688 int main(int _argc, char **_argv)
689 {
690     return QT_PREPEND_NAMESPACE(runMoc)(_argc, _argv);
691 }
692