c++ - online - CMake: ¿Cómo configurar las dependencias Source, Library y CMakeLists.txt?
descarga cmake (1)
Tengo varios proyectos (todos compilados con CMake de la misma estructura de árbol fuente), todos usando su propia mezcla de docenas de bibliotecas de soporte.
Entonces me surgió la pregunta de cómo configurar esto correctamente en CMake. Hasta ahora, solo he encontrado CMake cómo crear correctamente dependencias entre objetivos, pero todavía estoy luchando entre configurar todo con dependencias globales (el nivel del proyecto sí lo sabe todo) o con dependencias locales (cada objetivo de subnivel solo maneja su propio dependencias).
Aquí hay un ejemplo reducido de la estructura de mi directorio y de lo que actualmente se me ocurrió al usar CMake y las dependencias locales (el ejemplo muestra solo un proyecto ejecutable App1
, pero en realidad hay más App2
, App3
, ...):
Lib
+-- LibA
+-- Inc
+-- a.h
+-- Src
+-- a.cc
+-- CMakeLists.txt
+-- LibB
+-- Inc
+-- b.h
+-- Src
+-- b.cc
+-- CMakeLists.txt
+-- LibC
+-- Inc
+-- c.h
+-- Src
+-- c.cc
+-- CMakeLists.txt
App1
+-- Src
+-- main.cc
+-- CMakeLists.txt
Lib / LibA / CMakeLists.txt
include_directories(Inc ../LibC/Inc)
add_subdirectory(../LibC LibC)
add_library(LibA Src/a.cc Inc/a.h)
target_link_libraries(LibA LibC)
Lib / LibB / CMakeLists.txt
include_directories(Inc)
add_library(LibB Src/b.cc Inc/b.h)
Lib / LibC / CMakeLists.txt
include_directories(Inc ../LibB/Inc)
add_subdirectory(../LibB LibB)
add_library(LibC Src/c.cc Inc/c.h)
target_link_libraries(LibC LibB)
App1 / CMakeLists.txt (por la facilidad de reproducirlo genero los archivos fuente / encabezado aquí)
cmake_minimum_required(VERSION 2.8)
project(App1 CXX)
file(WRITE "Src/main.cc" "#include /"a.h/"/n#include /"b.h/"/nint main()/n{/na();/nb();/nreturn 0;/n}")
file(WRITE "../Lib/LibA/Inc/a.h" "void a();")
file(WRITE "../Lib/LibA/Src/a.cc" "#include /"c.h/"/nvoid a()/n{/nc();/n}")
file(WRITE "../Lib/LibB/Inc/b.h" "void b();")
file(WRITE "../Lib/LibB/Src/b.cc" "void b() {}")
file(WRITE "../Lib/LibC/Inc/c.h" "void c();")
file(WRITE "../Lib/LibC/Src/c.cc" "#include /"b.h/"/nvoid c()/n{/nb();/n}")
include_directories(
../Lib/LibA/Inc
../Lib/LibB/Inc
)
add_subdirectory(../Lib/LibA LibA)
add_subdirectory(../Lib/LibB LibB)
add_executable(App1 Src/main.cc)
target_link_libraries(App1 LibA LibB)
Las dependencias de la biblioteca en el ejemplo anterior se ven así:
App1 -> LibA -> LibC -> LibB
App1 -> LibB
Por el momento prefiero la variante de dependencias locales, porque es más fácil de usar. Acabo de dar las dependencias en el nivel de origen con include_directories()
, en el nivel de enlace con target_link_libraries()
y en el nivel de CMake con add_subdirectory()
.
Con esto no necesita conocer las dependencias entre las bibliotecas de soporte y, con el nivel "incluye" de CMake, solo terminará con los objetivos que realmente usa. Efectivamente, podría hacer que todos los directorios y destinos incluidos sean conocidos globalmente y permitir que el compilador / enlazador solucione el resto. Pero esto parece una especie de hinchazón para mí.
También traté de tener un Lib/CMakeLists.txt
para manejar todas las dependencias en el árbol de directorios de Lib
, pero terminé teniendo un montón de comprobaciones if ("${PROJECT_NAME}" STREQUAL ...)
y el problema que pude No cree bibliotecas intermedias que agrupen objetivos sin dar al menos un archivo fuente.
Así que el ejemplo anterior es "hasta ahora muy bueno", pero arroja el siguiente error porque debe / no puede agregar un CMakeLists.txt
dos veces:
CMake Error at Lib/LibB/CMakeLists.txt:2 (add_library):
add_library cannot create target "LibB" because another target with the
same name already exists. The existing target is a static library created
in source directory "Lib/LibB".
See documentation for policy CMP0002 for more details.
Por el momento veo dos soluciones para esto, pero creo que me volví complicado.
1. Sobrescribir add_subdirectory()
para evitar duplicados
function(add_subdirectory _dir)
get_filename_component(_fullpath ${_dir} REALPATH)
if (EXISTS ${_fullpath} AND EXISTS ${_fullpath}/CMakeLists.txt)
get_property(_included_dirs GLOBAL PROPERTY GlobalAddSubdirectoryOnceIncluded)
list(FIND _included_dirs "${_fullpath}" _used_index)
if (${_used_index} EQUAL -1)
set_property(GLOBAL APPEND PROPERTY GlobalAddSubdirectoryOnceIncluded "${_fullpath}")
_add_subdirectory(${_dir} ${ARGN})
endif()
else()
message(WARNING "add_subdirectory: Can''t find ${_fullpath}/CMakeLists.txt")
endif()
endfunction(add_subdirectory _dir)
2. Agregar un "incluir guardia" a todos los subniveles CMakeLists.txt
como:
if (NOT TARGET LibA)
...
endif()
Gracias de antemano por su ayuda para resolver esto.
EDITAR: He estado probando los conceptos sugeridos por tamas.kenez y ms con algunos resultados prometedores. Los resúmenes se pueden encontrar en mis siguientes respuestas:
- estructura preferida del proyecto cmake
- CMake comparte biblioteca con múltiples ejecutables
- Haciendo que la biblioteca cmake sea accesible por otros paquetes cmake automáticamente
Agregar el mismo subdirectorio varias veces está fuera de dudas, no es cómo se espera que CMake funcione. Hay dos alternativas principales para hacerlo de una manera limpia:
Construya sus bibliotecas en el mismo proyecto que su aplicación. Prefiera esta opción para las bibliotecas en las que está trabajando activamente (mientras trabaja en la aplicación), por lo que es probable que sean editadas y reconstruidas con frecuencia. También aparecerán en el mismo proyecto IDE.
Construya sus bibliotecas en un proyecto externo ( y no me refiero a ExternalProject ). Prefiero esta opción para las bibliotecas que acaba de utilizar su aplicación, pero no está trabajando en ellas. Este es el caso para la mayoría de las bibliotecas de terceros. Tampoco desordenarán su espacio de trabajo IDE.
Método 1
- el
CMakeLists.txt
su aplicación agrega los subdirectorios de las bibliotecas (y lasCMakeLists.txt
sus libs no) - el
CMakeLists.txt
su aplicación es responsable de agregar todas las dependencias inmediatas y transitivas y agregarlas en el orden correcto - asume que agregar el subdirectorio para
libx
creará un objetivo (por ejemplo,libx
) que se puede usar fácilmente contarget_link_libraries
Como nota al margen: para las bibliotecas, es una buena práctica crear un objetivo de biblioteca con todas las funciones , es decir, uno que contenga toda la información necesaria para usar la biblioteca:
add_library(LibB Src/b.cc Inc/b.h)
target_include_directories(LibB PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Inc>)
Entonces, la ubicación de los directorios de inclusión de la biblioteca puede seguir siendo un asunto interno de la lib. Solo tendrás que hacer esto;
target_link_libraries(LibC LibB)
luego los LibB
de LibB
de LibB
también se agregarán a la compilación de LibC
. Utilice el modificador PRIVATE
si LibB
no es utilizado por los encabezados públicos de LibC
:
target_link_libraries(LibC PRIVATE LibB)
Método n. ° 2
Cree e instale sus bibliotecas en proyectos separados de CMake. Sus bibliotecas instalarán un denominado módulo de configuración que describe las ubicaciones de los encabezados y archivos de la biblioteca y también compila indicadores. El CMakeList.txt
su aplicación asume que las bibliotecas ya se han creado e instalado y que los módulos de configuración se pueden encontrar mediante el comando find_package
. Esta es toda una historia diferente, así que no entraré en detalles aquí.
Algunas notas:
- Puede mezclar # 1 y # 2 ya que en la mayoría de los casos tendrá librerías de terceros inalterables y sus propias bibliotecas en desarrollo.
- Un compromiso entre # 1 y # 2 está utilizando el módulo ExternalProject , preferido por muchos. Es como incluir los proyectos externos de sus bibliotecas (construidos en su propio árbol de compilación) en el proyecto de su aplicación. De esta manera combina las desventajas de ambos enfoques: no puede usar sus bibliotecas como objetivos (porque están en un proyecto diferente) y no puede llamar a
find_package
(porque las bibliotecas no están instaladas cuando se está configurando laCMakeLists
su aplicación ) - Una variante del n. ° 2 es construir la biblioteca en un proyecto externo, pero en lugar de instalar los artefactos, utilícelos desde sus ubicaciones de origen / compilación. Para más información sobre esto, vea el comando
export()
.