Entendiendo CMakeLists

Familiarizarse con el funcionamiento de los archivos CMakeLists.txt

CMake

En nuestro tutorial de introducción hemos usado CMake como sistema de compilación para nuestra aplicación, aunque solo hemos prestado más atención a uno de nuestros archivos CMakeLists.txt. Aquí vamos a profundizar un poco más sobre cómo funciona.

CMake es útil porque nos permite automatizar la mayoría de las cosas necesarias que hay que hacer antes de compilar.

El CMakeLists.txt raíz

Es posible que recuerde este archivo CMakeLists.txt del primer tutorial:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
cmake_minimum_required(VERSION 3.20)
project(kirigami-tutorial)

find_package(ECM 6.0.0 REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})

include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(ECMFindQmlModule)
include(ECMQmlModule)

find_package(Qt6 REQUIRED COMPONENTS
    Core
    Quick
    Test
    Gui
    QuickControls2
    Widgets
)

find_package(KF6 REQUIRED COMPONENTS
    Kirigami
    I18n
    CoreAddons
    QQC2DesktopStyle
    IconThemes
)

ecm_find_qmlmodule(org.kde.kirigami REQUIRED)

add_subdirectory(src)

install(PROGRAMS org.kde.tutorial.desktop DESTINATION ${KDE_INSTALL_APPDIR})

feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)

La primera línea, cmake_minimum_required() define la versión de CMake que se va a utilizar.

A continuación, project(kirigami-tutorial) define el nombre del proyecto.

Luego llegamos a una sección donde incluimos una serie de preferencias necesarias de CMake y de KDE usando extra-cmake-modules. Estas proporcionan un conjunto de prácticas utilidades:

  • KDEInstallDirs proporciona variables de conveniencia, como ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}, ${KDE_INSTALL_QMLDIR}, ${KDE_INSTALL_BINDIR} y ${KDE_INSTALL_LIBDIR}.
  • KDECMakeSettings proporciona cosas como CMAKE_AUTORCC ON, un objetivo uninstall que se puede usar con cmake --build build/ --target uninstall, y ENABLE_CLAZY.
  • KDECompilerSettings proporciona un estándar de C++ mínimo, indicadores del compilador (como -pedantic) y macros de buenas prácticas (como -DQT_NO_CAST_FROM_ASCII para exigir conversiones explícitas, como QStringLiteral()).
  • ECMFindQmlModule proporciona un modo para asegurar que al compilar se encuentra una dependencia QML en tiempo de ejecución.
  • ECMQmlModule proporciona órdenes de CMake como ecm_add_qml_module() y ecm_target_qml_sources().

La siguiente sección es importante, ya que especifica las dependencias necesarias durante la compilación. Veamos la primera:

13
14
15
16
17
18
19
20
21
22
23
24
25
26
find_package(Qt6 REQUIRED COMPONENTS
    Core
    Quick
    Test
    Gui
    QuickControls2
    Widgets
)

find_package(KF6 REQUIRED COMPONENTS
    Kirigami
    I18n
    CoreAddons
    QQC2DesktopStyle
  • find_package() encuentra y carga la biblioteca externa y sus componentes.
  • REQUIRED indica a CMake que salga con un error si no se puede encontrar el paquete.
  • COMPONENTS es un parámetro que precede a los componentes específicos de la infraestructura que vamos a incluir.
  • Cada palabra tras COMPONENTS hace referencia a un determinado componente de la biblioteca.

La línea de instalación indica a CMake que instale el archivo de escritorio en ${KDE_INSTALL_APPDIR}, que en Linux se traduce a $XDG_DATA_DIRS/applications, normalmente /usr/share/applications, y en Windows se traduce a C:/Program Files/${PROJECT_NAME}/bin/data/applications:

32
add_subdirectory(src)

La última línea permite que CMake liste los paquetes que ha encontrado y que haga que la compilación falle inmediatamente si se produce un error:

34
install(PROGRAMS org.kde.tutorial.desktop DESTINATION ${KDE_INSTALL_APPDIR})

Y sobre ella, add_subdirectory(src) hace que CMake se dirija al directorio src/, donde encontrará otro archivo CMakeLists.txt.

src/CMakeLists.txt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
add_executable(kirigami-hello)

ecm_add_qml_module(kirigami-hello
    URI
    org.kde.tutorial
)

target_sources(kirigami-hello
    PRIVATE
    main.cpp
)

ecm_target_qml_sources(kirigami-hello
    SOURCES
    Main.qml
)

target_link_libraries(kirigami-hello
    PRIVATE
    Qt6::Quick
    Qt6::Qml
    Qt6::Gui
    Qt6::QuickControls2
    Qt6::Widgets
    KF6::I18n
    KF6::CoreAddons
    KF6::IconThemes
)

install(TARGETS kirigami-hello ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})

