Organización de proyectos en C++ (con gtest, cmake y doxygen)


Soy nuevo en la programación en general, así que decidí que comenzaría haciendo una clase vectorial simple en C++. Sin embargo, me gustaría tener buenos hábitos desde el principio en lugar de intentar modificar mi flujo de trabajo más adelante.

Actualmente solo tengo dos archivos vector3.hpp y vector3.cpp. Este proyecto comenzará a crecer lentamente (haciéndolo mucho más una biblioteca de álgebra lineal general) a medida que me familiarice con todo, por lo que me gustaría adoptar un diseño de proyecto "estándar" para hacer vida más fácil después. Así que después de mirar alrededor he encontrado dos formas de organizar archivos hpp y cpp, la primera es:

project
└── src
    ├── vector3.hpp
    └── vector3.cpp

Y el segundo es:

project
├── inc
│   └── project
│       └── vector3.hpp
└── src
    └── vector3.cpp

¿Qué recomendarías y por qué?

En segundo lugar, me gustaría usar el Marco de pruebas de Google C++ para realizar pruebas unitarias de mi código, ya que parece bastante fácil de usar. ¿Sugieres empaquetar esto con mi código, por ejemplo en una carpeta inc/gtest o contrib/gtest? Si está incluido, ¿sugiere usar el script fuse_gtest_files.py para reducir la número o archivos, o dejarlo como está? Si no se incluye, ¿cómo se maneja esta dependencia?

Cuando se trata de escribir pruebas, ¿cómo están generalmente organizadas? Estaba pensando en tener un archivo cpp para cada clase (test_vector3.cpp por ejemplo) pero todo compilado en un binario para que todos puedan ejecutarse juntos fácilmente?

Dado que la biblioteca gtest generalmente se construye usando cmake y make, estaba pensando que tendría sentido que mi proyecto también se construya de esta manera. Si decidiera usar el siguiente diseño del proyecto:

├── CMakeLists.txt
├── contrib
│   └── gtest
│       ├── gtest-all.cc
│       └── gtest.h
├── docs
│   └── Doxyfile
├── inc
│   └── project
│       └── vector3.cpp
├── src
│   └── vector3.cpp
└── test
    └── test_vector3.cpp

¿Cómo tendría que verse el CMakeLists.txt para que pueda construir solo la biblioteca o la biblioteca y las pruebas? También he visto bastantes proyectos que tienen un directorio build y un directorio bin. ¿La compilación ocurre en el directorio de compilación y luego los binarios se trasladan al directorio bin? ¿Los binarios para las pruebas y la biblioteca vivirían en el mismo lugar? O tendría más sentido estructurarlo de la siguiente manera:

test
├── bin
├── build
└── src
    └── test_vector3.cpp

I también me gustaría usar doxygen para documentar mi código. ¿Es posible hacer que esto se ejecute automáticamente con cmake y make?

Lo siento por tantas preguntas, pero no he encontrado un libro sobre C++ que responda satisfactoriamente a este tipo de preguntas.

Author: A.L, 2012-11-23

4 answers

Los sistemas de compilación de C++ son un poco de arte negro y cuanto más antiguo es el proyecto las cosas más extrañas que se pueden encontrar por lo que no es de extrañar que un montón de preguntas. Voy a tratar de caminar a través de las preguntas una por una y mencionar algunas cosas generales con respecto a la construcción de bibliotecas de C++.

Separando encabezados y archivos cpp en directorios. Esto es sólo esencial si está construyendo un componente que se supone que debe usarse como una biblioteca en lugar de una aplicación real. Sus encabezados son el base para que los usuarios interactúen con lo que usted ofrece y debe ser instalar. Esto significa que tienen que estar en un subdirectorio (nadie quiere muchos encabezados que terminan en el nivel superior /usr/include/) y su las cabeceras deben poder incluirse con tal configuración.

└── prj
 ├── include
 │   └── prj
 │       ├── header2.h
 │       └── header.h
 └── src
     └── x.cpp

Funciona bien, porque incluir rutas de trabajo y se puede utilizar fácil globbing para objetivos de instalación.

