LTLAB
dev

CMAKE : EXPORTAR LIBRERÍAS ESTÁTICAS Y DINÁMICAS

Guía básica para exportar proyectos librerías c++ estáticas o dinámicas en sistemas GNU.

SUMARIO

INTRODUCCIÓN

En esta guía se repasarán una secuencia de instrucciones que dan la posibilidad a una librería en C++ de ser instalada en el sistema, ya sea en los repositorios por defecto del sistema (en esta guía se trara con sistemas gnu), o en una localización específica definida por el usuario.

En caso de querer instalarla como guía de sistema, lo más lógico es integrarlo en el path /usr/local, o … si el desarrollador tiene la intención de compartirlo a gran escala, es lógico integrarlo en el path /usr directamente.

En mi caso, cuando desarrollo un proyecto de software suelo separar las librerías de modelos de datos (con sus serializadores y deserializadores), y las posiciono fuera del core del programa. Sin entrar en detalles, la estructura resultante es funcionalmente similar a aquella de los proyectos compilados con catkin o colcon donde las librerías compiladas son accesibles desde una localización específica (el subdirectorio /install).

Esta guía no pretende profundizar en cmake, sino señalar una secuencia de acciones específicas para no perderse cuando se esté diseñando una librería estática o dinámica que se quiera compartir con otros repositorios, y se quiera integrar en dichos proyectos usando la directiva find_package de cmake.

DESARROLLO

Partamos de un ejemplo sencillo de fichero .cmake.

cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
project(my_models_lib LANGUAGES CXX VERSION 2.0.2)

#deps - init
find_package(comlex_lib)

set(ADDITIONAL_LIBS
      lib1
      lib2
)
#deps - end

