Warning, /frameworks/extra-cmake-modules/modules/ECMQmlModule.cmake is written in an unsupported language. File is not indexed.

0001 #
0002 # SPDX-FileCopyrightText: 2021 Arjen Hiemstra <ahiemstra@heimr.nl>
0003 #
0004 # SPDX-License-Identifier: BSD-3-Clause
0005 
0006 #[========================================================================[.rst:
0007 ECMQmlModule
0008 ------------
0009 
0010 This file contains helper functions to make it easier to create QML modules. It
0011 takes care of a number of things that often need to be repeated. It also takes
0012 care of special handling of QML modules between shared and static builds. When
0013 building a static version of a QML module, the relevant QML source files are
0014 bundled into the static library. When using a shared build, the QML plugin and
0015 relevant QML files are copied to the target's ``RUNTIME_OUTPUT_DIRECTORY`` to make
0016 it easier to run things directly from the build directory.
0017 
0018 Example usage:
0019 
0020 .. code-block:: cmake
0021 
0022     ecm_add_qml_module(ExampleModule URI "org.example.Example" VERSION 1.4)
0023 
0024     target_sources(ExampleModule PRIVATE ExamplePlugin.cpp)
0025     target_link_libraries(ExampleModule PRIVATE Qt::Quick)
0026 
0027     ecm_target_qml_sources(ExampleModule SOURCES ExampleItem.qml)
0028     ecm_target_qml_sources(ExampleModule SOURCES AnotherExampleItem.qml VERSION 1.5)
0029 
0030     ecm_finalize_qml_module(ExampleModule DESTINATION ${KDE_INSTALL_QMLDIR})
0031 
0032 
0033 ::
0034 
0035     ecm_add_qml_module(<target name> URI <module uri> [VERSION <module version>] [NO_PLUGIN] [CLASSNAME <class name>])
0036 
0037 This will declare a new CMake target called ``<target name>``. The ``URI``
0038 argument is required and should be a proper QML module URI. The ``URI`` is used,
0039 among others, to generate a subdirectory where the module will be installed to.
0040 
0041 If the ``VERSION`` argument is specified, it is used to initialize the default
0042 version that is used by  ``ecm_target_qml_sources`` when adding QML files. If it
0043 is not specified, a  default of 1.0 is used. Additionally, if a version greater
0044 than or equal to 2.0 is specified, the major version is appended to the
0045 installation path of the module.
0046 
0047 If the option ``NO_PLUGIN`` is set, a target is declared that is not expected to
0048 contain any C++ QML plugin.
0049 
0050 If the optional ``CLASSNAME`` argument is supplied, it will be used as class
0051 name in the generated QMLDIR file. If it is not specified, the target name will
0052 be used instead.
0053 
0054 You can add C++ and QML source files to the target using ``target_sources`` and
0055 ``ecm_target_qml_sources``, respectively.
0056 
0057 Since 5.91.0
0058 
0059 ::
0060 
0061     ecm_add_qml_module_dependencies(<target> DEPENDS <module string> [<module string> ...])
0062 
0063 Add the list of dependencies specified by the ``DEPENDS`` argument to be listed
0064 as dependencies in the generated QMLDIR file of ``<target>``.
0065 
0066 Since 5.91.0
0067 
0068 ::
0069 
0070     ecm_target_qml_sources(<target> SOURCES <source.qml> [<source.qml> ...] [VERSION <version>] [PATH <path>] [PRIVATE])
0071 
0072 Add the list of QML files specified by the ``SOURCES`` argument as source files
0073 to the QML module target ``<target>``.
0074 
0075 If the optional ``VERSION`` argument is specified, all QML files will be added
0076 with the specified version. If it is not specified, they will use the version of
0077 the QML module target.
0078 
0079 If the optional ``PRIVATE`` argument is specified, the QML files will be
0080 included in the target but not in the generated qmldir file. Any version
0081 argument will be ignored.
0082 
0083 The optional ``PATH`` argument declares a subdirectory of the module where the
0084 files should be copied to. By default, files will be copied to the module root.
0085 
0086 This function will fail if ``<target>`` is not a QML module target or any of the
0087 specified files do not exist.
0088 
0089 Since 5.91.0
0090 
0091 ::
0092 
0093     ecm_finalize_qml_module(<target> DESTINATION <QML install destination>)
0094 
0095 Finalize the specified QML module target. This must be called after all other
0096 setup (like adding sources) on the target has been done. It will perform a
0097 number of tasks:
0098 
0099 - It will generate a qmldir file from the QML files added to the target. If the
0100   module has a C++ plugin, this will also be included in the qmldir file.
0101 - If ``BUILD_SHARED_LIBS`` is off, a QRC file is generated from the QML files
0102   added to the target. This QRC file will be included when compiling the C++ QML
0103   module. The built static library will be installed in a subdirection of
0104   ``DESTINATION`` based on the QML module's uri. Note that if ``NO_PLUGIN`` is
0105   set, a C++ QML plugin will be generated to include the QRC files.
0106 - If ``BUILD_SHARED_LIBS`` in on, all generated files, QML sources and the C++
0107   plugin will be installed in a subdirectory of ``DESTINATION`` based upon the
0108   QML module's uri. In addition, these files will also be copied to the target's
0109   ``RUNTIME_OUTPUT_DIRECTORY`` in a similar subdirectory.
0110 
0111 This function will fail if ``<target>`` is not a QML module target.
0112 
0113 Since 5.91.0
0114 
0115 #]========================================================================]
0116 
0117 set(_ECM_QMLMODULE_STATIC_QMLONLY_H   "${CMAKE_CURRENT_LIST_DIR}/ECMQmlModule.h.in")
0118 set(_ECM_QMLMODULE_STATIC_QMLONLY_CPP "${CMAKE_CURRENT_LIST_DIR}/ECMQmlModule.cpp.in")
0119 
0120 set(_ECM_QMLMODULE_PROPERTY_URI "_ecm_qml_uri")
0121 set(_ECM_QMLMODULE_PROPERTY_QMLDIR "_ecm_qmldir_file")
0122 set(_ECM_QMLMODULE_PROPERTY_FILES "_ecm_qml_files")
0123 set(_ECM_QMLMODULE_PROPERTY_QMLONLY "_ecm_qml_only")
0124 set(_ECM_QMLMODULE_PROPERTY_VERSION "_ecm_qml_version")
0125 set(_ECM_QMLMODULE_PROPERTY_PRIVATE "_ecm_qml_private")
0126 set(_ECM_QMLMODULE_PROPERTY_PATH "_ecm_qml_path")
0127 set(_ECM_QMLMODULE_PROPERTY_CLASSNAME "_ecm_qml_classname")
0128 set(_ECM_QMLMODULE_PROPERTY_DEPENDS "_ecm_qml_depends")
0129 
0130 macro(_ecm_qmlmodule_verify_qml_target ARG_TARGET)
0131     if (NOT TARGET ${ARG_TARGET})
0132         message(FATAL_ERROR "Target ${ARG_TARGET} does not exist")
0133     endif()
0134     get_target_property(_qml_uri ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_URI})
0135     if ("${_qml_uri}" STREQUAL "" OR "${_qml_uri}" STREQUAL "${_ECM_QMLMODULE_PROPERTY_URI}-NOTFOUND")
0136         message(FATAL_ERROR "Target ${ARG_TARGET} is not a QML plugin target")
0137     endif()
0138 endmacro()
0139 
0140 macro(_ecm_qmlmodule_uri_to_path ARG_OUTPUT ARG_PATH ARG_VERSION)
0141     string(REPLACE "." "/" _output "${ARG_PATH}")
0142 
0143     # If the major version of the module is >2.0, Qt expects a ".MajorVersion"
0144     # suffix on the directory. So handle that.
0145     if (${ARG_VERSION} VERSION_GREATER_EQUAL 2.0)
0146         string(REGEX MATCH "^([0-9]+)" _version_major ${ARG_VERSION})
0147         set("${ARG_OUTPUT}" "${_output}.${_version_major}")
0148     else()
0149         set("${ARG_OUTPUT}" "${_output}")
0150     endif()
0151 endmacro()
0152 
0153 function(_ecm_qmlmodule_generate_qmldir ARG_TARGET)
0154     get_target_property(_qml_uri ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_URI})
0155     get_target_property(_qml_files ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_FILES})
0156     get_target_property(_qml_only ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_QMLONLY})
0157     get_target_property(_qml_classname ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_CLASSNAME})
0158 
0159     set(_qmldir_template "# This file was automatically generated by ECMQmlModule and should not be modified")
0160 
0161     string(APPEND _qmldir_template "\nmodule ${_qml_uri}")
0162 
0163     if (NOT ${_qml_only})
0164         string(APPEND _qmldir_template "\nplugin ${ARG_TARGET}")
0165 
0166         if ("${_qml_classname}" STREQUAL "")
0167             string(APPEND _qmldir_template "\nclassname ${ARG_TARGET}")
0168         else()
0169             string(APPEND _qmldir_template "\nclassname ${_qml_classname}")
0170         endif()
0171     endif()
0172 
0173     get_target_property(_qml_depends ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_DEPENDS})
0174     if (NOT "${_qml_depends}" STREQUAL "")
0175         foreach(_depends ${_qml_depends})
0176             string(APPEND _qmldir_template "\ndepends ${_depends}")
0177         endforeach()
0178     endif()
0179 
0180     foreach(_file ${_qml_files})
0181         get_filename_component(_filename ${_file} NAME)
0182         get_filename_component(_classname ${_file} NAME_WE)
0183         get_property(_version SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_VERSION})
0184         get_property(_private SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_PRIVATE})
0185         if (NOT _private)
0186             string(APPEND _qmldir_template "\n${_classname} ${_version} ${_filename}")
0187         endif()
0188     endforeach()
0189 
0190     string(APPEND _qmldir_template "\n")
0191 
0192     file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${ARG_TARGET}_qmldir" "${_qmldir_template}")
0193     set_target_properties(${ARG_TARGET} PROPERTIES _ecm_qmldir_file "${CMAKE_CURRENT_BINARY_DIR}/${ARG_TARGET}_qmldir")
0194 endfunction()
0195 
0196 function(_ecm_qmlmodule_generate_qrc ARG_TARGET)
0197     get_target_property(_qml_uri ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_URI})
0198     get_target_property(_qml_files ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_FILES})
0199     get_target_property(_qmldir_file ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_QMLDIR})
0200     get_target_property(_qml_version ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_VERSION})
0201 
0202     _ecm_qmlmodule_uri_to_path(_qml_prefix "${_qml_uri}" "${_qml_version}")
0203 
0204     set(_qrc_template "<!-- This file was automatically generated by ECMQmlModule and should not be modified -->")
0205 
0206     string(APPEND _qrc_template "\n<RCC>\n<qresource prefix=\"/qt-project.org/imports/${_qml_prefix}\">")
0207     string(APPEND _qrc_template "\n<file alias=\"qmldir\">${_qmldir_file}</file>")
0208 
0209     foreach(_file ${_qml_files})
0210         get_filename_component(_filename ${_file} NAME)
0211         get_property(_path SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_PATH})
0212 
0213         set(_file_path "${_filename}")
0214         if (NOT "${_path}" STREQUAL "")
0215             set(_file_path "${_path}/${_filename}")
0216         endif()
0217 
0218         string(APPEND _qrc_template "\n<file alias=\"${_file_path}\">${_file}</file>")
0219     endforeach()
0220 
0221     string(APPEND _qrc_template "\n</qresource>\n</RCC>\n")
0222 
0223     file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${ARG_TARGET}.qrc" "${_qrc_template}")
0224     qt_add_resources(_qrc_output "${CMAKE_CURRENT_BINARY_DIR}/${ARG_TARGET}.qrc")
0225 
0226     target_sources(${ARG_TARGET} PRIVATE ${_qrc_output})
0227 endfunction()
0228 
0229 function(ecm_add_qml_module ARG_TARGET)
0230     cmake_parse_arguments(PARSE_ARGV 1 ARG "NO_PLUGIN" "URI;VERSION;CLASSNAME" "")
0231 
0232     if ("${ARG_TARGET}" STREQUAL "")
0233         message(FATAL_ERROR "ecm_add_qml_module called without a valid target name.")
0234     endif()
0235 
0236     if ("${ARG_URI}" STREQUAL "")
0237         message(FATAL_ERROR "ecm_add_qml_module called without a valid module URI.")
0238     endif()
0239 
0240     string(FIND "${ARG_URI}" " " "_space")
0241     if (${_space} GREATER_EQUAL 0)
0242         message(FATAL_ERROR "ecm_add_qml_module called without a valid module URI.")
0243     endif()
0244 
0245     if (${BUILD_SHARED_LIBS} AND ${ARG_NO_PLUGIN})
0246         # CMake doesn't like library targets without sources, so if we have no
0247         # C++ sources, use a plain target that we can add all the install stuff
0248         # to.
0249         add_custom_target(${ARG_TARGET} ALL)
0250     else()
0251         add_library(${ARG_TARGET})
0252     endif()
0253 
0254     if ("${ARG_VERSION}" STREQUAL "")
0255         set(ARG_VERSION "1.0")
0256     endif()
0257 
0258     set_target_properties(${ARG_TARGET} PROPERTIES
0259         ${_ECM_QMLMODULE_PROPERTY_URI} "${ARG_URI}"
0260         ${_ECM_QMLMODULE_PROPERTY_FILES} ""
0261         ${_ECM_QMLMODULE_PROPERTY_QMLONLY} "${ARG_NO_PLUGIN}"
0262         ${_ECM_QMLMODULE_PROPERTY_VERSION} "${ARG_VERSION}"
0263         ${_ECM_QMLMODULE_PROPERTY_CLASSNAME} "${ARG_CLASSNAME}"
0264         ${_ECM_QMLMODULE_PROPERTY_DEPENDS} ""
0265     )
0266 
0267     # QQmlImportDatabase::resolvePlugin doesn't accept lib prefixes under
0268     # Windows, causing to fail to import when using as a dynamic plugin.
0269     if (MINGW)
0270         set_target_properties(${ARG_TARGET} PROPERTIES PREFIX "")
0271     endif()
0272 
0273     # -Muri is required for static QML plugins to work properly, see
0274     # https://bugreports.qt.io/browse/QTBUG-82598
0275     set_target_properties(${ARG_TARGET} PROPERTIES AUTOMOC_MOC_OPTIONS -Muri=${ARG_URI})
0276 endfunction()
0277 
0278 function(ecm_add_qml_module_dependencies ARG_TARGET)
0279     cmake_parse_arguments(PARSE_ARGV 1 ARG "" "" "DEPENDS")
0280 
0281     _ecm_qmlmodule_verify_qml_target(${ARG_TARGET})
0282 
0283     if ("${ARG_DEPENDS}" STREQUAL "")
0284         message(FATAL_ERROR "ecm_add_qml_module_dependencies called without required argument DEPENDS")
0285     endif()
0286 
0287     set_target_properties(${ARG_TARGET} PROPERTIES ${_ECM_QMLMODULE_PROPERTY_DEPENDS} "${ARG_DEPENDS}")
0288 endfunction()
0289 
0290 function(ecm_target_qml_sources ARG_TARGET)
0291     cmake_parse_arguments(PARSE_ARGV 1 ARG "PRIVATE" "VERSION;PATH" "SOURCES")
0292 
0293     _ecm_qmlmodule_verify_qml_target(${ARG_TARGET})
0294 
0295     if ("${ARG_SOURCES}" STREQUAL "")
0296         message(FATAL_ERROR "ecm_target_qml_sources called without required argument SOURCES")
0297     endif()
0298 
0299     if ("${ARG_VERSION}" STREQUAL "")
0300         get_target_property(ARG_VERSION ${ARG_TARGET} "_ecm_qml_version")
0301     endif()
0302 
0303     foreach(_file ${ARG_SOURCES})
0304         # Check if a given file exists, but only for files that are not
0305         # automatically generated.
0306         set(_path "${_file}")
0307         get_source_file_property(_generated ${_file} GENERATED)
0308         if (NOT _generated)
0309             if (IS_ABSOLUTE ${_path})
0310                 # Assume absolute paths are generated, which may not always be
0311                 # true but is fairly likely.
0312                 set(_generated TRUE)
0313             else()
0314                 string(FIND ${_file} ${CMAKE_BINARY_DIR} _in_binary_dir)
0315                 if (${_in_binary_dir} GREATER_EQUAL 0)
0316                     # Assume anything in binary dir is generated.
0317                     set(_generated TRUE)
0318                 else()
0319                     set(_path "${CMAKE_CURRENT_SOURCE_DIR}/${_file}")
0320                 endif()
0321             endif()
0322         endif()
0323 
0324         if (NOT ${_generated} AND NOT EXISTS ${_path})
0325             message(FATAL_ERROR "ecm_target_qml_sources called with nonexistent file ${_file}")
0326         endif()
0327 
0328         if (NOT ARG_PRIVATE)
0329             set_property(SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_VERSION} "${ARG_VERSION}")
0330         else()
0331             set_property(SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_PRIVATE} TRUE)
0332         endif()
0333         set_property(SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_PATH} "${ARG_PATH}")
0334         set_property(TARGET ${ARG_TARGET}
0335             APPEND PROPERTY ${_ECM_QMLMODULE_PROPERTY_FILES} ${_path}
0336         )
0337     endforeach()
0338 endfunction()
0339 
0340 function(ecm_finalize_qml_module ARG_TARGET)
0341     cmake_parse_arguments(PARSE_ARGV 1 ARG "" "DESTINATION" "")
0342 
0343     _ecm_qmlmodule_verify_qml_target(${ARG_TARGET})
0344 
0345     if ("${ARG_DESTINATION}" STREQUAL "")
0346         message(FATAL_ERROR "ecm_finalize_qml_module called without required argument DESTINATION")
0347     endif()
0348 
0349     _ecm_qmlmodule_generate_qmldir(${ARG_TARGET})
0350 
0351     get_target_property(_qml_uri ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_URI})
0352     get_target_property(_version ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_VERSION})
0353 
0354     _ecm_qmlmodule_uri_to_path(_plugin_path "${_qml_uri}" "${_version}")
0355 
0356     get_target_property(_qml_only ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_QMLONLY})
0357 
0358     if (NOT BUILD_SHARED_LIBS)
0359         _ecm_qmlmodule_generate_qrc(${ARG_TARGET})
0360         set(CPP_RESOURCE_INIT "#include <qglobal.h> \n#include <QDebug> \n void initQmlResource${ARG_TARGET}() {Q_INIT_RESOURCE(${ARG_TARGET}); qWarning()<<Q_FUNC_INFO;};")
0361         file(WRITE ${ARGS_TARGET}_init.cpp "${CPP_RESOURCE_INIT}")
0362         target_sources(${ARG_TARGET} PRIVATE ${ARGS_TARGET}_init.cpp)
0363         target_compile_definitions(${ARG_TARGET} PRIVATE -DQT_PLUGIN_RESOURCE_INIT_FUNCTION=initQmlResource${ARG_TARGET} -DQT_STATICPLUGIN=1)
0364 
0365         if (${_qml_only})
0366             # If we do not have any C++ sources for the target, we need a way to
0367             # compile the generated QRC file. So generate a very plain C++ QML
0368             # plugin that we include in the target.
0369             configure_file(${_ECM_QMLMODULE_STATIC_QMLONLY_H} ${CMAKE_CURRENT_BINARY_DIR}/QmlModule.h @ONLY)
0370             configure_file(${_ECM_QMLMODULE_STATIC_QMLONLY_CPP} ${CMAKE_CURRENT_BINARY_DIR}/QmlModule.cpp @ONLY)
0371 
0372             target_sources(${ARG_TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/QmlModule.cpp)
0373             if (TARGET Qt::Qml)
0374                 target_link_libraries(${ARG_TARGET} PRIVATE Qt::Qml)
0375             else()
0376                 target_link_libraries(${ARG_TARGET} PRIVATE Qt5::Qml)
0377             endif()
0378         endif()
0379 
0380         install(TARGETS ${ARG_TARGET} DESTINATION ${ARG_DESTINATION}/${_plugin_path})
0381 
0382         return()
0383     endif()
0384 
0385     get_target_property(_runtime_output_dir ${ARG_TARGET} RUNTIME_OUTPUT_DIRECTORY)
0386     if (NOT ${_runtime_output_dir})
0387         if ("${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" STREQUAL "")
0388             set(_runtime_output_dir ${CMAKE_CURRENT_BINARY_DIR})
0389         else()
0390             set(_runtime_output_dir ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
0391         endif()
0392     endif()
0393 
0394     add_custom_command(
0395         TARGET ${ARG_TARGET}
0396         POST_BUILD
0397         WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
0398         COMMAND ${CMAKE_COMMAND} -E make_directory ${_runtime_output_dir}/${_plugin_path}
0399         BYPRODUCTS ${_runtime_output_dir}/${_plugin_path}
0400     )
0401 
0402     get_target_property(_qmldir_file ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_QMLDIR})
0403     install(FILES ${_qmldir_file} DESTINATION ${ARG_DESTINATION}/${_plugin_path} RENAME "qmldir")
0404 
0405     add_custom_command(
0406         TARGET ${ARG_TARGET}
0407         POST_BUILD
0408         WORKING_DIRECTORY ${_runtime_output_dir}
0409         COMMAND ${CMAKE_COMMAND} -E copy ${_qmldir_file} ${_plugin_path}/qmldir
0410     )
0411 
0412     if (NOT ${_qml_only})
0413         install(TARGETS ${ARG_TARGET} DESTINATION ${ARG_DESTINATION}/${_plugin_path})
0414 
0415         add_custom_command(
0416             TARGET ${ARG_TARGET}
0417             POST_BUILD
0418             WORKING_DIRECTORY ${_runtime_output_dir}
0419             COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${ARG_TARGET}> ${_plugin_path}
0420         )
0421     endif()
0422 
0423     get_target_property(_qml_files ${ARG_TARGET} ${_ECM_QMLMODULE_PROPERTY_FILES})
0424     foreach(_file ${_qml_files})
0425         get_filename_component(_filename ${_file} NAME)
0426         get_property(_path SOURCE ${_file} PROPERTY ${_ECM_QMLMODULE_PROPERTY_PATH})
0427 
0428         set(_file_path "${_plugin_path}/")
0429         if (NOT "${_path}" STREQUAL "")
0430             set(_file_path "${_plugin_path}/${_path}/")
0431         endif()
0432 
0433         install(FILES ${_file} DESTINATION ${ARG_DESTINATION}/${_file_path})
0434 
0435         add_custom_command(
0436             TARGET ${ARG_TARGET}
0437             POST_BUILD
0438             WORKING_DIRECTORY ${_runtime_output_dir}
0439             COMMAND ${CMAKE_COMMAND} -E make_directory ${_file_path}
0440             COMMAND ${CMAKE_COMMAND} -E copy ${_file} ${_file_path}
0441             BYPRODUCTS ${_file_path}/${_filename}
0442         )
0443     endforeach()
0444 endfunction()