2022-05-13 13:12:05 +00:00
|
|
|
// Copyright (C) 2021 The Qt Company Ltd.
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2021-09-16 07:35:33 +00:00
|
|
|
|
|
|
|
#include "qmltccommandlineutils.h"
|
2022-02-28 14:28:34 +00:00
|
|
|
#include "qmltcvisitor.h"
|
|
|
|
#include "qmltctyperesolver.h"
|
|
|
|
|
|
|
|
#include "qmltccompiler.h"
|
2021-09-16 07:35:33 +00:00
|
|
|
|
|
|
|
#include <private/qqmljscompiler_p.h>
|
2022-01-24 12:46:58 +00:00
|
|
|
#include <private/qqmljsresourcefilemapper_p.h>
|
2021-09-16 07:35:33 +00:00
|
|
|
|
|
|
|
#include <QtCore/qcoreapplication.h>
|
|
|
|
#include <QtCore/qurl.h>
|
|
|
|
#include <QtCore/qhashfunctions.h>
|
|
|
|
#include <QtCore/qfileinfo.h>
|
|
|
|
#include <QtCore/qlibraryinfo.h>
|
2022-01-18 15:16:20 +00:00
|
|
|
#include <QtCore/qcommandlineparser.h>
|
2021-09-16 07:35:33 +00:00
|
|
|
|
2022-06-03 11:52:47 +00:00
|
|
|
#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>
|
|
|
|
|
2021-02-16 11:52:41 +00:00
|
|
|
#include <cstdlib> // EXIT_SUCCESS, EXIT_FAILURE
|
2021-09-16 07:35:33 +00:00
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
|
2021-09-21 08:52:24 +00:00
|
|
|
void setupLogger(QQmlJSLogger &logger) // prepare logger to work with compiler
|
|
|
|
{
|
2022-05-30 12:52:03 +00:00
|
|
|
for (const QQmlJSLogger::Category &category : logger.categories()) {
|
|
|
|
if (category == qmlControlsSanity // this category is just weird
|
|
|
|
|| category == qmlUnusedImports)
|
2022-02-04 09:42:33 +00:00
|
|
|
continue;
|
2022-05-30 12:52:03 +00:00
|
|
|
logger.setCategoryLevel(category.id(), QtCriticalMsg);
|
|
|
|
logger.setCategoryIgnored(category.id(), false);
|
2022-02-04 09:42:33 +00:00
|
|
|
}
|
2021-09-21 08:52:24 +00:00
|
|
|
}
|
|
|
|
|
2021-09-16 07:35:33 +00:00
|
|
|
int main(int argc, char **argv)
|
|
|
|
{
|
|
|
|
// Produce reliably the same output for the same input by disabling QHash's
|
|
|
|
// random seeding.
|
|
|
|
qSetGlobalQHashSeed(0);
|
|
|
|
QCoreApplication app(argc, argv);
|
2022-03-21 09:21:18 +00:00
|
|
|
QCoreApplication::setApplicationName(u"qmltc"_s);
|
2021-09-16 07:35:33 +00:00
|
|
|
QCoreApplication::setApplicationVersion(QStringLiteral(QT_VERSION_STR));
|
|
|
|
|
|
|
|
// command-line parsing:
|
|
|
|
QCommandLineParser parser;
|
|
|
|
parser.addHelpOption();
|
|
|
|
parser.addVersionOption();
|
|
|
|
|
|
|
|
QCommandLineOption importPathOption {
|
2022-03-21 09:21:18 +00:00
|
|
|
u"I"_s, QCoreApplication::translate("main", "Look for QML modules in specified directory"),
|
2021-09-16 07:35:33 +00:00
|
|
|
QCoreApplication::translate("main", "import directory")
|
|
|
|
};
|
|
|
|
parser.addOption(importPathOption);
|
2021-12-09 10:22:52 +00:00
|
|
|
QCommandLineOption qmldirOption {
|
2022-03-21 09:21:18 +00:00
|
|
|
u"i"_s, QCoreApplication::translate("main", "Include extra qmldir files"),
|
2021-12-09 10:22:52 +00:00
|
|
|
QCoreApplication::translate("main", "qmldir file")
|
2021-09-16 07:35:33 +00:00
|
|
|
};
|
2021-12-09 10:22:52 +00:00
|
|
|
parser.addOption(qmldirOption);
|
2021-09-16 07:35:33 +00:00
|
|
|
QCommandLineOption outputCppOption {
|
2022-03-21 09:21:18 +00:00
|
|
|
u"impl"_s, QCoreApplication::translate("main", "Generated C++ source file path"),
|
2021-09-16 07:35:33 +00:00
|
|
|
QCoreApplication::translate("main", "cpp path")
|
|
|
|
};
|
|
|
|
parser.addOption(outputCppOption);
|
|
|
|
QCommandLineOption outputHOption {
|
2022-03-21 09:21:18 +00:00
|
|
|
u"header"_s, QCoreApplication::translate("main", "Generated C++ header file path"),
|
2021-09-16 07:35:33 +00:00
|
|
|
QCoreApplication::translate("main", "h path")
|
|
|
|
};
|
|
|
|
parser.addOption(outputHOption);
|
2022-01-24 12:46:58 +00:00
|
|
|
QCommandLineOption resourceOption {
|
2022-03-21 09:21:18 +00:00
|
|
|
u"resource"_s,
|
2022-01-24 12:46:58 +00:00
|
|
|
QCoreApplication::translate(
|
|
|
|
"main", "Qt resource file that might later contain one of the compiled files"),
|
|
|
|
QCoreApplication::translate("main", "resource file name")
|
|
|
|
};
|
|
|
|
parser.addOption(resourceOption);
|
2022-07-01 13:53:48 +00:00
|
|
|
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);
|
2021-10-20 09:14:52 +00:00
|
|
|
QCommandLineOption namespaceOption {
|
2022-03-21 09:21:18 +00:00
|
|
|
u"namespace"_s, QCoreApplication::translate("main", "Namespace of the generated C++ code"),
|
2021-10-20 09:14:52 +00:00
|
|
|
QCoreApplication::translate("main", "namespace")
|
|
|
|
};
|
|
|
|
parser.addOption(namespaceOption);
|
2021-09-16 07:35:33 +00:00
|
|
|
|
|
|
|
parser.process(app);
|
|
|
|
|
|
|
|
const QStringList sources = parser.positionalArguments();
|
|
|
|
if (sources.size() != 1) {
|
|
|
|
if (sources.isEmpty()) {
|
|
|
|
parser.showHelp();
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "%s\n",
|
2022-03-21 09:21:18 +00:00
|
|
|
qPrintable(u"Too many input files specified: '"_s + sources.join(u"' '"_s)
|
2021-09-16 07:35:33 +00:00
|
|
|
+ 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString sourceCode = loadUrl(url);
|
|
|
|
if (sourceCode.isEmpty())
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
|
|
|
QString implicitImportDirectory = getImplicitImportDirectory(url);
|
|
|
|
if (implicitImportDirectory.isEmpty())
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
|
|
|
QStringList importPaths = parser.values(importPathOption);
|
2021-11-09 07:33:45 +00:00
|
|
|
importPaths.append(QLibraryInfo::path(QLibraryInfo::QmlImportsPath));
|
2021-12-09 10:22:52 +00:00
|
|
|
QStringList qmldirFiles = parser.values(qmldirOption);
|
2021-09-16 07:35:33 +00:00
|
|
|
|
|
|
|
QString outputCppFile;
|
|
|
|
if (!parser.isSet(outputCppOption)) {
|
2022-03-21 09:21:18 +00:00
|
|
|
outputCppFile = url.first(url.size() - 3) + u"cpp"_s;
|
2021-09-16 07:35:33 +00:00
|
|
|
} else {
|
|
|
|
outputCppFile = parser.value(outputCppOption);
|
|
|
|
}
|
|
|
|
|
|
|
|
QString outputHFile;
|
|
|
|
if (!parser.isSet(outputHOption)) {
|
2022-03-21 09:21:18 +00:00
|
|
|
outputHFile = url.first(url.size() - 3) + u"h"_s;
|
2021-09-16 07:35:33 +00:00
|
|
|
} else {
|
|
|
|
outputHFile = parser.value(outputHOption);
|
|
|
|
}
|
|
|
|
|
2022-01-26 12:04:29 +00:00
|
|
|
if (!parser.isSet(resourceOption)) {
|
2022-01-24 12:46:58 +00:00
|
|
|
fprintf(stderr, "No resource paths for file: %s\n", qPrintable(inputFile));
|
2021-09-16 07:35:33 +00:00
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// main logic:
|
2022-06-03 11:52:47 +00:00
|
|
|
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)));
|
|
|
|
}
|
2021-09-16 07:35:33 +00:00
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
|
2022-01-24 12:46:58 +00:00
|
|
|
const QStringList resourceFiles = parser.values(resourceOption);
|
|
|
|
QQmlJSResourceFileMapper mapper(resourceFiles);
|
2022-07-01 13:53:48 +00:00
|
|
|
const QStringList metaResourceFiles = parser.values(metaResourceOption);
|
|
|
|
QQmlJSResourceFileMapper metaDataMapper(metaResourceFiles);
|
2022-01-24 12:46:58 +00:00
|
|
|
|
2022-06-10 07:41:27 +00:00
|
|
|
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;
|
|
|
|
};
|
2022-01-24 12:46:58 +00:00
|
|
|
// verify that we can map current file to qrc (then use the qrc path later)
|
|
|
|
const QStringList paths = mapper.resourcePaths(QQmlJSResourceFileMapper::localFileFilter(url));
|
2022-01-26 12:04:29 +00:00
|
|
|
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) {
|
2022-06-10 07:41:27 +00:00
|
|
|
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;
|
|
|
|
}
|
2022-01-24 12:46:58 +00:00
|
|
|
}
|
|
|
|
|
2022-02-28 14:28:34 +00:00
|
|
|
QmltcCompilerInfo info;
|
|
|
|
info.outputCppFile = parser.value(outputCppOption);
|
|
|
|
info.outputHFile = parser.value(outputHOption);
|
2022-06-10 07:41:27 +00:00
|
|
|
info.resourcePath = firstQml(paths);
|
2022-02-28 14:28:34 +00:00
|
|
|
info.outputNamespace = parser.value(namespaceOption);
|
2021-09-16 07:35:33 +00:00
|
|
|
|
2022-05-26 22:07:22 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-01-24 12:46:58 +00:00
|
|
|
QQmlJSImporter importer { importPaths, &mapper };
|
2022-07-01 13:53:48 +00:00
|
|
|
importer.setMetaDataMapper(&metaDataMapper);
|
2022-06-07 11:09:54 +00:00
|
|
|
auto createQmltcVisitor = [](const QQmlJSScope::Ptr &root, QQmlJSImporter *importer,
|
|
|
|
QQmlJSLogger *logger, const QString &implicitImportDirectory,
|
|
|
|
const QStringList &qmldirFiles) -> QQmlJSImportVisitor * {
|
|
|
|
return new QmltcVisitor(root, importer, logger, implicitImportDirectory, qmldirFiles);
|
|
|
|
};
|
|
|
|
importer.setImportVisitorCreator(createQmltcVisitor);
|
|
|
|
|
2021-11-18 13:26:29 +00:00
|
|
|
QQmlJSLogger logger;
|
|
|
|
logger.setFileName(url);
|
|
|
|
logger.setCode(sourceCode);
|
2021-09-21 08:52:24 +00:00
|
|
|
setupLogger(logger);
|
2021-02-16 11:52:41 +00:00
|
|
|
|
2022-06-07 11:09:54 +00:00
|
|
|
QmltcVisitor visitor(QQmlJSScope::create(), &importer, &logger,
|
2022-02-28 14:28:34 +00:00
|
|
|
QQmlJSImportVisitor::implicitImportDirectory(url, &mapper), qmldirFiles);
|
2022-06-08 14:36:56 +00:00
|
|
|
visitor.setMode(QmltcVisitor::Compile);
|
2022-02-28 14:28:34 +00:00
|
|
|
QmltcTypeResolver typeResolver { &importer };
|
2022-06-03 11:52:47 +00:00
|
|
|
typeResolver.init(&visitor, qmlParser.rootNode());
|
2021-11-18 13:24:12 +00:00
|
|
|
|
2022-02-04 09:42:33 +00:00
|
|
|
if (logger.hasErrors())
|
2021-11-09 07:33:45 +00:00
|
|
|
return EXIT_FAILURE;
|
2021-09-16 07:35:33 +00:00
|
|
|
|
2022-01-18 12:08:09 +00:00
|
|
|
QList<QQmlJS::DiagnosticMessage> warnings = importer.takeGlobalWarnings();
|
|
|
|
if (!warnings.isEmpty()) {
|
2022-05-30 12:52:03 +00:00
|
|
|
logger.log(QStringLiteral("Type warnings occurred while compiling file:"), qmlImport,
|
Redesign QQmlJSLogger internals
High-level goal: be able to reuse existing infrastructure
"as is" to configure semantic analysis categories in tools
(qmllint, qmltc, qmlsc, etc.)
To achieve that, simplify the logging to always "log"
something, without explicitly specifying the severity. The
severity is now baked into the category (and we can extend
those to cover different cases)
One slight deviation is the cache generation which likes
to do its own thing at present. Provide a "forced logging"
option where we can specify which severify we want. The
hope is that this gets removed at some point
Particular list of (noteworthy) changes:
* No more "thresholding" by the level (this is rarely needed
and is actually questionable). Instead, we can ignore a
particular category explicitly
* Category levels are repurposed as category severities
(at least from the high-level picture that always should've
been this way)
* log{Warning,Info,Critical} removed. We use category severity
instead
* "category error" makes zero sense so removed: if our severity
is:
- QtWarningMsg (qmllint), it is already an "error"
- QtCriticalMsg (compilers), it is already an "error"
* Align m_output and m_{infos,warnings,errors} stored information
* Accept the fact that we don't support QtDebugMsg and QtFatalMsg
* Additional categories added to cover for places where the same
category would be both an error and not an error
Task-number: QTBUG-100052
Change-Id: I3cd5d17d58be204f48428877bed053f756ac40a8
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-02-03 12:45:18 +00:00
|
|
|
QQmlJS::SourceLocation());
|
2022-05-30 12:52:03 +00:00
|
|
|
logger.processMessages(warnings, qmlImport);
|
2022-02-09 10:38:33 +00:00
|
|
|
// Log_Import is critical for the compiler
|
|
|
|
return EXIT_FAILURE;
|
2022-01-18 12:08:09 +00:00
|
|
|
}
|
2022-02-09 10:38:33 +00:00
|
|
|
|
2022-03-10 08:37:01 +00:00
|
|
|
QmltcCompiler compiler(url, &typeResolver, &visitor, &logger);
|
2022-06-03 11:52:47 +00:00
|
|
|
compiler.compile(info);
|
2022-01-18 12:08:09 +00:00
|
|
|
|
2022-02-04 09:42:33 +00:00
|
|
|
if (logger.hasErrors())
|
2021-09-16 07:35:33 +00:00
|
|
|
return EXIT_FAILURE;
|
2021-11-09 07:33:45 +00:00
|
|
|
|
2021-09-16 07:35:33 +00:00
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|