# fuentes - init
file(GLOB_RECURSE MODEL_OBJECTS_SRC src/my_models_lib_sources/*.cpp)
# fuentes - end

# ----
include_directories(comlex_lib_INCLUDE_DIRS)


add_library(my_models
            ${MODEL_OBJECTS_SRC}
)
target_link_libraries(my_models
            ${comlex_lib_LIBRARIES}
            ${ADDITIONAL_LIBS}
)

Cualquiera con un mínimo conocimiento de cmake podrá entender que se trata de la definición de un modelo de compilación de una librería estática llamada my_models sobre una serie de códigos fuentes situados en src/..., que para su compilación necesitan a su vez de otras 3 librerías: lib1, lib2 y complex_lib que se carga vía find_package.

Paso 1 : fijar el directorio de instalación

La variable de entorno que fija el directorio de instalación es CMAKE_INSTALL_PREFIX. Si quieres hacerlo relativo al directorio donde se encuentra el CMakeLists.txt, hay que usar también la variable CMAKE_CURRENT_LIST_DIR.

set(CMAKE_INSTALL_PREFIX "${CMAKE_CURRENT_LIST_DIR}/../install")

Paso 2 : construcción de los .cmake para exportar la librería.

Este paso es bastante mecánico y sirve para crear los ficheros necesarios para que sea utilizado con la directiva find_package desde otro proyecto.

Se trata básicamente de 5 etapas que van a producir dos ficheros .cmake, el ...Targets.cmake y el ...ConfigVersion.cmake, además de indicar dónde se instalarán las librerías y programas estáticos, los include, y los .cmake.

Estos ficheros aseguran que, una vez hayamos hecho el find_package(my_models) desde el proyecto externo, se incluyan automáticamente las referencias a los include y la localización de las librerías que vamos a necesitar.

install(TARGETS my_models

        ARCHIVE   DESTINATION   lib
        INCLUDES  DESTINATION   include
)

install(TARGETS     my_models
        EXPORT      my_modelsTargets
        FILE_SET    HEADERS
        ARCHIVE     DESTINATION       lib
        INCLUDES    DESTINATION       include
)

install(EXPORT      my_modelsTargets
        FILE        my_modelsTargets.cmake
        DESTINATION lib/cmake
)

install(DIRECTORY   ${CMAKE_CURRENT_LIST_DIR}/include/<directory>
        DESTINATION include)


include(CMakePackageConfigHelpers)

write_basic_package_version_file(
  "my_modelsConfigVersion.cmake"
  VERSION ${my_models_VERSION}
  COMPATIBILITY AnyNewerVersion
)

Es importante señalar que ésto sirve para librerías estáticas, definidas con add_library(<nombre-libreria> ...), y no con librerías dinámicas definidas con `add_library(<nombre-librería> SHARED …).

En caso de querer compartir una librería dinámica, se agrega el TARGET LIBRARY. Para exportar los ejecutables/binarios: RUNTIME.

install(TARGETS geo 
    EXPORT geoTargets
    FILE_SET HEADERS
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
    RUNTIME DESTINATION bin
    INCLUDES DESTINATION include)

Paso 3 : creación del fichero ...Config.cmake

El fichero my_modelsConfig.cmake, que es el que realmente busca la directiva find_package lo debemos crear nosotros a mano

Sin embargo también podemos automatizar este proceso dentro del CMakeLists.cmake

Al principio del proceso de interpretación del CMakeLists.cmake indicamos que queremos crear un fichero, my_modelsConfig.cmake, y se agregan las definiciones básicas para otras macros que vamos a usar:

file(WRITE my_modelsConfig.cmake "include(CMakeFindDependencyMacro) \n")

El \n, efectivamente, agrega el retorno de carro. De otra forma, todo lo que se agrege después se agregaría de seguido, provocando un fichero inservible.

Es importante agregar en este fichero las directivas find_package que cargue todas las definiciones sobre dependencias complejas.

Por dependencias complejas me refiero a aquellas dependencias cuya referencia a la librería se hace vía namespaces (espacios de nombres…)

Así pasa con librerías por ejemplo Qt… Para cargar la dependencia de Qt5Core es necesario hacer un find_package(Qt5 REQUIRED Core). Sin embargo, la variable creada ${Qt5Core_LIBRARIES} hará referencia, entre otros, a Qt5::Core que, al ser referenciada en la directiva target_link_libraries sera expandida a la verdadera librería, que es Qt5Core. Sin embargo, esa expansión sólo es posible si se busca la librería a través de find_package.

Como explicación, todo ésto carece de perspectiva científica, pero como señalo en el principio del post, mi único interés es compartir estas ideas desde un punto de vista práctico para dar una cierta comprensión a la escritura de las instrucciones.

Lo que necesitamos es agregar en el fichero my_modelsConfig.cmake las cargas a todos los paquetes cargados via find_package:

file(APPEND my_modelsConfig.cmake "find_package(complex_lib) \n")

… y después de incluir esas directivas “cerraremos” el fichero con un:

file(APPEND my_modelsConfig.cmake "include(\${CMAKE_CURRENT_LIST_DIR}/my_modelsTargets.cmake) \n")

… para finalmente indicarle al instalador que ese fichero debe también ser copiado a la lista de los .cmake:

install(FILES "my_modelsConfig.cmake" "${CMAKE_CURRENT_LIST_DIR}/my_ModelsConfig.cmake"
        DESTINATION lib/cmake)

INSTALACIÓN

La instalación se realiza con un make install clásico. Precedido del sudo si es necesario…

El make install creará un manifiesto: install_manifest.txt que contiene la lista de todos los ficheros instalados. Para desinstalar, será suficiente con xargs rm < install_manifest.txt.

EJEMPLO COMPLETO:

El fichero CMakeLists.txt con todos los añadidos para crear los instaladores sería:

cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
project(my_models_lib LANGUAGES CXX VERSION 2.0.2)

file(WRITE my_modelsConfig.cmake "include(CMakeFindDependencyMacro) \n")

#deps - init
find_package(comlex_lib)

file(APPEND my_modelsConfig.cmake "find_package(complex_lib) \n")

include_directories(comlex_lib_INCLUDE_DIRS)


set(ADDITIONAL_LIBS
      lib1
      lib2
)
#deps - end

# fuentes - init
file(GLOB_RECURSE MODEL_OBJECTS_SRC src/my_models_lib_sources/*.cpp)
# fuentes - end

# ---- -------
add_library(my_models
            ${MODEL_OBJECTS_SRC}
)
target_link_libraries(my_models
            ${comlex_lib_LIBRARIES}
            ${ADDITIONAL_LIBS}
)

install(TARGETS my_models

        ARCHIVE   DESTINATION   lib
        INCLUDES  DESTINATION   include
)

install(TARGETS     my_models
        EXPORT      my_modelsTargets
        FILE_SET    HEADERS
        ARCHIVE     DESTINATION       lib
        INCLUDES    DESTINATION       include
)

install(EXPORT      my_modelsTargets
        FILE        my_modelsTargets.cmake
        DESTINATION lib/cmake
)

install(DIRECTORY   ${CMAKE_CURRENT_LIST_DIR}/include/<directory>
        DESTINATION include)


include(CMakePackageConfigHelpers)

write_basic_package_version_file(
  "my_modelsConfigVersion.cmake"
  VERSION ${my_models_VERSION}
  COMPATIBILITY AnyNewerVersion
)


file(APPEND my_modelsConfig.cmake "include(\${CMAKE_CURRENT_LIST_DIR}/my_modelsTargets.cmake) \n")
install(FILES "my_modelsConfig.cmake" "${CMAKE_CURRENT_LIST_DIR}/my_ModelsConfig.cmake"
        DESTINATION lib/cmake)

Referencia desde proyecto externo.

Desde el proyecto externo será suficiente con hacer una referencia find_package a la librería my_models, indicando la localización de los .cmake producidos en la instalación de my_models, si estos .cmakes han sido instalados fuera de las rutas de instalación por defecto (/usr, /usr/local, …)

find_package(my_models REQUIRED PATHS ${CMAKE_INSTALL_PREFIX}/lib/cmake)

El target_link_libraries del proyecto-usuario deberá (por supuesto) incluir my_models.

Hi, I’m ricardogonalm

Leave a Reply

Your email address will not be published. Required fields are marked *