CMake: ¿Cómo configurar dependencias fuente, biblioteca y CMakeLists.txt?

Tengo varios proyectos (todos comstackdos con CMake de la misma estructura de árbol fuente), todos usando su propia mezcla de docenas de bibliotecas de soporte.

Entonces surgió la pregunta de cómo configurar esto correctamente en CMake. Hasta ahora solo he encontrado CMake cómo crear correctamente dependencias entre objectives , pero todavía estoy luchando entre configurar todo con dependencias globales (el nivel del proyecto sí lo sabe todo) o con dependencias locales (cada objective de subnivel solo maneja sus propias dependencias).

Aquí hay un ejemplo reducido de la estructura de mi directorio y lo que actualmente se me ocurrió usando CMake y las dependencias locales (el ejemplo muestra solo un proyecto ejecutable, App1 , pero en realidad hay más, App2 , App3 , etc.):

 Lib +-- LibA +-- Inc +-- ah +-- Src +-- a.cc +-- CMakeLists.txt +-- LibB +-- Inc +-- bh +-- Src +-- b.cc +-- CMakeLists.txt +-- LibC +-- Inc +-- ch +-- 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/ah) target_link_libraries(LibA LibC) 

Lib / LibB / CMakeLists.txt

 include_directories(Inc) add_library(LibB Src/b.cc Inc/bh) 

Lib / LibC / CMakeLists.txt

 include_directories(Inc ../LibB/Inc) add_subdirectory(../LibB LibB) add_library(LibC Src/c.cc Inc/ch) 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 \"ah\"\n#include \"bh\"\nint main()\n{\na();\nb();\nreturn 0;\n}") file(WRITE "../Lib/LibA/Inc/ah" "void a();") file(WRITE "../Lib/LibA/Src/a.cc" "#include \"ch\"\nvoid a()\n{\nc();\n}") file(WRITE "../Lib/LibB/Inc/bh" "void b();") file(WRITE "../Lib/LibB/Src/b.cc" "void b() {}") file(WRITE "../Lib/LibC/Inc/ch" "void c();") file(WRITE "../Lib/LibC/Src/c.cc" "#include \"bh\"\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 objectives que realmente usa. Efectivamente, podría hacer que todos los directorios y destinos de inclusión se conozcan globalmente y permitir que el comstackdor / enlazador solucione el rest. 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 objectives 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í demasiado 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. Agregando un “incluir guardia” a todos los sub-niveles CMakeLists.txt s, como:

 if (NOT TARGET LibA) ... endif() 

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:

  1. 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.

  2. 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 las CMakeLists.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 objective (por ejemplo, libx ) que se puede usar fácilmente con target_link_libraries

Como nota al margen: para las bibliotecas, es una buena práctica crear un objective 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/bh) target_include_directories(LibB PUBLIC $) 

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 comstackció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 comstack 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 comstackción) en el proyecto de su aplicación. De esta manera combina las desventajas de ambos enfoques: no puede usar sus bibliotecas como objectives (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 la CMakeLists 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 / comstackción. Para más información sobre esto, vea el comando export() .