Agrupar dependencias: Creo que esto depende en gran medida de la capacidad de el sistema de compilación para localizar y configurar dependencias y cómo dependiente de su código en una sola versión. También depende de cómo able sus usuarios son y lo fácil que es la dependencia de instalar en su plataforma. CMake viene con un script find_package para Google Prueba. Esto hace las cosas mucho más fáciles. Yo iría con la agrupación sólo cuando sea necesario y evitar lo contrario.

Cómo construir: Evite las compilaciones en el código fuente. CMake hace a partir de compilaciones de código fuente fácil y hace la vida mucho más fácil.

Supongo que también quieres usar CTest para ejecutar pruebas para su sistema (it también viene con soporte incorporado para GTest). Una decisión importante para el diseño del directorio y la organización de la prueba serán: subproyectos? Si es así, necesita un poco más de trabajo al configurar listas de CMakeLists y debe dividir sus subproyectos en subdirectorios, cada uno con su archivos propios include y src. Tal vez incluso su propio doxygen corre y salidas (combinar múltiples proyectos doxygen es posible, pero no es fácil o bonito).

Terminarás con algo así:

└── prj
    ├── CMakeLists.txt <-- (1)
    ├── include
    │   └── prj
    │       ├── header2.hpp
    │       └── header.hpp
    ├── src
    │   ├── CMakeLists.txt <-- (2)
    │   └── x.cpp
    └── test
        ├── CMakeLists.txt <-- (3)
        ├── data
        │   └── testdata.yyy
        └── testcase.cpp

Donde

  • (1) configura dependencias, especificaciones de la plataforma y rutas de salida
  • (2) configura la biblioteca que vas a construir
  • (3) configura los ejecutables de prueba y los casos de prueba

En caso de que tenga subcomponentes, sugeriría agregar otra jerarquía y usar el árbol anterior para cada subproyecto. Entonces las cosas se ponen complicadas, porque necesitas decidir si los subcomponentes buscan y configuran sus dependencias o si haces eso en el nivel superior. Esto debería decidirse caso por caso.

Doxygen: Después de que lograste pasar por la danza de configuración de doxygen, es trivial usar CMake add_custom_command para añadir un doc target.

Así es como terminan mis proyectos y he visto algunos proyectos muy similares, pero por supuesto esto no es una cura para todo.

Addendum En algún momento querrá generar un config.hpp archivo que contiene una versión definir y tal vez una definir a algunos versión identificador de control (un hash de Git o número de revisión SVN). CMake ha módulos para automatizar la búsqueda de esa información y generar file. Puede usar configure_file de CMake para reemplazar variables en un archivo de plantilla con variables definidas dentro del CMakeLists.txt.

Si está creando bibliotecas, también necesitará una definición de exportación para obtener la diferencia entre los compiladores de la derecha, por ejemplo, __declspec en MSVC y visibility atributos en GCC/clang.

 69
Author: pmr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-23 01:06:51

Como iniciador, hay algunos nombres convencionales para directorios que no puede ignorar, estos se basan en la larga tradición con el sistema de archivos Unix. Estos son:

trunk
├── bin     : for all executables (applications)
├── lib     : for all other binaries (static and shared libraries (.so or .dll))
├── include : for all header files
├── src     : for source files
└── doc     : for documentation

Probablemente sea una buena idea atenerse a este diseño básico, al menos en el nivel superior.

Acerca de la división de los archivos de cabecera y los archivos de origen (cpp), ambos esquemas son bastante comunes. Sin embargo, tiendo a preferir mantenerlos juntos, simplemente es más práctico en las tareas del día a día tener los archivos juntos. Además, cuando todo el código está bajo una carpeta de nivel superior, es decir, la carpeta trunk/src/, puede notar que todas las otras carpetas (bin, lib, include, doc, y tal vez alguna carpeta de prueba) en el nivel superior, además del directorio "build" para una compilación fuera de código, son carpetas que no contienen nada más que archivos que se generan en el proceso de compilación. Y por lo tanto, solo la carpeta src necesita ser respaldada, o mucho mejor, mantenida bajo un sistema / servidor de control de versiones (como Git o SVN).

