CMake en proyectos Qt

Hola,

Hoy os voy a hablar de algo de lo que no tengo ni idea pero que estoy trabajando en aprender porque ya me he encontrado con ello en varias ocasiones. Se trata de CMake y su uso en proyectos con librerías Qt.

La primera pregunta que os tiene que venir a la cabeza es la siguiente:
¿Por qué en concreto en proyectos Qt?

La respuesta no es del todo fácil pero sí que es divertida. El tema es que Qt es una cosa muy molona que necesita unos “asuntos especiales” para poder compilarse correctamente. No se comporta como un proyecto en C o C++ cualquiera. En esta entrada voy a intentar mencionar un poco cuales son esos casos aunque no pueda profundizar lo que me gustaría porque, como digo más arriba, no tengo ni idea de esto. Sólo soy capaz de hacer que funcione, no conozco el funcionamiento interno (esta entrada es una excusa para investigar y aprender 😉 ).

En principio, CMake es una herramienta que crea MakeFiles. Ya hablé de ellos en una entrada anterior (si no te acuerdas échale un ojo aquí) pero, resumiendo, los MakeFiles son recetas que sirven para compilar código de forma automatizada. Si nuestro proyecto es muy complicado y depende de muchas librerías externas, estas recetas se complican sobremanera porque hay que comprobar que la librería esté instalada, linkarla, etc. Para evitar eso tenemos este tipo de herramientas, a las que se les dan unas directrices y ellas crean los MakeFiles de forma automática. Con CMake, los makefiles que generamos son nativos para el equipo en el que lo usamos por lo que es multiplataforma (su nombre viene de Cross-Platform-Make). Sabiendo esto, entendemos que antes de compilar necesitamos una etapa más: configurar.

A nivel práctico, tenemos que crear unos archivos llamados CMakeLists.txt con nuestras órdenes de búsqueda de librerías, añadido de código fuente, etc. Y luego se ejecutará usando el comando cmake. Este comando, construirá los makefiles necesarios y con ellos podremos compilar e instalar del modo habitual.

Lo habréis visto a menudo utilizando un script llamado configure en algún manual de instalación, en el que te piden que sigas este proceso: configure, make, make install.

CMake entraría en el primer trozo, sería algo así: cmake, make, make install. Simplemente que el configure es la primera etapa cuando se usa Autotools y cmake es la primera etapa cuando se usa CMake. El razonamiento es el mismo, sólo cambia la herramienta. [1]

Bueno, vamos a por Qt ahora. Qt necesita un tratamiento especial porque utiliza unos trucos sucios supermolones. Los que habéis programado algo en Qt sabéis que conecta señales a slots en tiempo de ejecución, entre otras cosas divertidas. Para esto necesita de un mecanismo especial que le permita leer características de los objetos en tiempo de ejecución. Este mecanismo se llama meta-object system y aporta dos características especiales: el mecanismo de señales y slots y la introspección.

La introspección sirve para obtener meta-información de los objetos en tiempo de ejecución, como el nombre de la clase y una lista de señales y slots. También soporta un sistema de propiedades y la internacionalización, entre otras cosas. C++ no permite hacer esto por defecto, por lo que Qt necesita tirar de una herramienta extra, llamada MOC, con la que parsea las clases definidas como Q_OBJECTs y hace que la información esté disponible mediante unas funciones de C++. El sistema MOC es puro C++, por lo que puede compilarse de forma normal.

Este mecanismo funciona de la siguiente manera:

Busca las clases definidas con la macro Q_OBJECT e implementa las funciones necesarias para ellas, una lista de meta-información, etc creando archivos que se llaman como nuestras clases pero con moc. Por ejemplo, para mainwindow.h creará moc_mainwindow.cxx y un archivo moc_mainwindow_parameters (que, sinceramente, no sé para qué sirve aunque el nombre nos puede dar una pista). Si abrimos el archivo moc_mainwindow.cxx veremos lo siguiente en la cabecera (por supuesto, podemos leer el propio archivo, es C++ normal y corriente):

