blob: ba559b572f2ae8c81318560b1b570e3221b72725 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2016 Intel Corporation.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "preprocessor.h"
#include "moc.h"
#include "outputrevision.h"
#include <qfile.h>
#include <qfileinfo.h>
#include <qdir.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <qcoreapplication.h>
#include <qcommandlineoption.h>
#include <qcommandlineparser.h>
QT_BEGIN_NAMESPACE
/*
This function looks at two file names and returns the name of the
infile with a path relative to outfile.
Examples:
/tmp/abc, /tmp/bcd -> abc
xyz/a/bc, xyz/b/ac -> ../a/bc
/tmp/abc, xyz/klm -> /tmp/abc
*/
static QByteArray combinePath(const QString &infile, const QString &outfile)
{
QFileInfo inFileInfo(QDir::current(), infile);
QFileInfo outFileInfo(QDir::current(), outfile);
const QByteArray relativePath = QFile::encodeName(outFileInfo.dir().relativeFilePath(inFileInfo.filePath()));
#ifdef Q_OS_WIN
// It's a system limitation.
// It depends on the Win API function which is used by the program to open files.
// cl apparently uses the functions that have the MAX_PATH limitation.
if (outFileInfo.dir().absolutePath().length() + relativePath.length() + 1 >= 260)
return QFile::encodeName(inFileInfo.absoluteFilePath());
#endif
return relativePath;
}
void error(const char *msg = "Invalid argument")
{
if (msg)
fprintf(stderr, "moc: %s\n", msg);
}
static inline bool hasNext(const Symbols &symbols, int i)
{ return (i < symbols.size()); }
static inline const Symbol &next(const Symbols &symbols, int &i)
{ return symbols.at(i++); }
QByteArray composePreprocessorOutput(const Symbols &symbols) {
QByteArray output;
int lineNum = 1;
Token last = PP_NOTOKEN;
Token secondlast = last;
int i = 0;
while (hasNext(symbols, i)) {
Symbol sym = next(symbols, i);
switch (sym.token) {
case PP_NEWLINE:
case PP_WHITESPACE:
if (last != PP_WHITESPACE) {
secondlast = last;
last = PP_WHITESPACE;
output += ' ';
}
continue;
case PP_STRING_LITERAL:
if (last == PP_STRING_LITERAL)
output.chop(1);
else if (secondlast == PP_STRING_LITERAL && last == PP_WHITESPACE)
output.chop(2);
else
break;
output += sym.lexem().mid(1);
secondlast = last;
last = PP_STRING_LITERAL;
continue;
case MOC_INCLUDE_BEGIN:
lineNum = 0;
continue;
case MOC_INCLUDE_END:
lineNum = sym.lineNum;
continue;
default:
break;
}
secondlast = last;
last = sym.token;
const int padding = sym.lineNum - lineNum;
if (padding > 0) {
output.resize(output.size() + padding);
memset(output.data() + output.size() - padding, '\n', padding);
lineNum = sym.lineNum;
}
output += sym.lexem();
}
return output;
}
static QStringList argumentsFromCommandLineAndFile(const QStringList &arguments)
{
QStringList allArguments;
allArguments.reserve(arguments.size());
for (const QString &argument : arguments) {
// "@file" doesn't start with a '-' so we can't use QCommandLineParser for it
if (argument.startsWith(QLatin1Char('@'))) {
QString optionsFile = argument;
optionsFile.remove(0, 1);
if (optionsFile.isEmpty()) {
error("The @ option requires an input file");
return QStringList();
}
QFile f(optionsFile);
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
error("Cannot open options file specified with @");
return QStringList();
}
while (!f.atEnd()) {
QString line = QString::fromLocal8Bit(f.readLine().trimmed());
if (!line.isEmpty())
allArguments << line;
}
} else {
allArguments << argument;
}
}
return allArguments;
}
int runMoc(int argc, char **argv)
{
QCoreApplication app(argc, argv);
QCoreApplication::setApplicationVersion(QString::fromLatin1(QT_VERSION_STR));
bool autoInclude = true;
bool defaultInclude = true;
Preprocessor pp;
Moc moc;
pp.macros["Q_MOC_RUN"];
pp.macros["__cplusplus"];
// Don't stumble over GCC extensions
Macro dummyVariadicFunctionMacro;
dummyVariadicFunctionMacro.isFunction = true;
dummyVariadicFunctionMacro.isVariadic = true;
dummyVariadicFunctionMacro.arguments += Symbol(0, PP_IDENTIFIER, "__VA_ARGS__");
pp.macros["__attribute__"] = dummyVariadicFunctionMacro;
pp.macros["__declspec"] = dummyVariadicFunctionMacro;
QString filename;
QString output;
QFile in;
FILE *out = 0;
// Note that moc isn't translated.
// If you use this code as an example for a translated app, make sure to translate the strings.
QCommandLineParser parser;
parser.setApplicationDescription(QStringLiteral("Qt Meta Object Compiler version %1 (Qt %2)")
.arg(mocOutputRevision).arg(QString::fromLatin1(QT_VERSION_STR)));
parser.addHelpOption();
parser.addVersionOption();
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
QCommandLineOption outputOption(QStringLiteral("o"));
outputOption.setDescription(QStringLiteral("Write output to file rather than stdout."));
outputOption.setValueName(QStringLiteral("file"));
outputOption.setFlags(QCommandLineOption::ShortOptionStyle);
parser.addOption(outputOption);
QCommandLineOption includePathOption(QStringLiteral("I"));
includePathOption.setDescription(QStringLiteral("Add dir to the include path for header files."));
includePathOption.setValueName(QStringLiteral("dir"));
includePathOption.setFlags(QCommandLineOption::ShortOptionStyle);
parser.addOption(includePathOption);
QCommandLineOption macFrameworkOption(QStringLiteral("F"));
macFrameworkOption.setDescription(QStringLiteral("Add Mac framework to the include path for header files."));
macFrameworkOption.setValueName(QStringLiteral("framework"));
macFrameworkOption.setFlags(QCommandLineOption::ShortOptionStyle);
parser.addOption(macFrameworkOption);
QCommandLineOption preprocessOption(QStringLiteral("E"));
preprocessOption.setDescription(QStringLiteral("Preprocess only; do not generate meta object code."));
parser.addOption(preprocessOption);
QCommandLineOption defineOption(QStringLiteral("D"));
defineOption.setDescription(QStringLiteral("Define macro, with optional definition."));
defineOption.setValueName(QStringLiteral("macro[=def]"));
defineOption.setFlags(QCommandLineOption::ShortOptionStyle);
parser.addOption(defineOption);
QCommandLineOption undefineOption(QStringLiteral("U"));
undefineOption.setDescription(QStringLiteral("Undefine macro."));
undefineOption.setValueName(QStringLiteral("macro"));
undefineOption.setFlags(QCommandLineOption::ShortOptionStyle);
parser.addOption(undefineOption);
QCommandLineOption metadataOption(QStringLiteral("M"));
metadataOption.setDescription(QStringLiteral("Add key/value pair to plugin meta data"));
metadataOption.setValueName(QStringLiteral("key=value"));
metadataOption.setFlags(QCommandLineOption::ShortOptionStyle);
parser.addOption(metadataOption);
QCommandLineOption compilerFlavorOption(QStringLiteral("compiler-flavor"));
compilerFlavorOption.setDescription(QStringLiteral("Set the compiler flavor: either \"msvc\" or \"unix\"."));
compilerFlavorOption.setValueName(QStringLiteral("flavor"));
parser.addOption(compilerFlavorOption);
QCommandLineOption noIncludeOption(QStringLiteral("i"));
noIncludeOption.setDescription(QStringLiteral("Do not generate an #include statement."));
parser.addOption(noIncludeOption);
QCommandLineOption pathPrefixOption(QStringLiteral("p"));
pathPrefixOption.setDescription(QStringLiteral("Path prefix for included file."));
pathPrefixOption.setValueName(QStringLiteral("path"));
pathPrefixOption.setFlags(QCommandLineOption::ShortOptionStyle);
parser.addOption(pathPrefixOption);
QCommandLineOption forceIncludeOption(QStringLiteral("f"));
forceIncludeOption.setDescription(QStringLiteral("Force #include <file> (overwrite default)."));
forceIncludeOption.setValueName(QStringLiteral("file"));
forceIncludeOption.setFlags(QCommandLineOption::ShortOptionStyle);
parser.addOption(forceIncludeOption);
QCommandLineOption prependIncludeOption(QStringLiteral("b"));
prependIncludeOption.setDescription(QStringLiteral("Prepend #include <file> (preserve default include)."));
prependIncludeOption.setValueName(QStringLiteral("file"));
prependIncludeOption.setFlags(QCommandLineOption::ShortOptionStyle);
parser.addOption(prependIncludeOption);
QCommandLineOption includeOption(QStringLiteral("include"));
includeOption.setDescription(QStringLiteral("Parse <file> as an #include before the main source(s)."));
includeOption.setValueName(QStringLiteral("file"));
parser.addOption(includeOption);
QCommandLineOption noNotesWarningsCompatOption(QStringLiteral("n"));
noNotesWarningsCompatOption.setDescription(QStringLiteral("Do not display notes (-nn) or warnings (-nw). Compatibility option."));
noNotesWarningsCompatOption.setValueName(QStringLiteral("which"));
noNotesWarningsCompatOption.setFlags(QCommandLineOption::ShortOptionStyle);
parser.addOption(noNotesWarningsCompatOption);
QCommandLineOption noNotesOption(QStringLiteral("no-notes"));
noNotesOption.setDescription(QStringLiteral("Do not display notes."));
parser.addOption(noNotesOption);
QCommandLineOption noWarningsOption(QStringLiteral("no-warnings"));
noWarningsOption.setDescription(QStringLiteral("Do not display warnings (implies --no-notes)."));
parser.addOption(noWarningsOption);
QCommandLineOption ignoreConflictsOption(QStringLiteral("ignore-option-clashes"));
ignoreConflictsOption.setDescription(QStringLiteral("Ignore all options that conflict with compilers, like -pthread conflicting with moc's -p option."));
parser.addOption(ignoreConflictsOption);
parser.addPositionalArgument(QStringLiteral("[header-file]"),
QStringLiteral("Header file to read from, otherwise stdin."));
parser.addPositionalArgument(QStringLiteral("[@option-file]"),
QStringLiteral("Read additional options from option-file."));
const QStringList arguments = argumentsFromCommandLineAndFile(app.arguments());
if (arguments.isEmpty())
return 1;
parser.process(arguments);
const QStringList files = parser.positionalArguments();
if (files.count() > 1) {
error(qPrintable(QLatin1String("Too many input files specified: '") + files.join(QLatin1String("' '")) + QLatin1Char('\'')));
parser.showHelp(1);
} else if (!files.isEmpty()) {
filename = files.first();
}
const bool ignoreConflictingOptions = parser.isSet(ignoreConflictsOption);
output = parser.value(outputOption);
pp.preprocessOnly = parser.isSet(preprocessOption);
if (parser.isSet(noIncludeOption)) {
moc.noInclude = true;
autoInclude = false;
}
if (!ignoreConflictingOptions) {
if (parser.isSet(forceIncludeOption)) {
moc.noInclude = false;
autoInclude = false;
const auto forceIncludes = parser.values(forceIncludeOption);
for (const QString &include : forceIncludes) {
moc.includeFiles.append(QFile::encodeName(include));
defaultInclude = false;
}
}
const auto prependIncludes = parser.values(prependIncludeOption);
for (const QString &include : prependIncludes)
moc.includeFiles.prepend(QFile::encodeName(include));
if (parser.isSet(pathPrefixOption))
moc.includePath = QFile::encodeName(parser.value(pathPrefixOption));
}
const auto includePaths = parser.values(includePathOption);
for (const QString &path : includePaths)
pp.includes += Preprocessor::IncludePath(QFile::encodeName(path));
QString compilerFlavor = parser.value(compilerFlavorOption);
if (compilerFlavor.isEmpty() || compilerFlavor == QLatin1String("unix")) {
// traditional Unix compilers use both CPATH and CPLUS_INCLUDE_PATH
// $CPATH feeds to #include <...> and #include "...", whereas
// CPLUS_INCLUDE_PATH is equivalent to GCC's -isystem, so we parse later
const auto cpath = qgetenv("CPATH").split(QDir::listSeparator().toLatin1());
for (const QByteArray &p : cpath)
pp.includes += Preprocessor::IncludePath(p);
const auto cplus_include_path = qgetenv("CPLUS_INCLUDE_PATH").split(QDir::listSeparator().toLatin1());
for (const QByteArray &p : cplus_include_path)
pp.includes += Preprocessor::IncludePath(p);
} else if (compilerFlavor == QLatin1String("msvc")) {
// MSVC uses one environment variable: INCLUDE
const auto include = qgetenv("INCLUDE").split(QDir::listSeparator().toLatin1());
for (const QByteArray &p : include)
pp.includes += Preprocessor::IncludePath(p);
} else {
error(qPrintable(QLatin1String("Unknown compiler flavor '") + compilerFlavor +
QLatin1String("'; valid values are: msvc, unix.")));
parser.showHelp(1);
}
const auto macFrameworks = parser.values(macFrameworkOption);
for (const QString &path : macFrameworks) {
// minimalistic framework support for the mac
Preprocessor::IncludePath p(QFile::encodeName(path));
p.isFrameworkPath = true;
pp.includes += p;
}
const auto defines = parser.values(defineOption);
for (const QString &arg : defines) {
QByteArray name = arg.toLocal8Bit();
QByteArray value("1");
int eq = name.indexOf('=');
if (eq >= 0) {
value = name.mid(eq + 1);
name = name.left(eq);
}
if (name.isEmpty()) {
error("Missing macro name");
parser.showHelp(1);
}
Macro macro;
macro.symbols = Preprocessor::tokenize(value, 1, Preprocessor::TokenizeDefine);
macro.symbols.removeLast(); // remove the EOF symbol
pp.macros.insert(name, macro);
}
const auto undefines = parser.values(undefineOption);
for (const QString &arg : undefines) {
QByteArray macro = arg.toLocal8Bit();
if (macro.isEmpty()) {
error("Missing macro name");
parser.showHelp(1);
}
pp.macros.remove(macro);
}
const QStringList noNotesCompatValues = parser.values(noNotesWarningsCompatOption);
if (parser.isSet(noNotesOption) || noNotesCompatValues.contains(QLatin1String("n")))
moc.displayNotes = false;
if (parser.isSet(noWarningsOption) || noNotesCompatValues.contains(QLatin1String("w")))
moc.displayWarnings = moc.displayNotes = false;
if (autoInclude) {
int spos = filename.lastIndexOf(QDir::separator());
int ppos = filename.lastIndexOf(QLatin1Char('.'));
// spos >= -1 && ppos > spos => ppos >= 0
moc.noInclude = (ppos > spos && filename.at(ppos + 1).toLower() != QLatin1Char('h'));
}
if (defaultInclude) {
if (moc.includePath.isEmpty()) {
if (filename.size()) {
if (output.size())
moc.includeFiles.append(combinePath(filename, output));
else
moc.includeFiles.append(QFile::encodeName(filename));
}
} else {
moc.includeFiles.append(combinePath(filename, filename));
}
}
if (filename.isEmpty()) {
filename = QStringLiteral("standard input");
in.open(stdin, QIODevice::ReadOnly);
} else {
in.setFileName(filename);
if (!in.open(QIODevice::ReadOnly)) {
fprintf(stderr, "moc: %s: No such file\n", qPrintable(filename));
return 1;
}
moc.filename = filename.toLocal8Bit();
}
const auto metadata = parser.values(metadataOption);
for (const QString &md : metadata) {
int split = md.indexOf(QLatin1Char('='));
QString key = md.left(split);
QString value = md.mid(split + 1);
if (split == -1 || key.isEmpty() || value.isEmpty()) {
error("missing key or value for option '-M'");
} else if (key.indexOf(QLatin1Char('.')) != -1) {
// Don't allow keys with '.' for now, since we might need this
// format later for more advanced meta data API
error("A key cannot contain the letter '.' for option '-M'");
} else {
QJsonArray array = moc.metaArgs.value(key);
array.append(value);
moc.metaArgs.insert(key, array);
}
}
moc.currentFilenames.push(filename.toLocal8Bit());
moc.includes = pp.includes;
// 1. preprocess
const auto includeFiles = parser.values(includeOption);
for (const QString &includeName : includeFiles) {
QByteArray rawName = pp.resolveInclude(QFile::encodeName(includeName), moc.filename);
if (rawName.isEmpty()) {
fprintf(stderr, "Warning: Failed to resolve include \"%s\" for moc file %s\n",
includeName.toLocal8Bit().constData(),
moc.filename.isEmpty() ? "<standard input>" : moc.filename.constData());
} else {
QFile f(QFile::decodeName(rawName));
if (f.open(QIODevice::ReadOnly)) {
moc.symbols += Symbol(0, MOC_INCLUDE_BEGIN, rawName);
moc.symbols += pp.preprocessed(rawName, &f);
moc.symbols += Symbol(0, MOC_INCLUDE_END, rawName);
} else {
fprintf(stderr, "Warning: Cannot open %s included by moc file %s: %s\n",
rawName.constData(),
moc.filename.isEmpty() ? "<standard input>" : moc.filename.constData(),
f.errorString().toLocal8Bit().constData());
}
}
}
moc.symbols += pp.preprocessed(moc.filename, &in);
if (!pp.preprocessOnly) {
// 2. parse
moc.parse();
}
// 3. and output meta object code
if (output.size()) { // output file specified
#if defined(_MSC_VER)
if (_wfopen_s(&out, reinterpret_cast<const wchar_t *>(output.utf16()), L"w") != 0)
#else
out = fopen(QFile::encodeName(output).constData(), "w"); // create output file
if (!out)
#endif
{
fprintf(stderr, "moc: Cannot create %s\n", QFile::encodeName(output).constData());
return 1;
}
} else { // use stdout
out = stdout;
}
if (pp.preprocessOnly) {
fprintf(out, "%s\n", composePreprocessorOutput(moc.symbols).constData());
} else {
if (moc.classList.isEmpty())
moc.note("No relevant classes found. No output generated.");
else
moc.generate(out);
}
if (output.size())
fclose(out);
return 0;
}
QT_END_NAMESPACE
int main(int _argc, char **_argv)
{
return QT_PREPEND_NAMESPACE(runMoc)(_argc, _argv);
}