2021-09-16 07:35:33 +00:00
|
|
|
/****************************************************************************
|
|
|
|
**
|
|
|
|
** Copyright (C) 2021 The Qt Company Ltd.
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
|
|
|
**
|
|
|
|
** This file is part of the tools applications of the Qt Toolkit.
|
|
|
|
**
|
|
|
|
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
|
|
|
** Commercial License Usage
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
** accordance with the commercial license agreement provided with the
|
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
|
|
**
|
|
|
|
** GNU General Public License Usage
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
|
|
**
|
|
|
|
** $QT_END_LICENSE$
|
|
|
|
**
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
#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-02-04 09:42:33 +00:00
|
|
|
const QSet<QQmlJSLoggerCategory> exceptions {
|
|
|
|
Log_ControlsSanity, // this category is just weird
|
|
|
|
Log_UnusedImport, // not critical
|
|
|
|
};
|
|
|
|
|
|
|
|
for (int i = 0; i <= static_cast<int>(QQmlJSLoggerCategory_Last); ++i) {
|
|
|
|
const auto c = static_cast<QQmlJSLoggerCategory>(i);
|
|
|
|
if (exceptions.contains(c))
|
|
|
|
continue;
|
|
|
|
logger.setCategoryLevel(c, QtCriticalMsg);
|
|
|
|
logger.setCategoryIgnored(c, false);
|
|
|
|
}
|
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);
|
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);
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
fprintf(stderr, "Too many (expected 1) 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);
|
|
|
|
info.resourcePath = paths.first();
|
|
|
|
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-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()) {
|
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
|
|
|
logger.log(QStringLiteral("Type warnings occurred while compiling file:"), Log_Import,
|
|
|
|
QQmlJS::SourceLocation());
|
|
|
|
logger.processMessages(warnings, Log_Import);
|
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;
|
|
|
|
}
|