aboutsummaryrefslogtreecommitdiffstats
path: root/tools/qmltc/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/qmltc/main.cpp')
-rw-r--r--tools/qmltc/main.cpp313
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;
+}