Add support for machine-readable JSON output to the MOC

The --output-json parameter will make moc produce a .json file next to
the regular output file. With --collect-json the .json files for a
module can be merged into a single one.

Task-number: QTBUG-68796
Change-Id: I0e8fb802d47bd22da219701a8df947973d4bd7b5
Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
Simon Hausmann 2018-12-17 15:09:23 +01:00 committed by Ulf Hermann
parent 64473c9320
commit da284ef10e
11 changed files with 3144 additions and 6 deletions

View File

@ -0,0 +1,34 @@
qtPrepareTool(MOC_COLLECT_JSON, moc)
QMAKE_MOC_OPTIONS += --output-json
moc_json_header.input = HEADERS
moc_json_header.output = $$MOC_DIR/$${QMAKE_H_MOD_MOC}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_CPP)}.json
moc_json_header.CONFIG = no_link moc_verify
moc_json_header.depends = $$MOC_DIR/$${QMAKE_H_MOD_MOC}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_CPP)}
moc_json_header.commands = $$escape_expand(\\n) # force creation of rule
moc_json_header.variable_out = MOC_JSON_FILES
moc_json_source.input = SOURCES
moc_json_source.output = $$MOC_DIR/$${QMAKE_CPP_MOD_MOC}${QMAKE_FILE_BASE}$${QMAKE_EXT_CPP_MOC}.json
moc_json_source.CONFIG = no_link moc_verify
moc_json_source.depends = $$MOC_DIR/$${QMAKE_CPP_MOD_MOC}${QMAKE_FILE_BASE}$${QMAKE_EXT_CPP_MOC}
moc_json_source.commands = $$escape_expand(\\n) # force creation of rule
moc_json_source.variable_out = MOC_JSON_FILES
MOC_COLLECT_JSON_OUTPUT = $$lower($$basename(TARGET))_metatypes.json
moc_collect_json.CONFIG += no_link combine
moc_collect_json.commands = $$MOC_COLLECT_JSON --collect-json -o ${QMAKE_FILE_OUT} ${QMAKE_FILE_IN}
moc_collect_json.input = MOC_JSON_FILES
moc_collect_json.output = $$MOC_COLLECT_JSON_OUTPUT
moc_collect_json.name = Collect moc JSON output into central file
install_metatypes {
do_install.path = $$[QT_INSTALL_LIBS]/metatypes
do_install.files = $$OUT_PWD/$$MOC_COLLECT_JSON_OUTPUT
prefix_build: INSTALLS += do_install
else: COPIES += do_install
}
QMAKE_EXTRA_COMPILERS += moc_collect_json moc_json_header moc_json_source

View File