Mientras el primer archivo se encargaba de los metadatos y de encontrar bibliotecas, este se encargará de las dependencias y de instalar la aplicación. Contiene las siguientes llamadas de CMake:

  • add_executable() crea el ejecutable de destino que usaremos para ejecutar nuestro proyecto.
  • ecm_add_qml_module() crea un módulo QML de destino que será accesible mediante la importación de «org.kde.tutorial».
  • target_sources() añade los archivos de código fuente C++ al ejecutable de destino.
  • ecm_target_qml_sources() añade archivos QML al módulo.
  • target_link_libraries enlaza las bibliotecas de C++ usadas en el código fuente con nuestro ejecutable. Kirigami no está incluido aquí porque estamos usando solo su módulo QML.
  • install() instala el ejecutable en el sistema.

La documentación de las dos órdenes ECM se puede encontrar en la API de extra-cmake-modules para ECMQmlModule.

La llamada a ecm_add_qml_module() se ha usado aquí para modificar el destino ejecutable del código fuente C++ tradicional y convertirlo en algo que pueda aceptar archivos QML y código fuente C++ al que se pueda acceder desde QML en lo que se denomina uso del ejecutable como destino de respaldo para un módulo QML. Esto significa que los archivos QML se ejecutan directamente como parte de la aplicación, como suele ser el caso de las aplicaciones.

También puede crear un módulo QML independiente que no use el ejecutable como destino de respaldo mediante ecm_add_qml_module(). En este caso, crearía un destino de biblioteca mediante add_library(), lo enlazaría a un destino ejecutable existente mediante target_link_libraries() y, además de instalar la biblioteca con install(), deberá finalizar el módulo QML con ecm_finalize_qml_module() para que pueda generar dos archivos: qmldir y qmltypes. Las aplicaciones QtQuick usan estos archivos para buscar módulos QML independientes.

El método para crear un módulo QML separado se ejemplifica mejor en Uso de archivos separados.

Son adiciones proporcionadas por extra-cmake-modules para hacer que el uso del registro declarativo de Qt (el reemplazo de los archivos de recursos de Qt) sea más fácil.

La documentación de las tres órdenes se puede encontrar en la API de extra-cmake-modules para ECMQmlModule.

src/components/CMakeLists.txt

En el tutorial sobre cómo dividir el código en archivos separados, se introdujo un nuevo archivo CMake para permitir módulos QML separados:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
add_library(kirigami-hello-components)

ecm_add_qml_module(kirigami-hello-components
    URI "org.kde.tutorial.components"
    GENERATE_PLUGIN_SOURCE
)

ecm_target_qml_sources(kirigami-hello-components
    SOURCES
    AddDialog.qml
    KountdownDelegate.qml
)

ecm_finalize_qml_module(kirigami-hello-components)

install(TARGETS kirigami-hello-components ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})

El requisito para que CMake lea este archivo es añadir una llamada a add_subdirectory() en el src/CMakeLists.txt que apunte a él.

Creamos un nuevo objetivo llamado kirigami-hello-components y luego lo convertimos en un módulo QML usando ecm_add_qml_module() bajo el nombre de importación org.kde.tutorial.components y añadimos los archivos QML relevantes.

La llamada a add_library() genera un nuevo objetivo llamado kirigami-hello-components. Este objetivo tendrá su propio conjunto de archivos de código fuente, archivos QML, enlazará sus propias bibliotecas, etc., aunque necesita estar enlazado al ejecutable, pero una vez que se compila, necesita estar enlazado al ejecutable creado en src/CMakeLists.txt. Esto se hace añadiendo el nombre del objetivo a la lista de bibliotecas que se enlazarán al ejecutable en target_link_libraries().

The call to ecm_add_qml_module() changes the library to allow it to accept QML files as before, but this time we need to use GENERATE_PLUGIN_SOURCE. When the executable is used as a backing target (like with kirigami-hello) it doesn't need to generate plugin code since it's built into the executable; with separate QML modules like kirigami-hello-components the plugin code is necessary.

Upstream Qt's qt_add_qml_module() by default generates a plugin together with the QML module, but KDE's ecm_add_qml_module() by default does not for backwards compatibility.

Another thing that is necessary for separate QML modules is to finalize the target. This mainly means CMake generates two files, qmldir and qmltypes, which describe the QML modules we have and exports their symbols for use in the library. They are important when installing your application so that the executable being run is able to find where the QML files for each module are, so they are automatically added to the target.

Después podrá instalar el objetivo como antes.

Next time you need to add more QML files, remember to include them in this file. C++ files that use the QML_ELEMENT keyword which we will see much later in the tutorial can also be added here using target_sources(). You can logically separate your code by creating more QML modules with different imports as needed.

Esta configuración será útil para desarrollar la mayoría de las aplicaciones de Kirigami.