Y cuando se trata de instalar sus archivos de cabecera en el sistema de destino (si desea distribuir eventualmente su biblioteca), bueno, CMake tiene un comando para instalar archivos (crea implícitamente un destino "install", para hacer "make install") que puede usar para poner todos los encabezados en el directorio /usr/include/. Solo uso la siguiente macro cmake para este propósito:

# custom macro to register some headers as target for installation:
#  setup_headers("/path/to/header/something.h" "/relative/install/path")
macro(setup_headers HEADER_FILES HEADER_PATH)
  foreach(CURRENT_HEADER_FILE ${HEADER_FILES})
    install(FILES "${SRCROOT}${CURRENT_HEADER_FILE}" DESTINATION "${INCLUDEROOT}${HEADER_PATH}")
  endforeach(CURRENT_HEADER_FILE)
endmacro(setup_headers)

Donde SRCROOT es una variable cmake que configuro en la carpeta src, y {[6] } es una variable cmake que configuro en los encabezados tienen que irse. Por supuesto, hay muchas otras maneras de hacer esto, y estoy seguro de que mi manera no es la mejor. El punto es que no hay razón para dividir los encabezados y las fuentes solo porque solo los encabezados necesitan ser instalados en el sistema de destino, porque es muy fácil, especialmente con CMake (o CPack), elegir y configurar los encabezados para ser instalados sin tener que tenerlos en un directorio separado. Y esto es lo que he visto en la mayoría de las bibliotecas.

Cita: En segundo lugar I me gustaría usar el Marco de pruebas de Google C++ para realizar pruebas unitarias de mi código, ya que parece bastante fácil de usar. ¿Sugiere incluir esto con mi código, por ejemplo, en una carpeta" inc/gtest "o" contrib/gtest"? Si está incluido, ¿sugiere usar el fuse_gtest_files.py script para reducir el número o archivos, o dejarlo como está? Si no se incluye, ¿cómo se maneja esta dependencia?

No agrupe dependencias con su biblioteca. Esta es generalmente una idea bastante horrible, y siempre la odio cuando estoy atascado tratando de construir una biblioteca que hizo eso. Debería ser tu último recurso, y ten cuidado con las trampas. A menudo, las personas agrupan dependencias con su biblioteca ya sea porque se dirigen a un entorno de desarrollo terrible (por ejemplo, Windows), o porque solo admiten una versión antigua (obsoleta) de la biblioteca (dependencia) en cuestión. El principal escollo es que su dependencia incluida podría entrar en conflicto con las versiones ya instaladas de la misma biblioteca / aplicación (por ejemplo, que incluye gtest, pero la persona que intenta construir su biblioteca ya tiene una versión más nueva (o más antigua) de gtest ya instalada, entonces los dos podrían chocar y darle a esa persona un dolor de cabeza muy desagradable). Así que, como dije, hazlo bajo tu propio riesgo, y yo diría que solo como último recurso. Pedir a la gente que instale algunas dependencias antes de poder compilar su biblioteca es un mal mucho menor que tratar de resolver los conflictos entre sus dependencias agrupadas y las instalaciones existentes.

Cita: Cuando se en cuanto a las pruebas de escritura, ¿cómo se organizan en general? Estaba pensando en tener un archivo cpp para cada clase (test_vector3.cpp por ejemplo) pero todos compilados en un binario para que todos puedan ejecutarse juntos fácilmente?

Un archivo cpp por clase (o pequeño grupo cohesivo de clases y funciones) es más habitual y práctico en mi opinión. Sin embargo, definitivamente, no los compile todos en un binario solo para que "todos puedan ejecutarse juntos". Es una muy mala idea. Generalmente, cuando se trata de codificación, desea dividir las cosas tanto como sea razonable hacerlo. En el caso de las pruebas unitarias, no querrás que un binario ejecute todas las pruebas, porque eso significa que cualquier pequeño cambio que hagas a cualquier cosa en tu biblioteca es probable que cause una recompilación casi total de ese programa de prueba unitaria, y eso es solo minutos / horas perdidos esperando la recompilación. Simplemente apégate a un esquema simple: 1 unidad = 1 unidad-programa de prueba. A continuación, utilice un script o un marco de pruebas unitarias (como gtest y/o CTest) para ejecutar todos los programas de prueba e informar a las tasas de fracaso / éxito.