@ -0,0 +1,103 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite 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 <qfile.h>
#include <qjsonarray.h>
#include <qjsondocument.h>
#include <qjsonobject.h>
#include <qhashfunctions.h>
#include <qstringlist.h>
#include <cstdlib>
static bool readFromDevice(QIODevice *device, QJsonArray *allMetaObjects)
{
const QByteArray contents = device->readAll();
if (contents.isEmpty())
return true;
QJsonParseError error {};
QJsonDocument metaObjects = QJsonDocument::fromJson(contents, &error);
if (error.error != QJsonParseError::NoError) {
fprintf(stderr, "%s at %d\n", error.errorString().toUtf8().constData(), error.offset);
return false;
}
allMetaObjects->append(metaObjects.object());
return true;
}
int collectJson(const QStringList &jsonFiles, const QString &outputFile)
{
qSetGlobalQHashSeed(0);
QFile output;
if (outputFile.isEmpty()) {
if (!output.open(stdout, QIODevice::WriteOnly)) {
fprintf(stderr, "Error opening stdout for writing\n");
return EXIT_FAILURE;
}
} else {
output.setFileName(outputFile);
if (!output.open(QIODevice::WriteOnly)) {
fprintf(stderr, "Error opening %s for writing\n", qPrintable(outputFile));
return EXIT_FAILURE;
}
}
QJsonArray allMetaObjects;
if (jsonFiles.isEmpty()) {
QFile f;
if (!f.open(stdin, QIODevice::ReadOnly)) {
fprintf(stderr, "Error opening stdin for reading\n");
return EXIT_FAILURE;
}
if (!readFromDevice(&f, &allMetaObjects)) {
fprintf(stderr, "Error parsing data from stdin\n");
return EXIT_FAILURE;
}
}
for (const QString &jsonFile: jsonFiles) {
QFile f(jsonFile);
if (!f.open(QIODevice::ReadOnly)) {
fprintf(stderr, "Error opening %s for reading\n", qPrintable(jsonFile));
return EXIT_FAILURE;
}
if (!readFromDevice(&f, &allMetaObjects)) {
fprintf(stderr, "Error parsing %s\n", qPrintable(jsonFile));
return EXIT_FAILURE;
}
}
QJsonDocument doc(allMetaObjects);
output.write(doc.toJson());
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,42 @@
/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** 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$
**
****************************************************************************/
#ifndef COLLECTJSON_H
#define COLLECTJSON_H
#include <qglobal.h>
#include <qstring.h>
#include <qstringlist.h>
QT_BEGIN_NAMESPACE
int collectJson(const QStringList &jsonFiles, const QString &outputFile);
QT_END_NAMESPACE
#endif // COLLECTOJSON_H

View File

@ -30,6 +30,7 @@
#include "preprocessor.h"
#include "moc.h"
#include "outputrevision.h"
#include "collectjson.h"
#include <qfile.h>
#include <qfileinfo.h>
@ -37,10 +38,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <qcoreapplication.h>
#include <qcommandlineoption.h>
#include <qcommandlineparser.h>
#include <qscopedpointer.h>
QT_BEGIN_NAMESPACE
@ -77,6 +80,10 @@ void error(const char *msg = "Invalid argument")
fprintf(stderr, "moc: %s\n", msg);
}
struct ScopedPointerFileCloser
{
static inline void cleanup(FILE *handle) { if (handle) fclose(handle); }
};
static inline bool hasNext(const Symbols &symbols, int i)
{ return (i < symbols.size()); }
@ -293,10 +300,20 @@ int runMoc(int argc, char **argv)
ignoreConflictsOption.setDescription(QStringLiteral("Ignore all options that conflict with compilers, like -pthread conflicting with moc's -p option."));
parser.addOption(ignoreConflictsOption);
QCommandLineOption jsonOption(QStringLiteral("output-json"));
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."));
parser.addOption(jsonOption);
QCommandLineOption collectOption(QStringLiteral("collect-json"));
collectOption.setDescription(QStringLiteral("Instead of processing C++ code, collect previously generated JSON output into a single file."));
parser.addOption(collectOption);
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."));
parser.addPositionalArgument(QStringLiteral("[MOC generated json file]"),
QStringLiteral("MOC generated json output"));
const QStringList arguments = argumentsFromCommandLineAndFile(app.arguments());
if (arguments.isEmpty())
@ -305,6 +322,10 @@ int runMoc(int argc, char **argv)
parser.process(arguments);
const QStringList files = parser.positionalArguments();
output = parser.value(outputOption);
if (parser.isSet(collectOption))
return collectJson(files, output);
if (files.count() > 1) {
error(qPrintable(QLatin1String("Too many input files specified: '") + files.join(QLatin1String("' '")) + QLatin1Char('\'')));
parser.showHelp(1);
@ -313,7 +334,6 @@ int runMoc(int argc, char **argv)
}
const bool ignoreConflictingOptions = parser.isSet(ignoreConflictsOption);
output = parser.value(outputOption);
pp.preprocessOnly = parser.isSet(preprocessOption);
if (parser.isSet(noIncludeOption)) {
moc.noInclude = true;
@ -485,6 +505,8 @@ int runMoc(int argc, char **argv)
// 3. and output meta object code
QScopedPointer<FILE, ScopedPointerFileCloser> jsonOutput;
if (output.size()) { // output file specified
#if defined(_MSC_VER)
if (_wfopen_s(&out, reinterpret_cast<const wchar_t *>(output.utf16()), L"w") != 0)
@ -496,6 +518,21 @@ int runMoc(int argc, char **argv)
fprintf(stderr, "moc: Cannot create %s\n", QFile::encodeName(output).constData());
return 1;
}
if (parser.isSet(jsonOption)) {
const QString jsonOutputFileName = output + QLatin1String(".json");
FILE *f;
#if defined(_MSC_VER)
if (_wfopen_s(&f, reinterpret_cast<const wchar_t *>(jsonOutputFileName.utf16()), L"w") != 0)
#else
f = fopen(QFile::encodeName(jsonOutputFileName).constData(), "w");
if (!f)
#endif
fprintf(stderr, "moc: Cannot create JSON output file %s. %s\n",
QFile::encodeName(jsonOutputFileName).constData(),
strerror(errno));
jsonOutput.reset(f);
}
} else { // use stdout
out = stdout;
}
@ -506,7 +543,7 @@ int runMoc(int argc, char **argv)
if (moc.classList.isEmpty())
moc.note("No relevant classes found. No output generated.");
else
moc.generate(out);
moc.generate(out, jsonOutput.data());
}
if (output.size())

