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, aQt5::Coreque, al ser referenciada en la directivatarget_link_librariessera expandida a la verdadera librería, que esQt5Core. Sin embargo, esa expansión sólo es posible si se busca la librería a través defind_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.
