Skip to content

Commit

Permalink
Add support for machine-readable JSON output to the MOC
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: Ulf Hermann <[email protected]>
  • Loading branch information
tronical authored and Ulf Hermann committed Oct 4, 2019
1 parent 64473c9 commit da284ef
Show file tree
Hide file tree
Showing 11 changed files with 3,144 additions and 6 deletions.
34 changes: 34 additions & 0 deletions mkspecs/features/metatypes.prf
Original file line number Diff line number Diff line change
@@ -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
103 changes: 103 additions & 0 deletions src/tools/moc/collectjson.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
42 changes: 42 additions & 0 deletions src/tools/moc/collectjson.h
Original file line number Diff line number Diff line change
@@ -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
41 changes: 39 additions & 2 deletions src/tools/moc/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,20 @@
#include "preprocessor.h"
#include "moc.h"
#include "outputrevision.h"
#include "collectjson.h"

#include <qfile.h>
#include <qfileinfo.h>
#include <qdir.h>
#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

Expand Down Expand Up @@ -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()); }
Expand Down Expand Up @@ -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())
Expand All @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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)
Expand All @@ -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;
}
Expand All @@ -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())
Expand Down
Loading

0 comments on commit da284ef

Please sign in to comment.