Cita: Dado que la biblioteca gtest generalmente se construye usando cmake y make, estaba pensando que tendría sentido que mi proyecto también se construya de esta manera. Si he decidido utilizar el siguiente diseño del proyecto:

Preferiría sugerir este diseño:

trunk
├── bin
├── lib
│   └── project
│       └── libvector3.so
│       └── libvector3.a        products of installation / building
├── docs
│   └── Doxyfile
├── include
│   └── project
│       └── vector3.hpp
│_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
│
├── src
│   └── CMakeLists.txt
│   └── Doxyfile.in
│   └── project                 part of version-control / source-distribution
│       └── CMakeLists.txt
│       └── vector3.hpp
│       └── vector3.cpp
│       └── test
│           └── test_vector3.cpp
│_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
│
├── build
└── test                        working directories for building / testing
    └── test_vector3

Algunas cosas para notar aquí. Primero, los subdirectorios de su directorio src deben reflejar el subdirectorios de su directorio de inclusión, esto es solo para mantener las cosas intuitivas (también, trate de mantener su estructura de subdirectorios razonablemente plana (superficial), porque el anidamiento profundo de carpetas es a menudo más de una molestia que cualquier otra cosa). En segundo lugar, el directorio "include" es solo un directorio de instalación, sus contenidos son solo los encabezados que se seleccionan del directorio src.

En tercer lugar, el sistema CMake está destinado a ser distribuido sobre los subdirectorios fuente, no como uno Listas de fabricantes.archivo txt en el nivel superior. Esto mantiene las cosas locales ,y eso es bueno (en el espíritu de dividir las cosas en piezas independientes). Si agrega una nueva fuente, un nuevo encabezado o un nuevo programa de prueba, todo lo que necesita es editar una pequeña y simple lista de CMakeLists.archivo txt en el subdirectorio en cuestión, sin afectar nada más. Esto también le permite reestructurar los directorios con facilidad (las listas de CMakeLists son locales y están contenidas en los subdirectorios que se mueven). Las mejores listas de CMakeLists debe contener la mayoría de las configuraciones de nivel superior, como configurar directorios de destino, comandos personalizados (o macros) y buscar paquetes instalados en el sistema. Las listas de CMAKEL de nivel inferior deben contener solo listas simples de encabezados, fuentes y fuentes de pruebas unitarias, y los comandos cmake que los registran en los destinos de compilación.

Cita: ¿Cómo serían las listas de CMakeLists?txt tiene que mirar para que pueda construir solo la biblioteca o la biblioteca y el pruebas?

La respuesta básica es que CMake le permite excluir específicamente ciertos destinos de "all" (que es lo que se construye cuando escribe "make"), y también puede crear paquetes específicos de destinos. No puedo hacer un tutorial de CMake aquí, pero es bastante sencillo descubrirlo por ti mismo. En este caso específico, sin embargo, la solución recomendada es, por supuesto, usar CTest, que es solo un conjunto adicional de comandos que puede usar en los archivos CMakeLists para registrar un número de objetivos (programas) que se marcan como pruebas unitarias. Por lo tanto, CMake pondrá todas las pruebas en una categoría especial de compilaciones, y eso es exactamente lo que pidió, por lo tanto, problema resuelto.

Cita: También he visto bastantes proyectos que tienen un anuncio de compilación de un directorio bin. ¿La compilación ocurre en el directorio de compilación y luego los binarios se trasladan al directorio bin? ¿Los binarios para las pruebas y la biblioteca vivirían en el mismo lugar? ¿O tendría más sentido estructurar es como sigue:

Tener un directorio de compilación fuera de la fuente (compilación"fuera de la fuente") es realmente lo único cuerdo que se puede hacer, es el estándar de facto en estos días. Así que, definitivamente, tener un directorio de "construcción" separado, fuera del directorio de código fuente, al igual que la gente de CMake recomienda, y como todos los programadores que he conocido hace. En cuanto al directorio bin, bueno, eso es una convención, y probablemente sea una buena idea apegarse a él, como dije al principio de este post.

Cita: También me gustaría usar doxygen para documentar mi código. ¿Es posible hacer que esto se ejecute automáticamente con cmake y make?