View File

@ -35,6 +35,7 @@
#include <QtCore/qfile.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qdir.h>
#include <QtCore/qjsondocument.h>
// for normalizeTypeInternal
#include <private/qmetaobject_moc_p.h>
@ -999,7 +1000,7 @@ static QByteArrayList requiredQtContainers(const QVector<ClassDef> &classes)
return required;
}
void Moc::generate(FILE *out)
void Moc::generate(FILE *out, FILE *jsonOutput)
{
QByteArray fn = filename;
int i = filename.length()-1;
@ -1062,6 +1063,23 @@ void Moc::generate(FILE *out)
fprintf(out, "QT_WARNING_POP\n");
fprintf(out, "QT_END_MOC_NAMESPACE\n");
if (jsonOutput) {
QJsonObject mocData;
mocData[QLatin1String("outputRevision")] = mocOutputRevision;
mocData[QLatin1String("inputFile")] = QLatin1String(fn.constData());
QJsonArray classesJsonFormatted;
for (const ClassDef &cdef: qAsConst(classList))
classesJsonFormatted.append(cdef.toJson());
if (!classesJsonFormatted.isEmpty())
mocData[QLatin1String("classes")] = classesJsonFormatted;
QJsonDocument jsonDoc(mocData);
fputs(jsonDoc.toJson().constData(), jsonOutput);
}
}
void Moc::parseSlots(ClassDef *def, FunctionDef::Access access)
@ -1784,6 +1802,186 @@ void Moc::checkProperties(ClassDef *cdef)
}
}
QJsonObject ClassDef::toJson() const
{
QJsonObject cls;
cls[QLatin1String("className")] = QString::fromUtf8(classname.constData());
cls[QLatin1String("qualifiedClassName")] = QString::fromUtf8(qualified.constData());
QJsonArray classInfos;
for (const auto &info: qAsConst(classInfoList)) {
QJsonObject infoJson;
infoJson[QLatin1String("name")] = QString::fromUtf8(info.name);
infoJson[QLatin1String("value")] = QString::fromUtf8(info.value);
classInfos.append(infoJson);
}
if (classInfos.size())
cls[QLatin1String("classInfos")] = classInfos;
const auto appendFunctions = [&cls](const QString &type, const QVector<FunctionDef> &funcs) {
QJsonArray jsonFuncs;
for (const FunctionDef &fdef: funcs)
jsonFuncs.append(fdef.toJson());
if (!jsonFuncs.isEmpty())
cls[type] = jsonFuncs;
};
appendFunctions(QLatin1String("signals"), signalList);
appendFunctions(QLatin1String("slots"), slotList);
appendFunctions(QLatin1String("constructors"), constructorList);
appendFunctions(QLatin1String("methods"), methodList);
QJsonArray props;
for (const PropertyDef &propDef: qAsConst(propertyList))
props.append(propDef.toJson());
if (!props.isEmpty())
cls[QLatin1String("properties")] = props;
if (hasQGadget)
cls[QLatin1String("gadget")] = true;
QJsonArray superClasses;
for (const auto &super: qAsConst(superclassList)) {
const auto name = super.first;
const auto access = super.second;
QJsonObject superCls;
superCls[QLatin1String("name")] = QString::fromUtf8(name);
FunctionDef::accessToJson(&superCls, access);
superClasses.append(superCls);
}
if (!superClasses.isEmpty())
cls[QLatin1String("superClasses")] = superClasses;
QJsonArray enums;
for (const EnumDef &enumDef: qAsConst(enumList))
enums.append(enumDef.toJson(*this));
if (!enums.isEmpty())
cls[QLatin1String("enums")] = enums;
QJsonArray ifaces;
for (const QVector<Interface> &ifaceList: interfaceList) {
QJsonArray jsonList;
for (const Interface &iface: ifaceList) {
QJsonObject ifaceJson;
ifaceJson[QLatin1String("id")] = QString::fromUtf8(iface.interfaceId);
ifaceJson[QLatin1String("className")] = QString::fromUtf8(iface.className);
jsonList.append(ifaceJson);
}
ifaces.append(jsonList);
}
if (!ifaces.isEmpty())
cls[QLatin1String("interfaces")] = ifaces;
return cls;
}
QJsonObject FunctionDef::toJson() const
{
QJsonObject fdef;
fdef[QLatin1String("name")] = QString::fromUtf8(name);
if (!tag.isEmpty())
fdef[QLatin1String("tag")] = QString::fromUtf8(tag);
fdef[QLatin1String("returnType")] = QString::fromUtf8(normalizedType);
QJsonArray args;
for (const ArgumentDef &arg: arguments)
args.append(arg.toJson());
if (!args.isEmpty())
fdef[QLatin1String("arguments")] = args;
accessToJson(&fdef, access);
if (revision > 0)
fdef[QLatin1String("revision")] = revision;
return fdef;
}
void FunctionDef::accessToJson(QJsonObject *obj, FunctionDef::Access acs)
{
switch (acs) {
case Private: (*obj)[QLatin1String("access")] = QLatin1String("private"); break;
case Public: (*obj)[QLatin1String("access")] = QLatin1String("public"); break;
case Protected: (*obj)[QLatin1String("access")] = QLatin1String("protected"); break;
}
}
QJsonObject ArgumentDef::toJson() const
{
QJsonObject arg;
arg[QLatin1String("type")] = QString::fromUtf8(normalizedType);
if (!name.isEmpty())
arg[QLatin1String("name")] = QString::fromUtf8(name);
return arg;
}
QJsonObject PropertyDef::toJson() const
{
QJsonObject prop;
prop[QLatin1String("name")] = QString::fromUtf8(name);
prop[QLatin1String("type")] = QString::fromUtf8(type);
const auto jsonify = [&prop](const char *str, const QByteArray &member) {
if (!member.isEmpty())
prop[QLatin1String(str)] = QString::fromUtf8(member);
};
jsonify("member", member);
jsonify("read", read);
jsonify("write", write);
jsonify("reset", reset);
jsonify("notify", notify);
jsonify("privateClass", inPrivateClass);
const auto jsonifyBoolOrString = [&prop](const char *str, const QByteArray &boolOrString) {
QJsonValue value;
if (boolOrString == "true")
value = true;
else if (boolOrString == "false")
value = false;
else
value = QString::fromUtf8(boolOrString); // function name to query at run-time
prop[QLatin1String(str)] = value;
};
jsonifyBoolOrString("designable", designable);
jsonifyBoolOrString("scriptable", scriptable);
jsonifyBoolOrString("stored", stored);
jsonifyBoolOrString("user", user);
prop[QLatin1String("constant")] = constant;
prop[QLatin1String("final")] = final;
if (revision > 0)
prop[QLatin1String("revision")] = revision;
return prop;
}
QJsonObject EnumDef::toJson(const ClassDef &cdef) const
{
QJsonObject def;
def[QLatin1String("name")] = QString::fromUtf8(name);
if (!enumName.isEmpty())
def[QLatin1String("alias")] = QString::fromUtf8(enumName);
def[QLatin1String("isFlag")] = cdef.enumDeclarations.value(name);
def[QLatin1String("isClass")] = isEnumClass;
QJsonArray valueArr;
for (const QByteArray &value: values)
valueArr.append(QString::fromUtf8(value));
if (!valueArr.isEmpty())
def[QLatin1String("values")] = valueArr;
return def;
}
QT_END_NAMESPACE