/****************************************************************************
** Meta object code from reading C++ file 'mainwindow.h'
**
** Created by: The Qt Meta Object Compiler version 63 (Qt 4.8.6)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/

Al compilar, hará como que hemos hecho nosotros esos archivos y listo, compilará perfecto.

Además de esto, si usamos el diseñador (Qt-Designer o Qt-Creator, depende de la versión), que es un editor WYSIWYG para crear interfaces gráficas, necesitaremos que de ellas nos genere el equivalente en C++, por lo que también hay que gestionar esa parte. En este caso, tomará los archivos .ui que tengamos (que son un XML con la explicación de cómo es nuestra interfaz) y construirá archivos .h que empiecen por ui. Por ejemplo, si la ventana principal se llama mainwindow.ui, creará ui_mainwindow.h. Si abrimos el archivo ui_mainwindow.h veremos lo siguiente (el archivo generado también es C++ normal y corriente):

/********************************************************************************
** Form generated from reading UI file 'mainwindow.ui'
**
** Created by: Qt User Interface Compiler version 4.8.6
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/

Otro punto importante es el uso de archivos de recursos. Estos archivos sirven para dar una lista de archivos que se usan en el proyecto pero que no son código fuente. Pueden ser iconos, imágenes, audio, etc. Los archivos de recursos tienen la extensión .qrc y simplemente sirven para tener ordenados los archivos que se utilizan y que no nos liemos a meter paths.

El tema es que, para poder controlar estas cositas, hay que pasar primero por una etapa de configuración, que es la etapa que haremos con CMake. En principio, Qt tiene una herramienta propia para encargarse de estas cosas, llamada QMAKE. El sistema que utiliza es muy sencillo de utilizar y funciona muy bien, siguiendo la explicación de antes, la forma de instalar un programa con esto sería: qmake, make, make install. Simplemente hay que crear un archivo con la extensión .pro y añadirle la información necesaria, la orden qmake lo parsea y actúa en consecuencia. Aquí podéis leer acerca de ellos.

Si nosotros queremos cambiar esa primera etapa por CMake tendremos que hacer que CMake sea capaz de gestionar los moc, los archivos .ui y los .qrc (realmente no es cambiar, CMake usará qmake por debajo, pero no tendremos que crear archivos .pro). Para ello hay un par de órdenes que se pueden usar. Cuando iba a escribir esta entrada, iba a hablar de las órdenes: QT4_WRAP_CPP, QT4_WRAP_UI y QT4_ADD_RESOURCES y sus equivalentes en Qt5, que simplemente es cambiarles el nombre. Cada una de ellas sigue el procedimiento que he explicado más arriba, la primera crea los llama a MOC cuando lo necesita, la segunda crea los archivos .h desde los .ui y la tercera gestiona los .qrc.

Sin embargo, ahora (a partir de CMake 2.8.11) hay otras macros muy molonas que permiten hacer esto sin tener que hacerlo a mano. Simplemente con avisarle ya haría todo el trabajo sucio, seteando unas variables booleanas a ON:

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)

Las he probado y tiran bien y el resultado es el mismo que utilizando las anteriores.

Esto sirve tanto para Qt4 como para Qt5.

Ejemplos y el caso KDevelop:

Me he puesto a investigar un poco sobre esto porque he creado un proyecto desde la plantilla de KDevelop 4.7. Éste me ha creado un CMake que estaba un poco anticuado y me ha hecho un include del estilo de: “#include “mainwindow.moc”” al final de los archivos .cpp de mi código fuente. No estoy acostumbrado a ver esto así que he estado investigando para ver cómo hay que hacerlo ahora. Éste es el CMakeLists.txt que me genera al crear un proyecto llamado TESTKDEVELOP:

cmake_minimum_required(VERSION 2.6)
project(testkdevelop)
find_package(Qt4 REQUIRED)
include_directories(${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})
set(TESTKDEVELOP_SRCS TESTKDEVELOP.cpp main.cpp)

qt4_automoc(${TESTKDEVELOP_SRCS})
add_executable(testkdevelop ${TESTKDEVELOP_SRCS})
target_link_libraries(testkdevelop ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY})
install(TARGETS testkdevelop RUNTIME DESTINATION bin)

Éste sería el mismo CMakeLists.txt usando los Qt4_wrap_cpp, pero con esto no se necesita el #include “archivo.moc” en los archivos .cpp. Fijáos que he dejado comentadas varias líneas porque no estoy usando ninguna UI ni ningún qrc, pero si quisierais usarlas simplemente tendríais que quitar el comentario:

cmake_minimum_required(VERSION 2.6)
project(testkdevelop)
find_package(Qt4 REQUIRED)

include_directories(${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})

set(TESTKDEVELOP_SRCS TESTKDEVELOP.cpp main.cpp)
SET(TESTKDEVELOP_HEADERS TESTKDEVELOP.h)
#SET(TESTKDEVELOP_UIS src/mainwindow.ui)
#SET(TESTKDEVELOP_RESOURCES images.qrc)

QT4_WRAP_CPP(TESTKDEVELOP_HEADERS_MOC ${TESTKDEVELOP_HEADERS})
#  QT4_WRAP_UI(TESTKDEVELOP_UIS_HEADERS ${TESTKDEVELOP_UIS})
#  QT4_ADD_RESOURCES(TESTKDEVELOP_RESOURCES_RCC ${TESTKDEVELOP_RESOURCES})

include_directories( ${CMAKE_CURRENT_BINARY_DIR})

add_executable(testkdevelop ${TESTKDEVELOP_SRCS} 
    ${TESTKDEVELOP_HEADERS_MOC} 
    ${TESTKDEVELOP_UIS_HEADERS}
    ${TESTKDEVELOP_RESOURCES_RCC})

    target_link_libraries(testkdevelop ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY})

install(TARGETS testkdevelop RUNTIME DESTINATION bin)

Y este sería el mismo CMakeLists.txt usando lo último que he explicado. En este caso tampoco se necesita el uso de los includes mencionados. Nótese el cambio en el cmake_minimum_required:

cmake_minimum_required(VERSION 2.8.11)
project(testkdevelop)
find_package(Qt4 REQUIRED)

include_directories(${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)

set(TESTKDEVELOP_SRCS TESTKDEVELOP.cpp main.cpp)

include_directories( ${CMAKE_CURRENT_BINARY_DIR})

add_executable(testkdevelop ${TESTKDEVELOP_SRCS})

    target_link_libraries(testkdevelop ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY})

install(TARGETS testkdevelop RUNTIME DESTINATION bin)

Por supuesto, estos ejemplos son cambios sobre el CMakeLists.txt que KDevelop te genera por defecto. No conozco todas las funciones que se usan así que es posible que se me haya colado algo. Sólo os puedo decir que a mí me funciona de las 3 maneras y tengo la versión de CMake 3.0.2.

Por supuesto, me quedo con la última, cuanto más automático sea todo mejor. 😉

Y esto es un poco todo el tema. Os dejo un par de links interesantes sobre lo que he hablado:

Y algún link un poco friki:

  • CopperSpice: es un proyecto derivado de Qt4.8 que ha eliminado el Meta-Object System de la librería. De esta forma, se compila más rápido, se pueden hacer más cosas que el moc no permite, por ejemplo, templates, y se pueden cazar más fallos en el momento de la compilación:
    http://www.copperspice.com/index.html
  • Por qué en KDE se usa CMake. Esto es interesante porque como KDE está basado en Qt y usa CMake, es casi compatible con otros sistemas. Se está trabajando en ello, hay algunas librerías de KDE que se pueden usar en windows, por ejemplo.
    https://techbase.kde.org/Development/Tutorials/CMake#Why_use_CMake_.3F

Pues creo que ya tenéis lectura para un rato… Me lo he pasado realmente bien investigando un poco sobre el tema. En algún momento me dará por probar CopperSpice, ya os contaré qué tal me ha ido… o no.

Espero que os haya parecido interesante y os haya animado a investigar.

Me despido hasta la próxima.

¡Un abrazo!


[1] Cosas concretas de CMake:

En la wikipedia dice, muy acertadamente:

CMake es una herramienta multiplataforma de generación o automatización de código. El nombre es una abreviatura para “cross platform make” (make multiplataforma).

[…]

CMake es una suite separada y de más alto nivel que el sistema make común de Unix, siendo similar a las autotools.

CMake es una familia de herramientas diseñada para construir, probar y empaquetar software. CMake se utiliza para controlar el proceso de compilación del software usando ficheros de configuración sencillos e independientes de la plataforma. Cmake genera makefiles nativos y espacios de trabajo que pueden usarse en el entorno de desarrollo deseado.

[…]

CMake soporta la generación de ficheros para varios sistemas operativos, lo que facilita el mantenimiento y elimina la necesidad de tener varios conjuntos de ficheros para cada plataforma.

El proceso de construcción se controla creando uno o más ficheros CMakeLists.txt en cada directorio (incluyendo subdirectorios). Cada CMakeLists.txt consiste en uno o más comandos. Cada comando tiene la forma COMANDO (argumentos…) donde COMANDO es el nombre del comando, y argumentos es una lista de argumentos separados por espacios. CMake provee comandos predefinidos y definidos por el usuario.

Normalmente para compilar código fuente de este estilo se hace así, en linux, lo pongo comando por comando:

user@machine:.../proyecto$ mkdir build
user@machine:.../proyecto$ cd build
user@machine:.../proyecto/build$ cmake ..
...
user@machine:.../proyecto/build$ make
...
user@machine:.../proyecto/build$ make install

Al ejecutar cmake, nos genera un montón de archivos, por eso, si lo hacemos desde la carpeta build, nos los dejará allí y no nos ensuciará el código fuente. Si queremos limpiarlo de nuevo, con borrar la carpeta build será suficiente.

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s