Skip to main content
Ir al contenido

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:

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

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:

36
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)

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().

La llamada a ecm_add_qml_module() cambia la biblioteca para permitirle aceptar archivos QML como antes, pero esta vez necesitamos usar GENERATE_PLUGIN_SOURCE. Cuando el ejecutable se usa como un destino de respaldo (como con kirigami-hello) no necesita generar el código del complemento, ya que está integrado en el ejecutable; con módulos QML separados, como kirigami-hello-components, el código del complemento es necesario.

De manera predeterminada, el qt_add_qml_module() de Qt genera un complemento junto al módulo QML, aunque el ecm_add_qml_module() de KDE no lo hace de forma predeterminada por compatibilidad con versiones anteriores.

Otro requisito para módulos QML independientes es finalizar el destino. Esto implica principalmente que CMake genera dos archivos: qmldir y qmltypes, que describen los módulos QML que tenemos y exportan sus símbolos para usarlos en la biblioteca. Son importantes durante la instalación de la aplicación para que el ejecutable que se lanza sea capaz de encontrar dónde están los archivos QML para cada módulo, de modo que se puedan añadir automáticamente al destino.

Después podrá instalar el objetivo como antes.

La próxima vez que necesite añadir más archivos QML, recuerde incluirlos en este archivo. Los archivos de C++ que usan la palabra clave QML_ELEMENT, que veremos posteriormente en este tutorial, también se pueden añadir aquí usando target_sources(). Se puede separar lógicamente el código creando más módulos QML con distintas importaciones, según sea necesario.

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