View File

@ -61,6 +61,7 @@ struct Type
};
Q_DECLARE_TYPEINFO(Type, Q_MOVABLE_TYPE);
struct ClassDef;
struct EnumDef
{
QByteArray name;
@ -68,6 +69,7 @@ struct EnumDef
QVector<QByteArray> values;
bool isEnumClass; // c++11 enum class
EnumDef() : isEnumClass(false) {}
QJsonObject toJson(const ClassDef &cdef) const;
};
Q_DECLARE_TYPEINFO(EnumDef, Q_MOVABLE_TYPE);
@ -78,6 +80,8 @@ struct ArgumentDef
QByteArray rightType, normalizedType, name;
QByteArray typeNameForCast; // type name to be used in cast from void * in metacall
bool isDefault;
QJsonObject toJson() const;
};
Q_DECLARE_TYPEINFO(ArgumentDef, Q_MOVABLE_TYPE);
@ -111,6 +115,9 @@ struct FunctionDef
bool isConstructor = false;
bool isDestructor = false;
bool isAbstract = false;
QJsonObject toJson() const;
static void accessToJson(QJsonObject *obj, Access acs);
};
Q_DECLARE_TYPEINFO(FunctionDef, Q_MOVABLE_TYPE);
@ -130,6 +137,8 @@ struct PropertyDef
int revision = 0;
bool constant = false;
bool final = false;
QJsonObject toJson() const;
};
Q_DECLARE_TYPEINFO(PropertyDef, Q_MOVABLE_TYPE);
@ -183,6 +192,7 @@ struct ClassDef : BaseDef {
bool hasQObject = false;
bool hasQGadget = false;
QJsonObject toJson() const;
};
Q_DECLARE_TYPEINFO(ClassDef, Q_MOVABLE_TYPE);
Q_DECLARE_TYPEINFO(ClassDef::Interface, Q_MOVABLE_TYPE);
@ -215,7 +225,7 @@ public:
QMap<QString, QJsonArray> metaArgs;
void parse();
void generate(FILE *out);
void generate(FILE *out, FILE *jsonOutput);
bool parseClassHead(ClassDef *def);
inline bool inClass(const ClassDef *def) const {

View File

@ -10,9 +10,12 @@ HEADERS = $$PWD/moc.h \
$$PWD/utils.h \
$$PWD/generator.h \
$$PWD/outputrevision.h \
$$PWD/cbordevice.h
$$PWD/cbordevice.h \
$$PWD/collectjson.h
SOURCES = $$PWD/moc.cpp \
$$PWD/preprocessor.cpp \
$$PWD/generator.cpp \
$$PWD/parser.cpp \
$$PWD/token.cpp
$$PWD/token.cpp \
$$PWD/collectjson.cpp

View File

@ -1 +1,2 @@
tst_moc
allmocs.json

File diff suppressed because it is too large Load Diff

View File

@ -32,6 +32,9 @@ HEADERS += using-namespaces.h no-keywords.h task87883.h c-comments.h backslash-n
namespace.h cxx17-namespaces.h \
cxx-attributes.h
# No platform specifics in the JSON files, so that we can compare them
JSON_HEADERS = $$HEADERS
JSON_HEADERS -= cxx-attributes.h
if(*-g++*|*-icc*|*-clang*|*-llvm):!win32-*: HEADERS += os9-newlines.h win-newlines.h
if(*-g++*|*-clang*): HEADERS += dollars.h
@ -50,3 +53,49 @@ QMAKE_MOC_OPTIONS += -Muri=com.company.app -Muri=com.company.app.private
# Define macro on the command lines used in parse-defines.h
QMAKE_MOC_OPTIONS += "-DDEFINE_CMDLINE_EMPTY=" "\"-DDEFINE_CMDLINE_SIGNAL=void cmdlineSignal(const QMap<int, int> &i)\""
QMAKE_MOC_OPTIONS += --output-json
debug_and_release {
CONFIG(debug, debug|release) {
MOC_CPP_DIR = $$MOC_DIR/debug
} else {
MOC_CPP_DIR = $$MOC_DIR/release
}
} else {
MOC_CPP_DIR = $$MOC_DIR
}
moc_json_header.input = JSON_HEADERS
moc_json_header.output = $$MOC_CPP_DIR/$${QMAKE_H_MOD_MOC}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_CPP)}.json
moc_json_header.CONFIG = no_link moc_verify
moc_json_header.depends = $$MOC_CPP_DIR/$${QMAKE_H_MOD_MOC}${QMAKE_FILE_BASE}$${first(QMAKE_EXT_CPP)}
moc_json_header.commands = $$escape_expand(\\n) # force creation of rule
moc_json_header.variable_out = MOC_JSON_HEADERS
BASELINE_IN = allmocs_baseline_in.json
copy_baseline.commands = $${QMAKE_COPY} $$shell_path(${QMAKE_FILE_NAME}) ${QMAKE_FILE_OUT}
copy_baseline.input = BASELINE_IN
copy_baseline.output = $$OUT_PWD/allmocs_baseline.json
copy_baseline.CONFIG = no_link
qtPrepareTool(MOC_COLLECT_JSON, moc)
jsoncollector.CONFIG += combine
jsoncollector.commands = $$MOC_COLLECT_JSON --collect-json -o ${QMAKE_FILE_OUT} ${QMAKE_FILE_IN}
jsoncollector.input = MOC_JSON_HEADERS
jsoncollector.output = $$OUT_PWD/allmocs.json
jsoncollector.variable_out = GENERATED_FILES
allmocs_contents = \
"<!DOCTYPE RCC><RCC version=\"1.0\">"\
"<qresource prefix=\"/\">"\
"<file>allmocs.json</file>"\
"<file>allmocs_baseline.json</file>"\
"</qresource>"\
"</RCC>"
allmocs_file = $$OUT_PWD/allmocs.qrc
!write_file($$allmocs_file, allmocs_contents): error()
RESOURCES += $$allmocs_file
QMAKE_EXTRA_COMPILERS += moc_json_header copy_baseline jsoncollector

