Pass qmldir to qmlcachegen, qmllint and qmltc, not the qmltypes file

The tools will still grudgingly accept qmltypes files being passed via
the -i option. We generally expect qmldir files, though. Ignoring the
qmldir file and importing the qmltypes directly, ignores qmldir imports,
dependencies and other component entries. This leads to unresolvable
types.

[ChangeLog][QML Tooling] qmllint expects qmldir files, not qmltypes
files to be passed via the -i option now. This enables it to see the
imports and dependencies of the module being imported. For backwards
compatibility it still accepts qmltypes files, with a warning.

Pick-to: 6.3
Fixes: QTBUG-99043
Change-Id: I5ed32d7e78df1e604aaf1bfa2ebda09d5d57b628
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Ulf Hermann 2021-12-09 11:22:52 +01:00
parent 22f4306283
commit 576fafd1e6
21 changed files with 104 additions and 76 deletions

View File

@ -1022,7 +1022,7 @@ function(qt6_target_compile_qml_to_cpp target)
set(args_single NAMESPACE)
set(args_multi FILES)
# TODO: add qmltypes argument
# TODO: add qmldir argument
# TODO: add qml import path argument
cmake_parse_arguments(PARSE_ARGV 1 arg
@ -1555,7 +1555,12 @@ function(qt6_target_qml_sources target)
endif()
if(NOT no_cachegen AND arg_QML_FILES)
_qt_internal_genex_getproperty(types_file ${target} QT_QML_MODULE_PLUGIN_TYPES_FILE)
# Even if we don't generate a qmldir file, it still should be here, manually written.
# We can pass it unconditionally. If it's not there, qmlcachegen or qmlsc might warn,
# but that's not fatal.
set(qmldir_file ${output_dir}/qmldir)
_qt_internal_genex_getproperty(qmlcachegen ${target} QT_QMLCACHEGEN_EXECUTABLE)
_qt_internal_genex_getproperty(direct_calls ${target} QT_QMLCACHEGEN_DIRECT_CALLS)
_qt_internal_genex_getjoinedproperty(arguments ${target}
@ -1575,7 +1580,7 @@ function(qt6_target_qml_sources target)
endif()
set(cachegen_args
${import_paths}
"$<${have_types_file}:-i$<SEMICOLON>${types_file}>"
-i "${qmldir_file}"
"$<${have_direct_calls}:--direct-calls>"
"$<${have_arguments}:${arguments}>"
${qrc_resource_args}

View File

@ -620,11 +620,11 @@ bool qSaveQmlJSUnitAsCpp(const QString &inputFileName, const QString &outputFile
}
QQmlJSAotCompiler::QQmlJSAotCompiler(
QQmlJSImporter *importer, const QString &resourcePath, const QStringList &qmltypesFiles,
QQmlJSImporter *importer, const QString &resourcePath, const QStringList &qmldirFiles,
QQmlJSLogger *logger)
: m_typeResolver(importer)
, m_resourcePath(resourcePath)
, m_qmltypesFiles(qmltypesFiles)
, m_qmldirFiles(qmldirFiles)
, m_importer(importer)
, m_logger(logger)
{
@ -642,7 +642,7 @@ void QQmlJSAotCompiler::setDocument(
m_entireSourceCodeLines = irDocument->code.split(u'\n');
QQmlJSImportVisitor visitor(m_importer, m_logger,
resourcePathInfo.canonicalPath() + u'/',
m_qmltypesFiles);
m_qmldirFiles);
m_typeResolver.init(&visitor, irDocument->program);
}

View File

@ -77,7 +77,7 @@ class QQmlJSAotCompiler
{
public:
QQmlJSAotCompiler(QQmlJSImporter *importer, const QString &resourcePath,
const QStringList &qmltypesFiles, QQmlJSLogger *logger);
const QStringList &qmldirFiles, QQmlJSLogger *logger);
virtual ~QQmlJSAotCompiler() = default;
@ -98,7 +98,7 @@ protected:
QStringList m_entireSourceCodeLines;
const QString m_resourcePath;
const QStringList m_qmltypesFiles;
const QStringList m_qmldirFiles;
const QmlIR::Document *m_document = nullptr;
const QmlIR::Object *m_currentObject = nullptr;

View File

@ -439,17 +439,32 @@ QQmlJSImporter::AvailableTypes QQmlJSImporter::builtinImportHelper()
/*!
* Imports types from the specified \a qmltypesFiles.
*/
void QQmlJSImporter::importQmltypes(const QStringList &qmltypesFiles)
void QQmlJSImporter::importQmldirs(const QStringList &qmldirFiles)
{
AvailableTypes types(builtinImportHelper().cppNames);
for (const auto &qmltypeFile : qmltypesFiles) {
for (const auto &file : qmldirFiles) {
Import result;
readQmltypes(qmltypeFile, &result.objects, &result.dependencies);
QString qmldirName;
if (file.endsWith(SlashQmldir)) {
result = readQmldir(file.chopped(SlashQmldir.size()));
qmldirName = file;
} else {
m_warnings.append({
QStringLiteral("Argument %1 to -i option is not a qmldir file. Assuming qmltypes.")
.arg(file),
QtWarningMsg,
QQmlJS::SourceLocation()
});
readQmltypes(file, &result.objects, &result.dependencies);
// Append _FAKE_QMLDIR to our made up qmldir name so that if it ever gets used somewhere
// else except for cache lookups, it will blow up due to a missing file instead of
// producing weird results.
qmldirName = file + QStringLiteral("_FAKE_QMLDIR");
}
// Append _FAKE_QMLDIR to our made up qmldir name so that if it ever gets used somewhere else except for cache lookups,
// it will blow up due to a missing file instead of producing weird results.
const QString qmldirName = qmltypeFile + QStringLiteral("_FAKE_QMLDIR");
m_seenQmldirFiles.insert(qmldirName, result);
for (const auto &object : qAsConst(result.objects)) {

View File

@ -60,7 +60,7 @@ public:
void setResourceFileMapper(QQmlJSResourceFileMapper *mapper) { m_mapper = mapper; }
ImportedTypes importBuiltins();
void importQmltypes(const QStringList &qmltypesFiles);
void importQmldirs(const QStringList &qmltypesFiles);
QQmlJSScope::Ptr importFile(const QString &file);
ImportedTypes importDirectory(const QString &directory, const QString &prefix = QString());

View File

@ -75,9 +75,9 @@ inline QString getScopeName(const QQmlJSScope::ConstPtr &scope, QQmlJSScope::Sco
QQmlJSImportVisitor::QQmlJSImportVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger,
const QString &implicitImportDirectory,
const QStringList &qmltypesFiles)
const QStringList &qmldirFiles)
: m_implicitImportDirectory(implicitImportDirectory),
m_qmltypesFiles(qmltypesFiles),
m_qmldirFiles(qmldirFiles),
m_currentScope(QQmlJSScope::create(QQmlJSScope::JSFunctionScope)),
m_importer(importer),
m_logger(logger)
@ -321,8 +321,8 @@ void QQmlJSImportVisitor::importBaseModules()
addImportWithLocation(name, invalidLoc);
}
if (!m_qmltypesFiles.isEmpty())
m_importer->importQmltypes(m_qmltypesFiles);
if (!m_qmldirFiles.isEmpty())
m_importer->importQmldirs(m_qmldirFiles);
// Pulling in the modules and neighboring qml files of the qmltypes we're trying to lint is not
// something we need to do.

View File

@ -61,7 +61,7 @@ class QQmlJSImportVisitor : public QQmlJS::AST::Visitor
public:
QQmlJSImportVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger,
const QString &implicitImportDirectory,
const QStringList &qmltypesFiles = QStringList());
const QStringList &qmldirFiles = QStringList());
~QQmlJSImportVisitor();
QQmlJSScope::Ptr result() const;
@ -152,7 +152,7 @@ protected:
QString m_implicitImportDirectory;
QStringView m_inlineComponentName;
bool m_nextIsInlineComponent = false;
QStringList m_qmltypesFiles;
QStringList m_qmldirFiles;
QQmlJSScope::Ptr m_currentScope;
QQmlJSScope::Ptr m_savedBindingOuterScope;
QQmlJSScope::Ptr m_exportedRootScope;

View File

@ -89,8 +89,7 @@ QQmlJSScope::Ptr QQmlJSTypeReader::operator()()
QQmlJSImportVisitor membersVisitor(
m_importer, &logger,
QQmlJSImportVisitor::implicitImportDirectory(m_file, m_importer->resourceFileMapper()),
m_qmltypesFiles);
QQmlJSImportVisitor::implicitImportDirectory(m_file, m_importer->resourceFileMapper()));
rootNode->accept(&membersVisitor);
auto result = membersVisitor.result();
Q_ASSERT(result);

View File

@ -54,11 +54,9 @@ QT_BEGIN_NAMESPACE
class QQmlJSTypeReader
{
public:
QQmlJSTypeReader(QQmlJSImporter *importer, const QString &file,
const QStringList &qmltypesFiles = QStringList())
QQmlJSTypeReader(QQmlJSImporter *importer, const QString &file)
: m_importer(importer)
, m_file(file)
, m_qmltypesFiles(qmltypesFiles)
{}
QQmlJSScope::Ptr operator()();
@ -67,7 +65,7 @@ public:
private:
QQmlJSImporter *m_importer;
QString m_file;
QStringList m_qmltypesFiles;
QStringList m_qmldirFiles;
QList<QQmlJS::DiagnosticMessage> m_errors;
};

View File

@ -39,8 +39,8 @@
QT_BEGIN_NAMESPACE
Codegen::Codegen(QQmlJSImporter *importer, const QString &fileName,
const QStringList &qmltypesFiles, QQmlJSLogger *logger, QQmlJSTypeInfo *typeInfo)
: QQmlJSAotCompiler(importer, fileName, qmltypesFiles, logger)
const QStringList &qmldirFiles, QQmlJSLogger *logger, QQmlJSTypeInfo *typeInfo)
: QQmlJSAotCompiler(importer, fileName, qmldirFiles, logger)
, m_typeInfo(typeInfo)
{
}

View File

@ -59,7 +59,7 @@ QT_BEGIN_NAMESPACE
class Codegen : public QQmlJSAotCompiler
{
public:
Codegen(QQmlJSImporter *importer, const QString &fileName, const QStringList &qmltypesFiles,
Codegen(QQmlJSImporter *importer, const QString &fileName, const QStringList &qmldirFiles,
QQmlJSLogger *logger, QQmlJSTypeInfo *typeInfo);
void setDocument(const QmlIR::JSCodeGen *codegen, const QmlIR::Document *document) override;

View File

@ -44,12 +44,12 @@
QT_BEGIN_NAMESPACE
FindWarningVisitor::FindWarningVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger,
QStringList qmltypesFiles,
QStringList qmldirFiles,
QList<QQmlJS::SourceLocation> comments)
: QQmlJSImportVisitor(
importer, logger,
implicitImportDirectory(logger->fileName(), importer->resourceFileMapper()),
qmltypesFiles)
qmldirFiles)
{
parseComments(comments);
}

View File

@ -58,7 +58,7 @@ class FindWarningVisitor : public QQmlJSImportVisitor
Q_DISABLE_COPY_MOVE(FindWarningVisitor)
public:
explicit FindWarningVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger,
QStringList qmltypesFiles, QList<QQmlJS::SourceLocation> comments);
QStringList qmldirFiles, QList<QQmlJS::SourceLocation> comments);
~FindWarningVisitor() override = default;
bool check();

