// Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "moc.h" #include #include #include #include #include #include QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; extern QByteArrayList qax_qualified_usertypes; QByteArray setterName(const QByteArray &propertyName) { QByteArray setter(propertyName); if (isupper(setter.at(0))) { setter = "Set" + setter; } else { setter[0] = QtMiscUtils::toAsciiUpper(setter[0]); setter = "set" + setter; } return setter; } void formatCppEnum(QTextStream &str, const QMetaEnum &metaEnum) { str << " enum " << metaEnum.name() << " {" << Qt::endl; for (int k = 0, last = metaEnum.keyCount() - 1; k <= last; ++k) { QByteArray key(metaEnum.key(k)); str << " " << key.leftJustified(24) << "= " << metaEnum.value(k); if (k < last) str << ','; str << Qt::endl; } str << " };" << Qt::endl; } void formatCppEnums(QTextStream &str, const QMetaObject *mo, const char *qEnumDecl = nullptr /* Q_ENUM, Q_ENUM_NS */) { const int offset = mo->enumeratorOffset(); const int count = mo->enumeratorCount(); for (int e = offset; e < count; ++e) { const auto me = mo->enumerator(e); formatCppEnum(str, me); if (qEnumDecl) str << " " << qEnumDecl << '(' << me.name() << ")\n"; str << '\n'; } if (offset < count) str << '\n'; } // Create a set of type names that have been declared in the type library. Remove any "struct" or // "enum" prefixes. Also remove Qt-types that we hard-code in qaxbase.cpp. static QSet collectKnownTypes() { QSet result; for (const QByteArray &ba : qax_qualified_usertypes) { if (ba == "struct QRect" || ba == "struct QSize" || ba == "struct QPoint") continue; if (ba.startsWith("enum ")) result.insert(ba.right(ba.length() - 5)); else if (ba.startsWith("struct ")) result.insert(ba.right(ba.length() - 7)); else result.insert(ba); } return result; } static bool isKnownUserType(const QByteArray &name) { static qsizetype nrOfQualifiedUserTypes = 0; static QSet knownTypes; if (nrOfQualifiedUserTypes != qax_qualified_usertypes.size()) { nrOfQualifiedUserTypes = qax_qualified_usertypes.size(); knownTypes = collectKnownTypes(); } return knownTypes.contains(name); } static QByteArray cleanedType(QByteArray name) { if (name.endsWith("*")) name.chop(1); return name; } // If 'name' is a type that's declared within the TLB then fully qualify it with the TLB namespace. static QByteArray maybeQualifyType(QByteArray name, const QByteArray &tlbNamespace) { if (!name.contains("::") && isKnownUserType(cleanedType(name))) { name.prepend("::"); name.prepend(tlbNamespace); } return name; } static void formatCppMethod(QTextStream &str, const QMetaMethod &mt, const QByteArray &tlbNamespace) { str << " " << maybeQualifyType(mt.typeName(), tlbNamespace) << ' ' << mt.name() << '('; const auto paramTypes = mt.parameterTypes(); auto ptit = paramTypes.begin(); if (ptit != paramTypes.end()) { while (true) { str << maybeQualifyType(*ptit, tlbNamespace); if (++ptit == paramTypes.end()) break; str << ", "; } } str << ");\n"; } static void formatCppMethods(QTextStream &str, const QMetaObject *mo, const QByteArray &tlbNamespace, QMetaMethod::MethodType filter) { for (int m = mo->methodOffset(), count = mo->methodCount(); m < count; ++m) { const auto &mt = mo->method(m); if (mt.methodType() == filter) formatCppMethod(str, mt, tlbNamespace); } } static void formatCppProperty(QTextStream &str, const QMetaProperty &p, const QByteArray &tlbNamespace) { str << " Q_PROPERTY(" << maybeQualifyType(p.typeName(), tlbNamespace) << ' ' << p.name() << " READ " << p.name(); if (p.isWritable()) str << " WRITE " << setterName(p.name()); if (p.hasNotifySignal()) str << " NOTIFY " << p.notifySignal().name(); if (p.isUser()) str << " USER true"; if (!p.isDesignable()) str << " DESIGNABLE false"; if (!p.isStored()) str << " STORED false"; if (p.isFinal()) str << " FINAL"; str << ")\n"; } static void formatCppQuotedString(QTextStream &str, const char *s) { str << '"'; for ( ; *s ; ++s) { const char c = *s; if (c == '\\' || c == '\"') str << '\\'; str << c; } str << '"'; } static QByteArray extractNamespaceFromTypeName(const QStringList &name) { QByteArray result; if (name.size() < 2) return result; result = name.first().toUtf8(); for (int i = 1, count = name.size() - 1; i < count; ++i) { result.append("::"); result.append(name.at(i).toUtf8()); } return result; } // Generate C++ code from an ActiveQt QMetaObject to be parsed by moc static QString mocHeader(const QMetaObject *mo, const QStringList &name, const QString &baseClass) { QString result; QTextStream str(&result); str << "#pragma once\n\n"; if (!baseClass.isEmpty()) str << "#include <" << baseClass << ">\n"; str << "#include \n\n"; for (int n = 0, count = name.size() - 1; n < count; ++n) str << "namespace " << name.at(n) << " {\n"; str << "\nclass " << name.constLast(); if (!baseClass.isEmpty()) str << " : public " << baseClass; str<< "\n{\n Q_OBJECT\n"; for (int i = mo->classInfoOffset(), count = mo->classInfoCount(); i < count; ++i) { const auto &info = mo->classInfo(i); str << " Q_CLASSINFO("; formatCppQuotedString(str, info.name()); str << ", "; formatCppQuotedString(str, info.value()); str << ")\n"; } const QByteArray tlbNamespace = extractNamespaceFromTypeName(name); for (int p = mo->propertyOffset(), count = mo-> propertyCount(); p < count; ++p) formatCppProperty(str, mo->property(p), tlbNamespace); str << "public:\n"; formatCppEnums(str, mo, "Q_ENUM"); formatCppMethods(str, mo, tlbNamespace, QMetaMethod::Constructor); str << "\nQ_SIGNALS:\n"; formatCppMethods(str, mo, tlbNamespace, QMetaMethod::Signal); str << "\npublic Q_SLOTS:\n"; formatCppMethods(str, mo, tlbNamespace, QMetaMethod::Slot); str << "};\n"; // The final part of "name" is the class name. It shouldn't be closed as a namespace for (int n = name.size() - 2; n >= 0; --n) str << "} // namespace " << name.at(n) << '\n'; return result; } static QString processOutput(QByteArray output) { for (int c = output.size() - 1; c >= 0; --c) { if (output.at(c) == '\r') output.remove(c, 1); } return QString::fromUtf8(output); } static QString runProcess(const QString &binary, const QStringList &args, const QByteArray &input, QString *errorString) { QProcess process; process.start(binary, args); if (!process.waitForStarted()) { *errorString = QLatin1String("Cannot start ") + binary + QLatin1String(": ") + process.errorString(); return QString(); } process.write(input); process.closeWriteChannel(); if (!process.waitForFinished()) { *errorString = binary + QLatin1String(" timed out: ") + process.errorString(); return QString(); } if (process.exitStatus() != QProcess::NormalExit) { *errorString = binary + QLatin1String(" crashed: ") + process.errorString(); return QString(); } if (process.exitCode() != 0) { *errorString = binary + QLatin1String(" failed: ") + processOutput(process.readAllStandardError()); return QString(); } return processOutput(process.readAllStandardOutput()); } QString mocCode(const QMetaObject *mo, const QString &qualifiedClassName, QString *errorString) { QStringList name = qualifiedClassName.split(QLatin1String("::")); if (name.isEmpty()) name.append(QLatin1String(mo->className())); const QString baseClass = QLatin1String(mo->superClass()->className()); const QString headerCode = mocHeader(mo, name, baseClass); const QString binary = QLatin1String("moc.exe"); QString result = runProcess(binary, { u"--active-qt"_s }, headerCode.toUtf8(), errorString); if (result.isEmpty()) { errorString->append(QLatin1String("\n\nOffending code:\n")); errorString->append(headerCode); } return result; } QT_END_NAMESPACE