View File

@ -33,6 +33,7 @@
#include <stdio.h>
#include <qobject.h>
#include <qmetaobject.h>
#include <qjsondocument.h>
#include "using-namespaces.h"
#include "assign-namespace.h"
@ -717,6 +718,7 @@ private slots:
void testQNamespace();
void cxx17Namespaces();
void cxxAttributes();
void mocJsonOutput();
signals:
void sigWithUnsignedArg(unsigned foo);
@ -3971,6 +3973,57 @@ void tst_Moc::cxxAttributes()
QCOMPARE(meta.keyCount(), 7);
}
void tst_Moc::mocJsonOutput()
{
const auto readFile = [](const QString &fileName) {
QFile f(fileName);
f.open(QIODevice::ReadOnly);
return QJsonDocument::fromJson(f.readAll());
};
const QString actualFile = QStringLiteral(":/allmocs.json");
const QString expectedFile = QStringLiteral(":/allmocs_baseline.json");
QVERIFY2(QFile::exists(actualFile), qPrintable(actualFile));
QVERIFY2(QFile::exists(expectedFile), qPrintable(expectedFile));
QJsonDocument actualOutput = readFile(QLatin1String(":/allmocs.json"));
QJsonDocument expectedOutput = readFile(QLatin1String(":/allmocs_baseline.json"));
const auto showPotentialDiff = [](const QJsonDocument &actual, const QJsonDocument &expected) -> QByteArray {
#if defined(Q_OS_UNIX)
QByteArray actualStr = actual.toJson();
QByteArray expectedStr = expected.toJson();
QTemporaryFile actualFile;
if (!actualFile.open())
return "Error opening actual temp file";
actualFile.write(actualStr);
actualFile.flush();
QTemporaryFile expectedFile;
if (!expectedFile.open())
return "Error opening expected temp file";
expectedFile.write(expectedStr);
expectedFile.flush();
QProcess diffProc;
diffProc.setProgram("diff");
diffProc.setArguments(QStringList() << "-ub" << expectedFile.fileName() << actualFile.fileName());
diffProc.start();
if (!diffProc.waitForStarted())
return "Error waiting for diff process to start.";
if (!diffProc.waitForFinished())
return "Error waiting for diff process to finish.";
return diffProc.readAllStandardOutput();
#else
return "Cannot launch diff. Please check allmocs.json and allmocs_baseline.json on disk.";
#endif
};
QVERIFY2(actualOutput == expectedOutput, showPotentialDiff(actualOutput, expectedOutput).constData());
}
QTEST_MAIN(tst_Moc)
// the generated code must compile with QT_NO_KEYWORDS