Sí. Es más que posible, es impresionante. Dependiendo de lo elegante que desee obtener, hay varias posibilidades. CMake tiene un módulo para Doxygen (es decir, find_package(Doxygen)) que le permite registrar destinos que ejecutarán Doxygen en algunos archivos. Si desea hacer cosas más elegantes, como actualizar el número de versión en el Doxyfile, o introducir automáticamente un sello de fecha / autor para los archivos fuente y así sucesivamente, todo es posible con un poco de CMake kung-fu. Generalmente, hacer esto implicará que mantenga un Doxyfile fuente (por ejemplo, el "Doxyfile.in" que puse en el diseño de la carpeta anterior) que tiene tokens para ser encontrados y reemplazados por comandos de análisis de CMake. En mi archivo de listas de cmakelistas de nivel superior , encontrarás una pieza de kung-fu de CMake que hace algunas cosas elegantes con cmake-doxygen juntos.

 35
Author: Mikael Persson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-23 03:47:22

Estructuración del proyecto

En general, estaría a favor de lo siguiente: {[17]]}

├── CMakeLists.txt
|
├── docs/
│   └── Doxyfile
|
├── include/
│   └── project/
│       └── vector3.hpp
|
├── src/
    └── project/
        └── vector3.cpp
        └── test/
            └── test_vector3.cpp

Esto significa que tiene un conjunto muy claramente definido de archivos API para su biblioteca, y la estructura significa que los clientes de su biblioteca harían

#include "project/vector3.hpp"

En lugar de la menos explícita

#include "vector3.hpp"


Me gusta que la estructura del árbol /src coincida con la del árbol /include, pero esa es realmente una preferencia personal. Sin embargo, si su proyecto se expande para contener subdirectorios dentro de/include /project, por lo general ayudaría a hacer coincidir los que están dentro del árbol / src.

Para las pruebas, estoy a favor de mantenerlas "cerca" de los archivos que prueban, y si terminas con subdirectorios dentro de /src, es un paradigma bastante fácil de seguir para otros si quieren encontrar el código de prueba de un archivo dado.


Pruebas

En segundo lugar, me gustaría usar el Marco de pruebas de Google C++ para realizar pruebas unitarias de mi código, ya que parece bastante fácil utilizar.

Gtest es de hecho simple de usar y es bastante completo en términos de sus capacidades. Se puede utilizar junto con gmock muy fácilmente para ampliar sus capacidades, pero mis propias experiencias con gmock han sido menos favorables. Estoy bastante preparado para aceptar que esto puede deberse a mis propias deficiencias, pero las pruebas de gmock tienden a ser más difíciles de crear y mucho más frágiles / difíciles de mantener. Un gran clavo en el ataúd de gmock es que realmente no juega bien con punteros inteligentes.

Esta es una respuesta muy trivial y subjetiva a una pregunta enorme (que probablemente no pertenece realmente a S. O.)

¿Sugiere incluir esto con mi código, por ejemplo, en una carpeta "inc/gtest" o "contrib/gtest"? Si está incluido, ¿sugiere usar el fuse_gtest_files.py script para reducir el número o archivos, o dejarlo como está? Si no se incluye, ¿cómo se maneja esta dependencia?

Yo prefiero usar CMake del ExternalProject_Add módulo. Esto evita tener que mantener el código fuente de gtest en su repositorio, o instalarlo en cualquier lugar. Se descarga y se construye en su árbol de compilación automáticamente.

Ver mi respuesta tratando con los detalles aquí.

Cuando se trata de escribir pruebas, ¿cómo se organizan generalmente? Estaba pensando en tener un archivo cpp para cada clase (test_vector3.cpp por ejemplo) pero todos compilados en un binario para que todos puedan ejecutarse juntos fácilmente?

Buen plan.


Edificio

Soy un fan de CMake, pero al igual que con sus preguntas relacionadas con la prueba, S. O. probablemente no es el mejor lugar para pedir opiniones sobre un tema tan subjetivo.

Cómo serían las listas de CMakeLists.txt tiene que mirar para que pueda construir solo la biblioteca o la biblioteca y las pruebas?

add_library(ProjectLibrary <All library sources and headers>)
add_executable(ProjectTest <All test files>)
target_link_libraries(ProjectTest ProjectLibrary)

