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>
This commit is contained in:
parent
4fcadf6a36
commit
cc46882322
|
@ -369,3 +369,6 @@ cmake_install.cmake
|
|||
*_autogen
|
||||
tst_*.xml
|
||||
CMakeLists.txt.user
|
||||
|
||||
# QML Language Server ini-files
|
||||
.qmlls.ini
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
// Copyright (C) 2021 The Qt Company Ltd.
|
||||
// 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.
|
||||
|
||||
*/
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -0,0 +1,5 @@
|
|||
import QtQuick 2.15
|
||||
|
||||
Item {
|
||||
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
import QtQuick 2.15
|
||||
|
||||
Item {
|
||||
|
||||
}
|
|
@ -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"
|
Loading…
Reference in New Issue