View File

@ -54,7 +54,7 @@ QQmlLinter::QQmlLinter(const QStringList &importPaths, bool useAbsolutePath)
bool QQmlLinter::lintFile(const QString &filename, const QString *fileContents, const bool silent,
QJsonArray *json, const QStringList &qmlImportPaths,
const QStringList &qmltypesFiles, const QStringList &resourceFiles,
const QStringList &qmldirFiles, const QStringList &resourceFiles,
const QMap<QString, QQmlJSLogger::Option> &options)
{
// Make sure that we don't expose an old logger if we return before a new one is created.
@ -198,7 +198,7 @@ bool QQmlLinter::lintFile(const QString &filename, const QString *fileContents,
FindWarningVisitor v {
&m_importer,
m_logger.get(),
qmltypesFiles,
qmldirFiles,
engine.comments(),
};
@ -231,7 +231,7 @@ bool QQmlLinter::lintFile(const QString &filename, const QString *fileContents,
? u':' + resourcePaths.first()
: filename;
Codegen codegen { &m_importer, resolvedPath, qmltypesFiles, m_logger.get(), &typeInfo };
Codegen codegen { &m_importer, resolvedPath, qmldirFiles, m_logger.get(), &typeInfo };
codegen.setTypeResolver(std::move(typeResolver));
QQmlJSSaveFunction saveFunction = [](const QV4::CompiledData::SaveableUnitPointer &,
const QQmlJSAotFunctionMap &,

View File

@ -56,7 +56,7 @@ public:
bool lintFile(const QString &filename, const QString *fileContents, const bool silent,
QJsonArray *json, const QStringList &qmlImportPaths,
const QStringList &qmltypesFiles, const QStringList &resourceFiles,
const QStringList &qmldirFiles, const QStringList &resourceFiles,
const QMap<QString, QQmlJSLogger::Option> &options);
const QQmlJSLogger *logger() const { return m_logger.get(); }

View File

@ -353,9 +353,10 @@ void TestQmllint::autoqmltypes()
process.waitForFinished();
QCOMPARE(process.exitStatus(), QProcess::NormalExit);
QCOMPARE(process.exitCode(), 0);
QVERIFY(process.exitCode() != 0);
QVERIFY(process.readAllStandardError().isEmpty());
QVERIFY(process.readAllStandardError()
.contains("is not a qmldir file. Assuming qmltypes"));
QVERIFY(process.readAllStandardOutput().isEmpty());
}
@ -1169,7 +1170,8 @@ void TestQmllint::settingsFile()
QVERIFY(runQmllint("settings/bare/bare.qml", false, { "--bare" }, false, false)
.contains(QStringLiteral("Failed to find the following builtins: "
"builtins.qmltypes, jsroot.qmltypes")));
QVERIFY(runQmllint("settings/qmltypes/qmltypes.qml", true, QStringList(), false).isEmpty());
QVERIFY(runQmllint("settings/qmltypes/qmltypes.qml", false, QStringList(), false)
.contains(QStringLiteral("not a qmldir file. Assuming qmltypes.")));
}
void TestQmllint::additionalImplicitImport()

View File

@ -112,8 +112,8 @@ int main(int argc, char **argv)
parser.addOption(directCallsOption);
QCommandLineOption importsOption(
QStringLiteral("i"),
QCoreApplication::translate("main", "Import extra qmltypes"),
QCoreApplication::translate("main", "qmltypes file"));
QCoreApplication::translate("main", "Import extra qmldir"),
QCoreApplication::translate("main", "qmldir file"));
parser.addOption(importsOption);
QCommandLineOption importPathOption(
QStringLiteral("I"),

View File

@ -130,17 +130,19 @@ All warnings can be set to three levels:
const QString qmlImportNoDefaultSetting = QLatin1String("DisableDefaultImports");
settings.addOption(qmlImportNoDefaultSetting, false);
QCommandLineOption qmltypesFilesOption(
QCommandLineOption qmldirFilesOption(
QStringList() << "i"
<< "qmltypes",
QLatin1String("Import the specified qmltypes files. By default, all qmltypes files "
"found in the current directory are used. When this option is set, you "
"have to explicitly add files from the current directory if you want "
"them to be used."),
QLatin1String("qmltypes"));
parser.addOption(qmltypesFilesOption);
const QString qmltypesFilesSetting = QLatin1String("OverwriteImportTypes");
settings.addOption(qmltypesFilesSetting);
QLatin1String("Import the specified qmldir files. By default, the qmldir file found "
"in the current directory is used if present. If no qmldir file is found,"
"but qmltypes files are, those are imported instead. When this option is "
"set, you have to explicitly add the qmldir or any qmltypes files in the "
"current directory if you want it to be used. Importing qmltypes files "
"without their corresponding qmldir file is inadvisable."),
QLatin1String("qmldirs"));
parser.addOption(qmldirFilesOption);
const QString qmldirFilesSetting = QLatin1String("OverwriteImportTypes");
settings.addOption(qmldirFilesSetting);
QCommandLineOption absolutePath(
QStringList() << "absolute-path",
@ -192,18 +194,25 @@ All warnings can be set to three levels:
QStringList qmlImportPaths =
parser.isSet(qmlImportNoDefault) ? QStringList {} : defaultImportPaths;
QStringList defaultQmltypesFiles;
if (parser.isSet(qmltypesFilesOption)) {
defaultQmltypesFiles = parser.values(qmltypesFilesOption);
QStringList defaultQmldirFiles;
if (parser.isSet(qmldirFilesOption)) {
defaultQmldirFiles = parser.values(qmldirFilesOption);
} else {
// If none are given explicitly, use the qmltypes files from the current directory.
QDirIterator it(".", {"*.qmltypes"}, QDir::Files);
while (it.hasNext()) {
it.next();
defaultQmltypesFiles.append(it.fileInfo().absoluteFilePath());
// If nothing given explicitly, use the qmldir file from the current directory.
QFileInfo qmldirFile(QStringLiteral("qmldir"));
if (qmldirFile.isFile()) {
defaultQmldirFiles.append(qmldirFile.absoluteFilePath());
} else {
// If no qmldir file is found, use the qmltypes files
// from the current directory for backwards compatibility.
QDirIterator it(".", {"*.qmltypes"}, QDir::Files);
while (it.hasNext()) {
it.next();
defaultQmldirFiles.append(it.fileInfo().absoluteFilePath());
}
}
}
QStringList qmltypesFiles = defaultQmltypesFiles;
QStringList qmldirFiles = defaultQmldirFiles;
const QStringList defaultResourceFiles =
parser.isSet(resourceOption) ? parser.values(resourceOption) : QStringList {};
@ -241,12 +250,12 @@ All warnings can be set to three levels:
addAbsolutePaths(resourceFiles, settings.value(resourceSetting).toStringList());
qmltypesFiles = defaultQmltypesFiles;
if (settings.isSet(qmltypesFilesSetting)
&& !settings.value(qmltypesFilesSetting).toStringList().isEmpty()) {
qmltypesFiles = {};
addAbsolutePaths(qmltypesFiles,
settings.value(qmltypesFilesSetting).toStringList());
qmldirFiles = defaultQmldirFiles;
if (settings.isSet(qmldirFilesSetting)
&& !settings.value(qmldirFilesSetting).toStringList().isEmpty()) {
qmldirFiles = {};
addAbsolutePaths(qmldirFiles,
settings.value(qmldirFilesSetting).toStringList());
}
if (parser.isSet(qmlImportNoDefault)
@ -267,7 +276,7 @@ All warnings can be set to three levels:
for (const QString &filename : arguments) {
#endif
success &= linter.lintFile(filename, nullptr, silent, useJson ? &jsonFiles : nullptr,
qmlImportPaths, qmltypesFiles, resourceFiles, options);
qmlImportPaths, qmldirFiles, resourceFiles, options);
}
if (useJson) {

View File

@ -70,11 +70,11 @@ int main(int argc, char **argv)
QCoreApplication::translate("main", "import directory")
};
parser.addOption(importPathOption);
QCommandLineOption qmltypesOption {
u"i"_qs, QCoreApplication::translate("main", "Include extra qmltypes"),
QCoreApplication::translate("main", "qmltypes file")
QCommandLineOption qmldirOption {
u"i"_qs, QCoreApplication::translate("main", "Include extra qmldir files"),
QCoreApplication::translate("main", "qmldir file")
};
parser.addOption(qmltypesOption);
parser.addOption(qmldirOption);
QCommandLineOption outputCppOption {
u"impl"_qs, QCoreApplication::translate("main", "Generated C++ source file path"),
QCoreApplication::translate("main", "cpp path")
@ -132,7 +132,7 @@ int main(int argc, char **argv)
QStringList importPaths = parser.values(importPathOption);
importPaths.append(QLibraryInfo::path(QLibraryInfo::QmlImportsPath));
importPaths.append(QFileInfo(url).absolutePath());
QStringList qmltypesFiles = parser.values(qmltypesOption);
QStringList qmldirFiles = parser.values(qmldirOption);
QString outputCppFile;
if (!parser.isSet(outputCppOption)) {
@ -174,7 +174,7 @@ int main(int argc, char **argv)
logger.setFileName(url);
logger.setCode(sourceCode);
setupLogger(logger);
QmltcVisitor visitor(&importer, &logger, implicitImportDirectory, qmltypesFiles);
QmltcVisitor visitor(&importer, &logger, implicitImportDirectory, qmldirFiles);
QmltcTypeResolver typeResolver { &importer };
typeResolver.init(&visitor, document.program);

View File

@ -44,8 +44,8 @@ static QString uniqueNameFromPieces(const QStringList &pieces, QHash<QString, in
}
QmltcVisitor::QmltcVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger,
const QString &implicitImportDirectory, const QStringList &qmltypesFiles)
: QQmlJSImportVisitor(importer, logger, implicitImportDirectory, qmltypesFiles)
const QString &implicitImportDirectory, const QStringList &qmldirFiles)
: QQmlJSImportVisitor(importer, logger, implicitImportDirectory, qmldirFiles)
{
m_qmlTypeNames.append(QFileInfo(logger->fileName()).baseName()); // put document root
}

View File

@ -46,7 +46,7 @@ class QmltcVisitor : public QQmlJSImportVisitor
public:
QmltcVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger,
const QString &implicitImportDirectory,
const QStringList &qmltypesFiles = QStringList());
const QStringList &qmldirFiles = QStringList());
bool visit(QQmlJS::AST::UiObjectDefinition *) override;
void endVisit(QQmlJS::AST::UiObjectDefinition *) override;