diff options
Diffstat (limited to 'tools/qmltc/main.cpp')
-rw-r--r-- | tools/qmltc/main.cpp | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/tools/qmltc/main.cpp b/tools/qmltc/main.cpp new file mode 100644 index 0000000000..1899f4087e --- /dev/null +++ b/tools/qmltc/main.cpp @@ -0,0 +1,313 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qmltccommandlineutils.h" +#include "qmltcvisitor.h" +#include "qmltctyperesolver.h" + +#include "qmltccompiler.h" + +#include <private/qqmljscompiler_p.h> +#include <private/qqmljsresourcefilemapper_p.h> +#include <private/qqmljsutils_p.h> + +#include <QtCore/qcoreapplication.h> +#include <QtCore/qurl.h> +#include <QtCore/qhashfunctions.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qlibraryinfo.h> +#include <QtCore/qcommandlineparser.h> +#include <QtCore/qregularexpression.h> + +#include <QtQml/private/qqmljslexer_p.h> +#include <QtQml/private/qqmljsparser_p.h> +#include <QtQml/private/qqmljsengine_p.h> +#include <QtQml/private/qqmljsastvisitor_p.h> +#include <QtQml/private/qqmljsast_p.h> +#include <QtQml/private/qqmljsdiagnosticmessage_p.h> +#include <QtQmlCompiler/qqmlsa.h> +#include <QtQmlCompiler/private/qqmljsliteralbindingcheck_p.h> + +#include <cstdlib> // EXIT_SUCCESS, EXIT_FAILURE + +using namespace Qt::StringLiterals; + +void setupLogger(QQmlJSLogger &logger) // prepare logger to work with compiler +{ + for (const QQmlJS::LoggerCategory &category : logger.categories()) { + if (category.id() == qmlUnusedImports) + continue; + logger.setCategoryLevel(category.id(), QtCriticalMsg); + logger.setCategoryIgnored(category.id(), false); + } +} + +int main(int argc, char **argv) +{ + // Produce reliably the same output for the same input by disabling QHash's + // random seeding. + QHashSeed::setDeterministicGlobalSeed(); + QCoreApplication app(argc, argv); + QCoreApplication::setApplicationName(u"qmltc"_s); + QCoreApplication::setApplicationVersion(QStringLiteral(QT_VERSION_STR)); + + // command-line parsing: + QCommandLineParser parser; + parser.addHelpOption(); + parser.addVersionOption(); + + QCommandLineOption bareOption { + u"bare"_s, + QCoreApplication::translate( + "main", "Do not include default import directories. This may be used to run " + "qmltc on a project using a different Qt version.") + }; + parser.addOption(bareOption); + + QCommandLineOption importPathOption { + u"I"_s, QCoreApplication::translate("main", "Look for QML modules in specified directory"), + QCoreApplication::translate("main", "import directory") + }; + parser.addOption(importPathOption); + QCommandLineOption qmldirOption { + u"i"_s, QCoreApplication::translate("main", "Include extra qmldir files"), + QCoreApplication::translate("main", "qmldir file") + }; + parser.addOption(qmldirOption); + QCommandLineOption outputCppOption { + u"impl"_s, QCoreApplication::translate("main", "Generated C++ source file path"), + QCoreApplication::translate("main", "cpp path") + }; + parser.addOption(outputCppOption); + QCommandLineOption outputHOption { + u"header"_s, QCoreApplication::translate("main", "Generated C++ header file path"), + QCoreApplication::translate("main", "h path") + }; + parser.addOption(outputHOption); + QCommandLineOption resourceOption { + u"resource"_s, + QCoreApplication::translate( + "main", "Qt resource file that might later contain one of the compiled files"), + QCoreApplication::translate("main", "resource file name") + }; + parser.addOption(resourceOption); + QCommandLineOption metaResourceOption { + u"meta-resource"_s, + QCoreApplication::translate("main", "Qt meta information file (in .qrc format)"), + QCoreApplication::translate("main", "meta file name") + }; + parser.addOption(metaResourceOption); + QCommandLineOption namespaceOption { + u"namespace"_s, QCoreApplication::translate("main", "Namespace of the generated C++ code"), + QCoreApplication::translate("main", "namespace") + }; + parser.addOption(namespaceOption); + QCommandLineOption moduleOption{ + u"module"_s, + QCoreApplication::translate("main", + "Name of the QML module that this QML code belongs to."), + QCoreApplication::translate("main", "module") + }; + parser.addOption(moduleOption); + QCommandLineOption exportOption{ u"export"_s, + QCoreApplication::translate( + "main", "Export macro used in the generated C++ code"), + QCoreApplication::translate("main", "export") }; + parser.addOption(exportOption); + QCommandLineOption exportIncludeOption{ + u"exportInclude"_s, + QCoreApplication::translate( + "main", "Header defining the export macro to be used in the generated C++ code"), + QCoreApplication::translate("main", "exportInclude") + }; + parser.addOption(exportIncludeOption); + + parser.process(app); + + const QStringList sources = parser.positionalArguments(); + if (sources.size() != 1) { + if (sources.isEmpty()) { + parser.showHelp(); + } else { + fprintf(stderr, "%s\n", + qPrintable(u"Too many input files specified: '"_s + sources.join(u"' '"_s) + + u'\'')); + } + return EXIT_FAILURE; + } + const QString inputFile = sources.first(); + + QString url = parseUrlArgument(inputFile); + if (url.isNull()) + return EXIT_FAILURE; + if (!url.endsWith(u".qml")) { + fprintf(stderr, "Non-QML file passed as input\n"); + return EXIT_FAILURE; + } + + static QRegularExpression nameChecker(u"^[a-zA-Z_][a-zA-Z0-9_]*\\.qml$"_s); + if (auto match = nameChecker.match(QUrl(url).fileName()); !match.hasMatch()) { + fprintf(stderr, + "The given QML filename is unsuited for type compilation: the name must consist of " + "letters, digits and underscores, starting with " + "a letter or an underscore and ending in '.qml'!\n"); + return EXIT_FAILURE; + } + + QString sourceCode = loadUrl(url); + if (sourceCode.isEmpty()) + return EXIT_FAILURE; + + QString implicitImportDirectory = getImplicitImportDirectory(url); + if (implicitImportDirectory.isEmpty()) + return EXIT_FAILURE; + + QStringList importPaths; + + if (parser.isSet(resourceOption)) { + importPaths.append(QLatin1String(":/qt-project.org/imports")); + importPaths.append(QLatin1String(":/qt/qml")); + }; + + if (parser.isSet(importPathOption)) + importPaths.append(parser.values(importPathOption)); + + if (!parser.isSet(bareOption)) + importPaths.append(QLibraryInfo::path(QLibraryInfo::QmlImportsPath)); + + QStringList qmldirFiles = QQmlJSUtils::cleanPaths(parser.values(qmldirOption)); + + QString outputCppFile; + if (!parser.isSet(outputCppOption)) { + outputCppFile = url.first(url.size() - 3) + u"cpp"_s; + } else { + outputCppFile = parser.value(outputCppOption); + } + + QString outputHFile; + if (!parser.isSet(outputHOption)) { + outputHFile = url.first(url.size() - 3) + u"h"_s; + } else { + outputHFile = parser.value(outputHOption); + } + + if (!parser.isSet(resourceOption)) { + fprintf(stderr, "No resource paths for file: %s\n", qPrintable(inputFile)); + return EXIT_FAILURE; + } + + // main logic: + QQmlJS::Engine engine; + QQmlJS::Lexer lexer(&engine); + lexer.setCode(sourceCode, /*lineno = */ 1); + QQmlJS::Parser qmlParser(&engine); + if (!qmlParser.parse()) { + const auto diagnosticMessages = qmlParser.diagnosticMessages(); + for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) { + fprintf(stderr, "%s\n", + qPrintable(QStringLiteral("%1:%2:%3: %4") + .arg(inputFile) + .arg(m.loc.startLine) + .arg(m.loc.startColumn) + .arg(m.message))); + } + return EXIT_FAILURE; + } + + const QStringList resourceFiles = parser.values(resourceOption); + QQmlJSResourceFileMapper mapper(resourceFiles); + const QStringList metaResourceFiles = parser.values(metaResourceOption); + QQmlJSResourceFileMapper metaDataMapper(metaResourceFiles); + + const auto firstQml = [](const QStringList &paths) { + auto it = std::find_if(paths.cbegin(), paths.cend(), + [](const QString &x) { return x.endsWith(u".qml"_s); }); + if (it == paths.cend()) + return QString(); + return *it; + }; + // verify that we can map current file to qrc (then use the qrc path later) + const QStringList paths = mapper.resourcePaths(QQmlJSResourceFileMapper::localFileFilter(url)); + if (paths.isEmpty()) { + fprintf(stderr, "Failed to find a resource path for file: %s\n", qPrintable(inputFile)); + return EXIT_FAILURE; + } else if (paths.size() > 1) { + bool good = !firstQml(paths).isEmpty(); + good &= std::any_of(paths.cbegin(), paths.cend(), + [](const QString &x) { return x.endsWith(u".h"_s); }); + if (!good || paths.size() > 2) { + fprintf(stderr, "Unexpected resource paths for file: %s\n", qPrintable(inputFile)); + return EXIT_FAILURE; + } + } + + QmltcCompilerInfo info; + info.outputCppFile = parser.value(outputCppOption); + info.outputHFile = parser.value(outputHOption); + info.resourcePath = firstQml(paths); + info.outputNamespace = parser.value(namespaceOption); + info.exportMacro = parser.value(exportOption); + info.exportInclude = parser.value(exportIncludeOption); + + if (info.outputCppFile.isEmpty()) { + fprintf(stderr, "An output C++ file is required. Pass one using --impl"); + return EXIT_FAILURE; + } + if (info.outputHFile.isEmpty()) { + fprintf(stderr, "An output C++ header file is required. Pass one using --header"); + return EXIT_FAILURE; + } + + QQmlJSImporter importer { importPaths, &mapper }; + importer.setMetaDataMapper(&metaDataMapper); + auto qmltcVisitor = [](QQmlJS::AST::Node *rootNode, QQmlJSImporter *self, + const QQmlJSImporter::ImportVisitorPrerequisites &p) { + QmltcVisitor v(p.m_target, self, p.m_logger, p.m_implicitImportDirectory, p.m_qmldirFiles); + QQmlJS::AST::Node::accept(rootNode, &v); + }; + importer.setImportVisitor(qmltcVisitor); + + QQmlJSLogger logger; + logger.setFileName(url); + logger.setCode(sourceCode); + setupLogger(logger); + + auto currentScope = QQmlJSScope::create(); + if (parser.isSet(moduleOption)) + currentScope->setOwnModuleName(parser.value(moduleOption)); + + QmltcVisitor visitor(currentScope, &importer, &logger, + QQmlJSImportVisitor::implicitImportDirectory(url, &mapper), qmldirFiles); + visitor.setMode(QmltcVisitor::Compile); + QmltcTypeResolver typeResolver { &importer }; + typeResolver.init(&visitor, qmlParser.rootNode()); + + using PassManagerPtr = + std::unique_ptr<QQmlSA::PassManager, + decltype(&QQmlSA::PassManagerPrivate::deletePassManager)>; + PassManagerPtr passMan(QQmlSA::PassManagerPrivate::createPassManager(&visitor, &typeResolver), + &QQmlSA::PassManagerPrivate::deletePassManager); + passMan->registerPropertyPass(std::make_unique<QQmlJSLiteralBindingCheck>(passMan.get()), + QString(), QString(), QString()); + passMan->analyze(QQmlJSScope::createQQmlSAElement(visitor.result())); + + if (logger.hasErrors()) + return EXIT_FAILURE; + + QList<QQmlJS::DiagnosticMessage> warnings = importer.takeGlobalWarnings(); + if (!warnings.isEmpty()) { + logger.log(QStringLiteral("Type warnings occurred while compiling file:"), qmlImport, + QQmlJS::SourceLocation()); + logger.processMessages(warnings, qmlImport); + // Log_Import is critical for the compiler + return EXIT_FAILURE; + } + + QmltcCompiler compiler(url, &typeResolver, &visitor, &logger); + compiler.compile(info); + + if (logger.hasErrors()) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} |