aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSami Shalayel <sami.shalayel@qt.io>2023-08-10 16:54:05 +0200
committerSami Shalayel <sami.shalayel@qt.io>2023-08-23 12:31:08 +0200
commitcc46882322f372b9b46948329fa18456413b18e9 (patch)
tree38a2b65ee8da82e97582ab040e4fdead1d38a6e8
parent4fcadf6a36aca48c5cfb0cc717c30211f5be0ba8 (diff)
cmake: add option to generate .qmlls.ini files
Add a CMake bool variable QT_QML_GENERATE_QMLLS_INI to indicate to qt_add_qml_module that .qmlls.ini files have to be generated for each directory where qt_add_qml_module is invoked. The .qmlls.ini files are generated in the source directory. This option needs to be set explicitly by the user, either as normal or as cache variable. The .qmlls.ini files are required for qmlls to work properly in any editor, without needing custom qmlls-client implementations for every client that passes the build folder on to qmlls. Each source directory with a CMakeLists.txt that does the qt_add_qml_module command gets a .qmlls.ini that contains all the build folders of all qt_add_qml_module-commands of the current source directory. This mimics how qmlls searches for the .qmlls.ini (starting at the source file directory and going up until it finds a .qmlls.ini), and avoids having to save a map from source folders to build folders in the .ini file. Warn the user when using CMake versions <= 3.19: QT_QML_GENERATE_QMLLS_INI requires deferring the calls to write the .qmlls.ini files to make sure that all build paths of all qt_add_qml_module calls of the current directory can be written inside the .qmlls.ini file. For multi-config build, this just makes use of the last generated config and overwrites the .qmlls.ini files for the other files. This is similar to what CMake does for compile_commands.json on the ninja multi-config generator, see https://gitlab.kitware.com/cmake/cmake/-/merge_requests/7477 for example. Added some documentation about the option, and also a test. Fixes: QTBUG-115225 Task-number: QTCREATORBUG-29419 Task-number: QTCREATORBUG-29407 Change-Id: I4a463ff7af534de266359176188fb016d48cf2c4 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io> Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
-rw-r--r--.gitignore3
-rw-r--r--src/qml/Qt6QmlMacros.cmake45
-rw-r--r--src/qml/doc/src/cmake/cmake-variables.qdoc33
-rw-r--r--src/quick/doc/src/guidelines/qtquick-tool-qmlls.qdoc3
-rw-r--r--tests/auto/cmake/CMakeLists.txt3
-rw-r--r--tests/auto/cmake/test_generate_qmlls_ini/CMakeLists.txt42
-rw-r--r--tests/auto/cmake/test_generate_qmlls_ini/Main.qml5
-rw-r--r--tests/auto/cmake/test_generate_qmlls_ini/SomeSubfolder/CMakeLists.txt13
-rw-r--r--tests/auto/cmake/test_generate_qmlls_ini/SomeSubfolder/Main.qml5
-rw-r--r--tests/auto/cmake/test_generate_qmlls_ini/main.cpp63
10 files changed, 215 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index da05a081b8..def0f040cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -369,3 +369,6 @@ cmake_install.cmake
*_autogen
tst_*.xml
CMakeLists.txt.user
+
+# QML Language Server ini-files
+.qmlls.ini
diff --git a/src/qml/Qt6QmlMacros.cmake b/src/qml/Qt6QmlMacros.cmake
index 8aea3a759d..334ac5f763 100644
--- a/src/qml/Qt6QmlMacros.cmake
+++ b/src/qml/Qt6QmlMacros.cmake
@@ -712,6 +712,51 @@ Check https://doc.qt.io/qt-6/qt-cmake-policy-qtp0001.html for policy details."
endif()
endforeach()
+ if(${QT_QML_GENERATE_QMLLS_INI})
+ if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.19.0")
+ # collect all build dirs obtained from all the qt_add_qml_module calls and
+ # write the .qmlls.ini file in a deferred call
+
+ if(NOT "${arg_OUTPUT_DIRECTORY}" STREQUAL "")
+ set(output_folder "${arg_OUTPUT_DIRECTORY}")
+ else()
+ set(output_folder "${CMAKE_CURRENT_BINARY_DIR}")
+ endif()
+ get_filename_component(build_folder "${output_folder}" DIRECTORY)
+ get_directory_property(_qmlls_ini_build_folders _qmlls_ini_build_folders)
+ list(APPEND _qmlls_ini_build_folders "${build_folder}")
+ set_directory_properties(PROPERTIES _qmlls_ini_build_folders "${_qmlls_ini_build_folders}")
+
+ # if no call with id 'qmlls_ini_generation_id' was deferred for this directory, do it now
+ cmake_language(DEFER GET_CALL qmlls_ini_generation_id call)
+ if("${call}" STREQUAL "")
+ cmake_language(EVAL CODE
+ "cmake_language(DEFER ID qmlls_ini_generation_id CALL _qt_internal_write_deferred_qmlls_ini_file)"
+ )
+ endif()
+ else()
+ get_property(__qt_internal_generate_qmlls_ini_warning GLOBAL PROPERTY __qt_internal_generate_qmlls_ini_warning)
+ if (NOT "${__qt_internal_generate_qmlls_ini_warning}")
+ message(WARNING "QT_QML_GENERATE_QMLLS_INI is not supported on CMake versions < 3.19, disabling...")
+ set_property(GLOBAL PROPERTY __qt_internal_generate_qmlls_ini_warning ON)
+ endif()
+ endif()
+ endif()
+endfunction()
+
+function(_qt_internal_write_deferred_qmlls_ini_file)
+ set(qmlls_ini_file "${CMAKE_CURRENT_SOURCE_DIR}/.qmlls.ini")
+ get_directory_property(_qmlls_ini_build_folders _qmlls_ini_build_folders)
+ list(REMOVE_DUPLICATES _qmlls_ini_build_folders)
+ if(NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
+ # replace cmake list separator ';' with unix path separator ':'
+ string(REPLACE ";" ":" concatenated_build_dirs "${_qmlls_ini_build_folders}")
+ else()
+ # cmake list separator and windows path separator are both ';', so no replacement needed
+ set(concatenated_build_dirs "${_qmlls_ini_build_folders}")
+ endif()
+ set(file_content "[General]\nbuildDir=${concatenated_build_dirs}\n")
+ file(CONFIGURE OUTPUT "${qmlls_ini_file}" CONTENT "${file_content}")
endfunction()
if(NOT QT_NO_CREATE_VERSIONLESS_FUNCTIONS)
diff --git a/src/qml/doc/src/cmake/cmake-variables.qdoc b/src/qml/doc/src/cmake/cmake-variables.qdoc
index 6e35655b43..e2d6598ed1 100644
--- a/src/qml/doc/src/cmake/cmake-variables.qdoc
+++ b/src/qml/doc/src/cmake/cmake-variables.qdoc
@@ -2,6 +2,15 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
+\group cmake-variables-qtqml
+\title CMake Global Variables in Qt6 Qml
+
+\l{CMake Command Reference#Qt6::Qml}{CMake Commands} know about the following
+global CMake variables:
+
+*/
+
+/*!
\page cmake-variable-qt-qml-output-directory.html
\ingroup cmake-variables-qtqml
@@ -26,6 +35,30 @@ The \c QT_QML_OUTPUT_DIRECTORY will also be added to the import path of the
modules under the same base location. This allows the project to use a source
directory structure that doesn't exactly match the URI structure of the QML
modules, or to merge sets of QML modules under a common base point.
+*/
+
+/*!
+\page cmake-variable-qt-qml-generate-qmlls-ini.html
+\ingroup cmake-variables-qtqml
+
+\title QT_QML_GENERATE_QMLLS_INI
+
+\brief Enables autogeneration of .qmlls.ini files for QML Language Server
+
+\c QT_QML_GENERATE_QMLLS_INI is a boolean that describes whether
+\l{qt6_add_qml_module}{qt6_add_qml_module()} calls generate \c{.qmlls.ini} files inside
+the \b{source folder}. If \c{.qmlls.ini} files already exists in the source folder,
+then they are overwritten.
+
+\note Using \c QT_QML_GENERATE_QMLLS_INI requires a CMake version >= 3.19.
+
+These \c{.qmlls.ini} files contain the path to the last configured build directory,
+and is needed by \l{QML Language Server} to find user defined modules. See also
+\l{QML Language Server} about the other ways of passing build folder to QML Language Server.
+
+\note The files generated by \c QT_QML_GENERATE_QMLLS_INI are only valid for the current
+configuration and should be ignored by your version control system. For git, this can be
+done by adding \c{.qmlls.ini} to your \c{.gitignore}, for example.
*/
diff --git a/src/quick/doc/src/guidelines/qtquick-tool-qmlls.qdoc b/src/quick/doc/src/guidelines/qtquick-tool-qmlls.qdoc
index 78c88872b6..9319f827a6 100644
--- a/src/quick/doc/src/guidelines/qtquick-tool-qmlls.qdoc
+++ b/src/quick/doc/src/guidelines/qtquick-tool-qmlls.qdoc
@@ -82,6 +82,9 @@ QML Language Server can be configured via a configuration file \c{.qmlls.ini}.
This file should be in the root source directory of the project.
It should be a text file in the ini-format.
+\note \c{.qmlls.ini} files can be generated automatically via
+\l{QT_QML_GENERATE_QMLLS_INI}.
+
The configuration file should look like this:
\code
// .qmlls.ini
diff --git a/tests/auto/cmake/CMakeLists.txt b/tests/auto/cmake/CMakeLists.txt
index 606a743bbb..12ca64f329 100644
--- a/tests/auto/cmake/CMakeLists.txt
+++ b/tests/auto/cmake/CMakeLists.txt
@@ -122,6 +122,9 @@ if(TARGET Qt::Quick)
BINARY cmake_test
)
endif()
+ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.19")
+ _qt_internal_test_expect_pass(test_generate_qmlls_ini BINARY tst_generate_qmlls_ini)
+ endif()
endif()
if(NOT QT6_IS_SHARED_LIBS_BUILD)
_qt_internal_test_expect_pass(test_import_static_shapes_plugin_resources
diff --git a/tests/auto/cmake/test_generate_qmlls_ini/CMakeLists.txt b/tests/auto/cmake/test_generate_qmlls_ini/CMakeLists.txt
new file mode 100644
index 0000000000..d841a1beb5
--- /dev/null
+++ b/tests/auto/cmake/test_generate_qmlls_ini/CMakeLists.txt
@@ -0,0 +1,42 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.19)
+project(tst_generate_qmlls_ini)
+
+find_package(Qt6 REQUIRED COMPONENTS Core Qml Test)
+
+qt_standard_project_setup()
+
+qt_add_executable(tst_generate_qmlls_ini main.cpp)
+target_link_libraries(tst_generate_qmlls_ini PRIVATE Qt6::Test)
+
+set(QT_QML_GENERATE_QMLLS_INI ON CACHE BOOL "" FORCE)
+
+add_subdirectory(SomeSubfolder)
+
+qt_add_qml_module(tst_generate_qmlls_ini
+ URI MainModule
+ VERSION 1.0
+ NO_RESOURCE_TARGET_PATH
+ SOURCES
+ main.cpp
+ QML_FILES
+ Main.qml
+)
+target_compile_definitions(tst_generate_qmlls_ini
+ PRIVATE
+ "SOURCE_DIRECTORY=u\"${CMAKE_CURRENT_SOURCE_DIR}\"_s"
+ "BUILD_DIRECTORY=u\"${CMAKE_CURRENT_BINARY_DIR}\"_s"
+)
+
+qt_add_qml_module(Module
+ URI Module
+ VERSION 1.0
+ QML_FILES
+ Main.qml
+ OUTPUT_DIRECTORY ./qml/hello/subfolders/Module
+)
+
+# Ensure linting runs when building the default "all" target
+set_target_properties(all_qmllint PROPERTIES EXCLUDE_FROM_ALL FALSE)
diff --git a/tests/auto/cmake/test_generate_qmlls_ini/Main.qml b/tests/auto/cmake/test_generate_qmlls_ini/Main.qml
new file mode 100644
index 0000000000..68c21087cb
--- /dev/null
+++ b/tests/auto/cmake/test_generate_qmlls_ini/Main.qml
@@ -0,0 +1,5 @@
+import QtQuick 2.15
+
+Item {
+
+}
diff --git a/tests/auto/cmake/test_generate_qmlls_ini/SomeSubfolder/CMakeLists.txt b/tests/auto/cmake/test_generate_qmlls_ini/SomeSubfolder/CMakeLists.txt
new file mode 100644
index 0000000000..ae6fb009f8
--- /dev/null
+++ b/tests/auto/cmake/test_generate_qmlls_ini/SomeSubfolder/CMakeLists.txt
@@ -0,0 +1,13 @@
+qt_add_qml_module(ModuleA
+ URI ModuleA
+ VERSION 1.0
+ QML_FILES Main.qml
+ OUTPUT_DIRECTORY ./qml/Some/Sub/Folder/ModuleA
+)
+
+qt_add_qml_module(ModuleB
+ URI ModuleB
+ VERSION 1.0
+ QML_FILES Main.qml
+ OUTPUT_DIRECTORY ./qml/Some/Sub/Folder/ModuleB
+)
diff --git a/tests/auto/cmake/test_generate_qmlls_ini/SomeSubfolder/Main.qml b/tests/auto/cmake/test_generate_qmlls_ini/SomeSubfolder/Main.qml
new file mode 100644
index 0000000000..68c21087cb
--- /dev/null
+++ b/tests/auto/cmake/test_generate_qmlls_ini/SomeSubfolder/Main.qml
@@ -0,0 +1,5 @@
+import QtQuick 2.15
+
+Item {
+
+}
diff --git a/tests/auto/cmake/test_generate_qmlls_ini/main.cpp b/tests/auto/cmake/test_generate_qmlls_ini/main.cpp
new file mode 100644
index 0000000000..0aedf61c1f
--- /dev/null
+++ b/tests/auto/cmake/test_generate_qmlls_ini/main.cpp
@@ -0,0 +1,63 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QtCore/qobject.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtQml/qqml.h>
+#include <QtTest/qtest.h>
+
+class tst_generate_qmlls_ini : public QObject
+{
+ Q_OBJECT
+private slots:
+ void qmllsIniAreCorrect();
+};
+
+using namespace Qt::StringLiterals;
+
+#ifndef SOURCE_DIRECTORY
+# define SOURCE_DIRECTORY u"invalid_source_directory"_s
+#endif
+#ifndef BUILD_DIRECTORY
+# define BUILD_DIRECTORY u"invalid_build_directory"_s
+#endif
+
+void tst_generate_qmlls_ini::qmllsIniAreCorrect()
+{
+ const QString qmllsIniName = u".qmlls.ini"_s;
+ QDir source(SOURCE_DIRECTORY);
+ QDir build(BUILD_DIRECTORY);
+ if (!source.exists())
+ QSKIP(u"Cannot find source directory '%1', skipping test..."_s.arg(SOURCE_DIRECTORY)
+ .toLatin1());
+
+ {
+ auto file = QFile(source.absoluteFilePath(qmllsIniName));
+ QVERIFY(file.exists());
+ QVERIFY(file.open(QFile::ReadOnly | QFile::Text));
+ const auto fileContent = QString::fromUtf8(file.readAll());
+ auto secondFolder = QDir(build.absolutePath().append(u"/qml/hello/subfolders"_s));
+ QVERIFY(secondFolder.exists());
+ QCOMPARE(fileContent,
+ u"[General]\nbuildDir=%1%2%3\n"_s.arg(build.absolutePath(), QDir::listSeparator(),
+ secondFolder.absolutePath()));
+ }
+
+ QDir sourceSubfolder = source;
+ QVERIFY(sourceSubfolder.cd(u"SomeSubfolder"_s));
+ QDir buildSubfolder(build.absolutePath().append(u"/SomeSubfolder/qml/Some/Sub/Folder"_s));
+ {
+ auto file = QFile(sourceSubfolder.absoluteFilePath(qmllsIniName));
+ QVERIFY(file.exists());
+ QVERIFY(file.open(QFile::ReadOnly | QFile::Text));
+ const auto fileContent = QString::fromUtf8(file.readAll());
+ QCOMPARE(fileContent,
+ u"[General]\nbuildDir=%1\n"_s.arg(buildSubfolder.absolutePath()));
+ }
+}
+
+QTEST_MAIN(tst_generate_qmlls_ini)
+
+#include "main.moc"