From 89ef515177fd5a0b5d95dcffd5fd0b0669e3625a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= Date: Thu, 5 Sep 2013 07:55:49 +0200 Subject: Add JSON parsing support to qmake. Add qjson* implementation files from corelib/json to the qmake build. Add a read-only compile mode, enabled by defining QT_JSON_READONLY. Add qmake built-in function parseJson(file, into) which parses a json file into the given variable. qmake uses a flat key -> value-list implementation for storing variables, which means that some hackery is need to represent arbitrarily nested JSON. Use a special "_KEYS_" variable for arrays and objects: Arrays: ["item1", "item2"] $${array._KEYS_} -> 0 1 2 $${array.0} -> "item1" $${array.1} -> "item2" Objects: { "key1" : "value1", "key2" : "value2" } $${object._KEYS_} -> key1 key2 $${object.key1} -> value1 $${object.key2} -> value2 Change-Id: I0aa2e4e4ae14fa25be8242bc16d3cffce32504d2 Reviewed-by: Lars Knoll --- qmake/Makefile.unix | 27 +++++++- qmake/Makefile.win32 | 13 +++- qmake/library/qmakebuiltins.cpp | 91 +++++++++++++++++++++++++- qmake/qmake.pri | 17 ++++- qmake/qmake.pro | 1 + src/corelib/json/qjsonarray.cpp | 2 +- src/corelib/json/qjsonarray.h | 2 +- src/corelib/json/qjsondocument.cpp | 6 +- src/corelib/json/qjsondocument.h | 4 +- src/corelib/json/qjsonobject.cpp | 2 +- src/corelib/json/qjsonobject.h | 2 +- src/corelib/json/qjsonvalue.cpp | 2 +- src/corelib/json/qjsonvalue.h | 2 +- tests/auto/tools/qmake/testdata/json/json.pro | 26 ++++++++ tests/auto/tools/qmake/testdata/json/test.json | 9 +++ tests/auto/tools/qmake/tst_qmake.cpp | 29 ++++++++ 16 files changed, 220 insertions(+), 15 deletions(-) create mode 100644 tests/auto/tools/qmake/testdata/json/json.pro create mode 100644 tests/auto/tools/qmake/testdata/json/test.json diff --git a/qmake/Makefile.unix b/qmake/Makefile.unix index a77b1b22dc..2f04a88a42 100644 --- a/qmake/Makefile.unix +++ b/qmake/Makefile.unix @@ -19,6 +19,7 @@ QOBJS=qtextcodec.o qutfcodec.o qstring.o qstringbuilder.o qtextstream.o qiodevic qabstractfileengine.o qtemporaryfile.o qmap.o qmetatype.o qsettings.o qsystemerror.o qlibraryinfo.o \ qvariant.o qvsnprintf.o qlocale.o qlocale_tools.o qlinkedlist.o qnumeric.o \ qcryptographichash.o qxmlstream.o qxmlutils.o qlogging.o \ + qjson.o qjsondocument.o qjsonparser.o qjsonarray.o qjsonobject.o qjsonvalue.o \ $(QTOBJS) @@ -80,6 +81,12 @@ DEPEND_SRC = \ $(SOURCE_PATH)/src/corelib/global/qlogging.cpp \ $(SOURCE_PATH)/src/corelib/plugin/qsystemlibrary.cpp \ $(SOURCE_PATH)/tools/shared/windows/registry.cpp \ + $(SOURCE_PATH)/src/corelib/json/qjson.cpp \ + $(SOURCE_PATH)/src/corelib/json/qjsondocument.cpp \ + $(SOURCE_PATH)/src/corelib/json/qjsonparser.cpp \ + $(SOURCE_PATH)/src/corelib/json/qjsonarray.cpp \ + $(SOURCE_PATH)/src/corelib/json/qjsonobject.cpp \ + $(SOURCE_PATH)/src/corelib/json/qjsonvalue.cpp \ $(QTSRCS) CPPFLAGS = -g $(EXTRA_CPPFLAGS) \ @@ -93,7 +100,7 @@ CPPFLAGS = -g $(EXTRA_CPPFLAGS) \ -DQT_BUILD_QMAKE -DQT_BOOTSTRAPPED -DPROEVALUATOR_FULL \ -DQT_NO_TEXTCODEC -DQT_NO_UNICODETABLES -DQT_NO_COMPONENT -DQT_NO_COMPRESS \ -DQT_NO_THREAD -DQT_NO_QOBJECT -DQT_NO_GEOM_VARIANT -DQT_NO_DATASTREAM \ - -DQT_CRYPTOGRAPHICHASH_ONLY_SHA1 + -DQT_CRYPTOGRAPHICHASH_ONLY_SHA1 -DQT_JSON_READONLY CXXFLAGS = $(EXTRA_CXXFLAGS) $(CPPFLAGS) @@ -385,4 +392,22 @@ qsystemlibrary.o: $(SOURCE_PATH)/src/corelib/plugin/qsystemlibrary.cpp registry.o: $(SOURCE_PATH)/tools/shared/windows/registry.cpp $(CXX) -c -o $@ $(CXXFLAGS) $(SOURCE_PATH)/tools/shared/windows/registry.cpp +qjson.o: $(SOURCE_PATH)/src/corelib/json/qjson.cpp + $(CXX) -c -o $@ $(CXXFLAGS) $(SOURCE_PATH)/src/corelib/json/qjson.cpp + +qjsondocument.o: $(SOURCE_PATH)/src/corelib/json/qjsondocument.cpp + $(CXX) -c -o $@ $(CXXFLAGS) $(SOURCE_PATH)/src/corelib/json/qjsondocument.cpp + +qjsonparser.o: $(SOURCE_PATH)/src/corelib/json/qjsonparser.cpp + $(CXX) -c -o $@ $(CXXFLAGS) $(SOURCE_PATH)/src/corelib/json/qjsonparser.cpp + +qjsonarray.o: $(SOURCE_PATH)/src/corelib/json/qjsonarray.cpp + $(CXX) -c -o $@ $(CXXFLAGS) $(SOURCE_PATH)/src/corelib/json/qjsonarray.cpp + +qjsonobject.o: $(SOURCE_PATH)/src/corelib/json/qjsonobject.cpp + $(CXX) -c -o $@ $(CXXFLAGS) $(SOURCE_PATH)/src/corelib/json/qjsonobject.cpp + +qjsonvalue.o: $(SOURCE_PATH)/src/corelib/json/qjsonvalue.cpp + $(CXX) -c -o $@ $(CXXFLAGS) $(SOURCE_PATH)/src/corelib/json/qjsonvalue.cpp + # DO NOT DELETE THIS LINE -- make depend depends on it diff --git a/qmake/Makefile.win32 b/qmake/Makefile.win32 index 9a772d9760..c0d20111e9 100644 --- a/qmake/Makefile.win32 +++ b/qmake/Makefile.win32 @@ -41,7 +41,7 @@ CFLAGS_BARE = -c -Fo./ \ -DQT_BUILD_QMAKE -DQT_BOOTSTRAPPED -DPROEVALUATOR_FULL \ -DQT_NO_TEXTCODEC -DQT_NO_UNICODETABLES -DQT_NO_COMPONENT -DQT_NO_COMPRESS \ -DQT_NO_THREAD -DQT_NO_QOBJECT -DQT_NO_GEOM_VARIANT -DQT_NO_DATASTREAM \ - -DUNICODE -DQT_CRYPTOGRAPHICHASH_ONLY_SHA1 + -DUNICODE -DQT_CRYPTOGRAPHICHASH_ONLY_SHA1 -DQT_JSON_READONLY CFLAGS = -Yuqmake_pch.h -FIqmake_pch.h -Fpqmake_pch.pch $(CFLAGS_BARE) $(CFLAGS) $(EXTRA_CPPFLAGS) CXXFLAGS_BARE = $(CFLAGS_BARE) @@ -120,7 +120,13 @@ QTOBJS= \ qxmlstream.obj \ qxmlutils.obj \ qnumeric.obj \ - qlogging.obj + qlogging.obj \ + qjson.obj \ + qjsondocument.obj \ + qjsonparser.obj \ + qjsonarray.obj \ + qjsonobject.obj \ + qjsonvalue.obj first all: qmake.exe @@ -207,5 +213,8 @@ qmake_pch.obj: {$(SOURCE_PATH)\src\corelib\xml}.cpp{}.obj:: $(CXX) $(CXXFLAGS) $< +{$(SOURCE_PATH)\src\corelib\json}.cpp{}.obj:: + $(CXX) $(CXXFLAGS) $< + {$(SOURCE_PATH)\tools\shared\windows}.cpp{}.obj:: $(CXX) $(CXXFLAGS) $< diff --git a/qmake/library/qmakebuiltins.cpp b/qmake/library/qmakebuiltins.cpp index 10ef523e15..9cefbc8572 100644 --- a/qmake/library/qmakebuiltins.cpp +++ b/qmake/library/qmakebuiltins.cpp @@ -56,6 +56,11 @@ #include #include #include +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) +# include +# include +# include +#endif #ifdef PROEVALUATOR_THREAD_SAFE # include #endif @@ -101,7 +106,7 @@ enum TestFunc { T_INVALID = 0, T_REQUIRES, T_GREATERTHAN, T_LESSTHAN, T_EQUALS, T_EXISTS, T_EXPORT, T_CLEAR, T_UNSET, T_EVAL, T_CONFIG, T_SYSTEM, T_DEFINED, T_CONTAINS, T_INFILE, - T_COUNT, T_ISEMPTY, T_INCLUDE, T_LOAD, T_DEBUG, T_LOG, T_MESSAGE, T_WARNING, T_ERROR, T_IF, + T_COUNT, T_ISEMPTY, T_PARSE_JSON, T_INCLUDE, T_LOAD, T_DEBUG, T_LOG, T_MESSAGE, T_WARNING, T_ERROR, T_IF, T_MKPATH, T_WRITE_FILE, T_TOUCH, T_CACHE }; @@ -178,6 +183,9 @@ void QMakeEvaluator::initFunctionStatics() { "infile", T_INFILE }, { "count", T_COUNT }, { "isEmpty", T_ISEMPTY }, +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + { "parseJson", T_PARSE_JSON }, +#endif { "load", T_LOAD }, { "include", T_INCLUDE }, { "debug", T_DEBUG }, @@ -286,6 +294,75 @@ quoteValue(const ProString &val) return ret; } +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) +static void addJsonValue(const QJsonValue &value, const QString &keyPrefix, ProValueMap *map); + +static void insertJsonKeyValue(const QString &key, const QStringList &values, ProValueMap *map) +{ + map->insert(ProKey(key), ProStringList(values)); +} + +static void addJsonArray(const QJsonArray &array, const QString &keyPrefix, ProValueMap *map) +{ + QStringList keys; + for (int i = 0; i < array.count(); ++i) { + keys.append(QString::number(i)); + addJsonValue(array.at(i), keyPrefix + QString::number(i), map); + } + insertJsonKeyValue(keyPrefix + QLatin1String("_KEYS_"), keys, map); +} + +static void addJsonObject(const QJsonObject &object, const QString &keyPrefix, ProValueMap *map) +{ + foreach (const QString &key, object.keys()) + addJsonValue(object.value(key), keyPrefix + key, map); + + insertJsonKeyValue(keyPrefix + QLatin1String("_KEYS_"), object.keys(), map); +} + +static void addJsonValue(const QJsonValue &value, const QString &keyPrefix, ProValueMap *map) +{ + switch (value.type()) { + case QJsonValue::Bool: + insertJsonKeyValue(keyPrefix, QStringList() << (value.toBool() ? QLatin1String("true") : QLatin1String("false")), map); + break; + case QJsonValue::Double: + insertJsonKeyValue(keyPrefix, QStringList() << QString::number(value.toDouble()), map); + break; + case QJsonValue::String: + insertJsonKeyValue(keyPrefix, QStringList() << value.toString(), map); + break; + case QJsonValue::Array: + addJsonArray(value.toArray(), keyPrefix + QLatin1Char('.'), map); + break; + case QJsonValue::Object: + addJsonObject(value.toObject(), keyPrefix + QLatin1Char('.'), map); + break; + default: + break; + } +} + +static QMakeEvaluator::VisitReturn parseJsonInto(const QByteArray &json, const QString &into, ProValueMap *value) +{ + QJsonDocument document = QJsonDocument::fromJson(json); + if (document.isNull()) + return QMakeEvaluator::ReturnFalse; + + QString currentKey = into + QLatin1Char('.'); + + // top-level item is either an array or object + if (document.isArray()) + addJsonArray(document.array(), currentKey, value); + else if (document.isObject()) + addJsonObject(document.object(), currentKey, value); + else + return QMakeEvaluator::ReturnFalse; + + return QMakeEvaluator::ReturnTrue; +} +#endif + QMakeEvaluator::VisitReturn QMakeEvaluator::writeFile(const QString &ctx, const QString &fn, QIODevice::OpenMode mode, const QString &contents) @@ -1278,6 +1355,18 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBuiltinConditional( m_valuemapStack.top()[var] = statics.fakeValue; return ReturnTrue; } +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + case T_PARSE_JSON: { + if (args.count() != 2) { + evalError(fL1S("parseJson(variable, into) requires two arguments.")); + return ReturnFalse; + } + + QByteArray json = values(args.at(0).toKey()).join(QLatin1Char(' ')).toUtf8(); + QString parseInto = args.at(1).toQString(m_tmp2); + return parseJsonInto(json, parseInto, &m_valuemapStack.top()); + } +#endif case T_INCLUDE: { if (args.count() < 1 || args.count() > 3) { evalError(fL1S("include(file, [into, [silent]]) requires one, two or three arguments.")); diff --git a/qmake/qmake.pri b/qmake/qmake.pri index 39959efe7b..5012bd0206 100644 --- a/qmake/qmake.pri +++ b/qmake/qmake.pri @@ -83,7 +83,13 @@ bootstrap { #Qt code qvsnprintf.cpp \ qxmlstream.cpp \ qxmlutils.cpp \ - qlogging.cpp + qlogging.cpp \ + qjson.cpp \ + qjsondocument.cpp \ + qjsonparser.cpp \ + qjsonarray.cpp \ + qjsonobject.cpp \ + qjsonvalue.cpp HEADERS+= \ qbitarray.h \ @@ -126,7 +132,14 @@ bootstrap { #Qt code quuid.h \ qvector.h \ qxmlstream.h \ - qxmlutils.h + qxmlutils.h \ + qjson.h \ + qjsondocument.h \ + qjsonparser.h \ + qjsonwriter.h \ + qjsonarray.h \ + qjsonobject.h \ + qjsonvalue.h unix { SOURCES += qfilesystemengine_unix.cpp qfilesystemiterator_unix.cpp qfsfileengine_unix.cpp diff --git a/qmake/qmake.pro b/qmake/qmake.pro index 568ad41ce1..e680942716 100644 --- a/qmake/qmake.pro +++ b/qmake/qmake.pro @@ -20,6 +20,7 @@ VPATH += $$QT_SOURCE_TREE/src/corelib/global \ $$QT_SOURCE_TREE/src/corelib/plugin \ $$QT_SOURCE_TREE/src/corelib/xml \ $$QT_SOURCE_TREE/src/corelib/io \ + $$QT_SOURCE_TREE/src/corelib/json \ $$QT_SOURCE_TREE/tools/shared/windows INCLUDEPATH += . \ diff --git a/src/corelib/json/qjsonarray.cpp b/src/corelib/json/qjsonarray.cpp index 8dd7f6092f..d81de89628 100644 --- a/src/corelib/json/qjsonarray.cpp +++ b/src/corelib/json/qjsonarray.cpp @@ -1089,7 +1089,7 @@ void QJsonArray::compact() } -#ifndef QT_NO_DEBUG_STREAM +#if !defined(QT_NO_DEBUG_STREAM) && !defined(QT_JSON_READONLY) QDebug operator<<(QDebug dbg, const QJsonArray &a) { if (!a.a) { diff --git a/src/corelib/json/qjsonarray.h b/src/corelib/json/qjsonarray.h index 1474ccae41..562e6accd7 100644 --- a/src/corelib/json/qjsonarray.h +++ b/src/corelib/json/qjsonarray.h @@ -211,7 +211,7 @@ private: QJsonPrivate::Array *a; }; -#ifndef QT_NO_DEBUG_STREAM +#if !defined(QT_NO_DEBUG_STREAM) && !defined(QT_JSON_READONLY) Q_CORE_EXPORT QDebug operator<<(QDebug, const QJsonArray &); #endif diff --git a/src/corelib/json/qjsondocument.cpp b/src/corelib/json/qjsondocument.cpp index 4806ac68d6..6e257df39d 100644 --- a/src/corelib/json/qjsondocument.cpp +++ b/src/corelib/json/qjsondocument.cpp @@ -303,10 +303,12 @@ QVariant QJsonDocument::toVariant() const \sa fromJson() */ +#ifndef QT_JSON_READONLY QByteArray QJsonDocument::toJson() const { return toJson(Indented); } +#endif /*! \enum QJsonDocument::JsonFormat @@ -338,6 +340,7 @@ QByteArray QJsonDocument::toJson() const \sa fromJson(), JsonFormat */ +#ifndef QT_JSON_READONLY QByteArray QJsonDocument::toJson(JsonFormat format) const { if (!d) @@ -352,6 +355,7 @@ QByteArray QJsonDocument::toJson(JsonFormat format) const return json; } +#endif /*! Parses a UTF-8 encoded JSON document and creates a QJsonDocument @@ -562,7 +566,7 @@ bool QJsonDocument::isNull() const return (d == 0); } -#ifndef QT_NO_DEBUG_STREAM +#if !defined(QT_NO_DEBUG_STREAM) && !defined(QT_JSON_READONLY) QDebug operator<<(QDebug dbg, const QJsonDocument &o) { if (!o.d) { diff --git a/src/corelib/json/qjsondocument.h b/src/corelib/json/qjsondocument.h index 0354262e2c..ea42d76b20 100644 --- a/src/corelib/json/qjsondocument.h +++ b/src/corelib/json/qjsondocument.h @@ -117,7 +117,7 @@ public: #ifdef Q_QDOC QByteArray toJson(JsonFormat format = Indented) const; -#else +#elif !defined(QT_JSON_READONLY) QByteArray toJson() const; //### Merge in Qt6 QByteArray toJson(JsonFormat format) const; #endif @@ -148,7 +148,7 @@ private: QJsonPrivate::Data *d; }; -#ifndef QT_NO_DEBUG_STREAM +#if !defined(QT_NO_DEBUG_STREAM) && !defined(QT_JSON_READONLY) Q_CORE_EXPORT QDebug operator<<(QDebug, const QJsonDocument &); #endif diff --git a/src/corelib/json/qjsonobject.cpp b/src/corelib/json/qjsonobject.cpp index 362d01384e..afc0d5f71f 100644 --- a/src/corelib/json/qjsonobject.cpp +++ b/src/corelib/json/qjsonobject.cpp @@ -1036,7 +1036,7 @@ void QJsonObject::setValueAt(int i, const QJsonValue &val) insert(e->key(), val); } -#ifndef QT_NO_DEBUG_STREAM +#if !defined(QT_NO_DEBUG_STREAM) && !defined(QT_JSON_READONLY) QDebug operator<<(QDebug dbg, const QJsonObject &o) { if (!o.o) { diff --git a/src/corelib/json/qjsonobject.h b/src/corelib/json/qjsonobject.h index 8226b614b4..ad3184b1f2 100644 --- a/src/corelib/json/qjsonobject.h +++ b/src/corelib/json/qjsonobject.h @@ -206,7 +206,7 @@ private: QJsonPrivate::Object *o; }; -#ifndef QT_NO_DEBUG_STREAM +#if !defined(QT_NO_DEBUG_STREAM) && !defined(QT_JSON_READONLY) Q_CORE_EXPORT QDebug operator<<(QDebug, const QJsonObject &); #endif diff --git a/src/corelib/json/qjsonvalue.cpp b/src/corelib/json/qjsonvalue.cpp index 8aa1f654c6..0a603b958a 100644 --- a/src/corelib/json/qjsonvalue.cpp +++ b/src/corelib/json/qjsonvalue.cpp @@ -661,7 +661,7 @@ QJsonValue QJsonValueRef::toValue() const return o->valueAt(index); } -#ifndef QT_NO_DEBUG_STREAM +#if !defined(QT_NO_DEBUG_STREAM) && !defined(QT_JSON_READONLY) QDebug operator<<(QDebug dbg, const QJsonValue &o) { switch (o.t) { diff --git a/src/corelib/json/qjsonvalue.h b/src/corelib/json/qjsonvalue.h index b18bbde0f7..c0ecdd2b61 100644 --- a/src/corelib/json/qjsonvalue.h +++ b/src/corelib/json/qjsonvalue.h @@ -179,7 +179,7 @@ private: uint index : 31; }; -#ifndef QT_NO_DEBUG_STREAM +#if !defined(QT_NO_DEBUG_STREAM) && !defined(QT_JSON_READONLY) Q_CORE_EXPORT QDebug operator<<(QDebug, const QJsonValue &); #endif diff --git a/tests/auto/tools/qmake/testdata/json/json.pro b/tests/auto/tools/qmake/testdata/json/json.pro new file mode 100644 index 0000000000..33440b3209 --- /dev/null +++ b/tests/auto/tools/qmake/testdata/json/json.pro @@ -0,0 +1,26 @@ +jsontext = $$cat($$PWD/test.json) +parseJson(jsontext, json) + +# print all keys +message(json._KEYS_ $${json._KEYS_}) + +# print array +message(json.array._KEYS_ $${json.array._KEYS_}) +for(key, json.array._KEYS_): \ + message(json.array.$${key} $$eval(json.array.$${key})) + +# print object +message(json.object._KEYS_ $${json.object._KEYS_}) +for(key, json.object._KEYS_): \ + message(json.object.$${key} $$eval(json.object.$${key})) + +# print value tyes +message(json.string: $${json.string}) +message(json.number: $${json.number}) +message(json.true: $${json.true}) +message(json.false: $${json.false}) +message(json.null: $${json.null}) + +# check that booleans work +$${json.true}: message(json.true is true) +!$${json.false}: message(json.false is false) diff --git a/tests/auto/tools/qmake/testdata/json/test.json b/tests/auto/tools/qmake/testdata/json/test.json new file mode 100644 index 0000000000..cc82908eba --- /dev/null +++ b/tests/auto/tools/qmake/testdata/json/test.json @@ -0,0 +1,9 @@ +{ + "array" : ["arrayItem1", "arrayItem2", "arrayItem3"], + "object" : { "key1" : "objectValue1", "key2" : "objectValue2" }, + "string" : "test string", + "number" : 999, + "true" : true, + "false" :false, + "null" : null +} diff --git a/tests/auto/tools/qmake/tst_qmake.cpp b/tests/auto/tools/qmake/tst_qmake.cpp index cf5c75a66b..87e86406b8 100644 --- a/tests/auto/tools/qmake/tst_qmake.cpp +++ b/tests/auto/tools/qmake/tst_qmake.cpp @@ -92,6 +92,7 @@ private slots: void substitutes(); void project(); void proFileCache(); + void json(); private: TestCompiler test_compiler; @@ -556,5 +557,33 @@ void tst_qmake::proFileCache() QVERIFY( test_compiler.qmake( workDir, "pro_file_cache" )); } +void tst_qmake::json() +{ + QString workDir = base_path + "/testdata/json"; + QVERIFY( test_compiler.qmake( workDir, "json.pro" )); + QString output = test_compiler.commandOutput(); + + // all keys + QVERIFY(output.contains("json._KEYS_ array false null number object string true")); + // array + QVERIFY(output.contains("json.array._KEYS_ 0 1 2")); + QVERIFY(output.contains("json.array.0 arrayItem1")); + QVERIFY(output.contains("json.array.1 arrayItem2")); + QVERIFY(output.contains("json.array.2 arrayItem3")); + // object + QVERIFY(output.contains("json.object._KEYS_ key1 key2")); + QVERIFY(output.contains("json.object.key1 objectValue1")); + QVERIFY(output.contains("json.object.key1 objectValue1")); + // value types + QVERIFY(output.contains("json.string: test string")); + QVERIFY(output.contains("json.number: 999")); + QVERIFY(output.contains("json.true: true")); + QVERIFY(output.contains("json.false: false")); + QVERIFY(output.contains("json.null:")); + // functional booleans + QVERIFY(output.contains("json.true is true")); + QVERIFY(output.contains("json.false is false")); +} + QTEST_MAIN(tst_qmake) #include "tst_qmake.moc" -- cgit v1.2.3