La biblioteca aparecerá como destino "ProjectLibrary", y el conjunto de pruebas como destino "ProjectTest" (en inglés). Al especificar la biblioteca como una dependencia del exe de prueba, la construcción del exe de prueba hará que la biblioteca se reconstruya automáticamente si está desactualizada.

También he visto bastantes proyectos que tienen un anuncio de compilación de un directorio bin. ¿La compilación ocurre en el directorio de compilación y luego los binarios se trasladan al directorio bin? ¿Los binarios para las pruebas y la biblioteca vivirían en el mismo lugar?

CMake recomienda " fuera de la fuente" construye, es decir, crea su propio directorio de construcción fuera del proyecto y ejecuta CMake desde allí. Esto evita "contaminar" su árbol de fuentes con archivos de compilación, y es muy deseable si está utilizando un vcs.

puede especificar que los binarios se mueven o copian a un directorio diferente una vez construidos, o que se crean por defecto en otro directorio, pero generalmente no hay necesidad. CMake proporciona formas completas de instalar su proyecto si lo desea, o hacerlo fácil para otros proyectos CMake para "encontrar" los archivos relevantes de su proyecto.

Con respecto al propio soporte de CMake para encontrar y ejecutar pruebas gtest, esto sería en gran medida inapropiado si compilas gtest como parte de tu proyecto. El módulo FindGtest está realmente diseñado para ser utilizado en el caso de que gtest se haya construido por separado fuera de su proyecto.

CMake proporciona su propio marco de prueba (CTest), e idealmente, cada caso gtest se agregaría como CTest caso.

Sin embargo, la macro GTEST_ADD_TESTS proporcionada por FindGtest para permitir la adición fácil de casos gtest como casos ctest individuales es algo deficiente, ya que no funciona para macros de gtest que no sean TEST y TEST_F. Valor - o Tipo-pruebas parametrizadas utilizando TEST_P, TYPED_TEST_P, etc. no se manejan en absoluto.

El problema no tiene una solución fácil, que yo sepa. La forma más robusta de obtener una lista de casos gtest es ejecutar el test exe con el flag --gtest_list_tests. Sin embargo, esto solo se puede hacer una vez que se construye el exe, por lo que CMake no puede hacer uso de esto. Lo que le deja con dos opciones; CMake debe tratar de analizar el código C++ para deducir los nombres de las pruebas (no trivial en el extremo si desea tener en cuenta todas las macros gtest, pruebas comentadas, pruebas deshabilitadas), o los casos de prueba se agregan a mano a las listas de CMakeLists.archivo txt.

También me gustaría usar doxygen para documentar mi código. ¿Es posible que esto se ejecute automáticamente con cmake y hacer?

Sí - aunque no tengo experiencia en este frente. CMake proporciona FindDoxygen para este propósito.

 17
Author: Fraser,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-20 11:08:15

Además de las otras (excelentes) respuestas, voy a describir una estructura que he estado usando para proyectos relativamente a gran escala.
No voy a abordar la pregunta sobre Doxygen, ya que me limitaré a repetir lo que se dice en las otras respuestas.


Justificación

Para modularidad y mantenibilidad, el proyecto se organiza como un conjunto de unidades pequeñas. Para mayor claridad, vamos a nombrarlos UnitX, con X = A, B, C,... (pero pueden tener cualquier general nombre). La estructura de directorios se organiza para reflejar esta elección, con la posibilidad de agrupar unidades si es necesario.

Solución

La disposición básica del directorio es la siguiente (el contenido de las unidades se detalla más adelante):

project
├── CMakeLists.txt
├── UnitA
├── UnitB
├── GroupA
│   └── CMakeLists.txt
│   └── GroupB
│       └── CMakeLists.txt
│       └── UnitC
│       └── UnitD
│   └── UnitE

project/CMakeLists.txt podría contener lo siguiente:

cmake_minimum_required(VERSION 3.0.2)
project(project)
enable_testing() # This will be necessary for testing (details below)

add_subdirectory(UnitA)
add_subdirectory(UnitB)
add_subdirectory(GroupA)

Y project/GroupA/CMakeLists.txt:

add_subdirectory(GroupB)
add_subdirectory(UnitE)

Y project/GroupB/CMakeLists.txt:

add_subdirectory(UnitC)
add_subdirectory(UnitD)

Ahora a la estructura de las diferentes unidades (tomemos, como ejemplo, UnitD)

project/GroupA/GroupB/UnitD
├── README.md
├── CMakeLists.txt
├── lib
│   └── CMakeLists.txt
│   └── UnitD
│       └── ClassA.h
│       └── ClassA.cpp
│       └── ClassB.h
│       └── ClassB.cpp
├── test
│   └── CMakeLists.txt
│   └── ClassATest.cpp
│   └── ClassBTest.cpp
│   └── [main.cpp]

A la diferentes componentes:

  • Me gusta tener source (.cpp) y headers (.h) en la misma carpeta. Esto evita una jerarquía de directorios duplicados, facilita el mantenimiento. Para la instalación, no es ningún problema (especialmente con CMake) simplemente filtrar los archivos de encabezado.
  • El rol del directorio UnitD es permitir más adelante incluir archivos con #include <UnitD/ClassA.h>. Además, al instalar esta unidad, solo puede copiar la estructura de directorios tal cual. Tenga en cuenta que también puede organizar su fuente archivos en subdirectorios.
  • Me gusta un archivo README para resumir de qué se trata la unidad y especificar información útil sobre ella.
  • CMakeLists.txt podría contener simplemente:

    add_subdirectory(lib)
    add_subdirectory(test)
    
  • lib/CMakeLists.txt:

    project(UnitD)
    
    set(headers
        UnitD/ClassA.h
        UnitD/ClassB.h
        )
    
    set(sources
        UnitD/ClassA.cpp
        UnitD/ClassB.cpp    
        )
    
    add_library(${TARGET_NAME} STATIC ${headers} ${sources})
    
    # INSTALL_INTERFACE: folder to which you will install a directory UnitD containing the headers
    target_include_directories(UnitD
                               PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
                               PUBLIC $<INSTALL_INTERFACE:include/SomeDir>
                               )
    
    target_link_libraries(UnitD
                          PUBLIC UnitA
                          PRIVATE UnitC
                          )
    

    Aquí, tenga en cuenta que no es necesario decirle a CMake que queremos los directorios include para UnitA y UnitC, ya que esto ya se especificó al configurar esas unidades. Además, PUBLIC le dirá a todos los objetivos que dependen de UnitD que deben incluya automáticamente la dependencia UnitA, mientras que UnitC no será necesario entonces (PRIVATE).

  • test/CMakeLists.txt (vea más abajo si desea usar GTest para ello):

    project(UnitDTests)
    
    add_executable(UnitDTests
                   ClassATest.cpp
                   ClassBTest.cpp
                   [main.cpp]
                   )
    
    target_link_libraries(UnitDTests
                          PUBLIC UnitD
    )
    
    add_test(
            NAME UnitDTests
            COMMAND UnitDTests
    )
    

Usando GoogleTest

Para la prueba de Google, la más fácil es si su fuente está presente en algún lugar de su directorio de fuentes, pero no tiene que agregarlo allí usted mismo. He estado usando este proyecto para descargarlo automáticamente, y envuelvo su uso en un función para asegurarse de que se descarga solo una vez, a pesar de que tenemos varios objetivos de prueba.

Esta función CMake es la siguiente:

function(import_gtest)
  include (DownloadProject)
  if (NOT TARGET gmock_main)
    include(DownloadProject)
    download_project(PROJ                googletest
                     GIT_REPOSITORY      https://github.com/google/googletest.git
                     GIT_TAG             release-1.8.0
                     UPDATE_DISCONNECTED 1
                     )
    set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Prevent GoogleTest from overriding our compiler/linker options when building with Visual Studio
    add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL)
  endif()
endfunction()

Y luego, cuando quiera usarlo dentro de uno de mis objetivos de prueba, agregaré las siguientes líneas al CMakeLists.txt (esto es para el ejemplo anterior, test/CMakeLists.txt):

import_gtest()
target_link_libraries(UnitDTests gtest_main gmock_main)
 5
Author: oLen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-30 09:56:12