Doxygen + namespaces + Qt

Desde hace algunos días me estoy dedicando a documentar unas bibliotecas para C++ que he desarrollado en mi trabajo. Para ello, como no, estoy utilizando Doxygen. Como utilizamos CMake para generar los proyectos, pues qué mejor lugar para generar la documentación. Muestro a continuación la configuración, simplemente a modo de ilustración:


#-- Add an Option to toggle the generation of the API documentation
OPTION(BUILD_DOCUMENTATION "Use Doxygen to create the HTML based API documentation" ON)
IF(BUILD_DOCUMENTATION)
FIND_PACKAGE(Doxygen)
IF(NOT DOXYGEN_FOUND)
MESSAGE(FATAL_ERROR "Doxygen is needed to build the documentation. Please install it correctly.")
ENDIF()
#-- Configure the Template Doxyfile for our specific project
CONFIGURE_FILE(Doxyfile.in ${PROJECT_BINARY_DIR}/Doxyfile @ONLY IMMEDIATE)
FILE(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/doc)
#-- Add a custom target to run Doxygen when ever the project is built
ADD_CUSTOM_TARGET(Docs
COMMAND ${DOXYGEN_EXECUTABLE} ${PROJECT_BINARY_DIR}/Doxyfile
SOURCES ${PROJECT_BINARY_DIR}/Doxyfile)
# IF you do NOT want the documentation to be generated EVERY time you build the project
# then leave out the 'ALL' keyword from the above command.
ENDIF()

El fichero Doxyfile.in contiene la siguiente configuración:


PROJECT_NAME = @LIBRARY_NAME@
PROJECT_NUMBER = @LIBRARY_VERSION_MAJOR@.@LIBRARY_VERSION_MINOR@.@LIBRARY_VERSION_PATCH@
OUTPUT_DIRECTORY = "@PROJECT_BINARY_DIR@/doc"
STRIP_FROM_PATH = "@PROJECT_SOURCE_DIR@/lib"
INPUT = "@PROJECT_SOURCE_DIR@/lib"
RECURSIVE = YES
CREATE_SUBDIRS = YES
EXTRACT_PRIVATE = NO
GENERATE_TODOLIST = YES
GENERATE_BUGLIST = YES
WARNINGS = NO
WARN_IF_UNDOCUMENTED = NO

Donde @LIBRARY_NAME@ y demás son variables de entorno definidas en el fichero CMake y que hacen alusión al nombre de la biblioteca y la versión actual de la misma.

No entraré en más detalle sobre el uso de Doxygen ya que hay mucho escrito, salvo por un pequeño “problema” que tuve.

Resulta que si generamos la documentación a palo seco, los namespaces que hayamos utilizado serán incluidos en la documentación. Esto está bien hasta cierto punto: si la documentación es de una API o de una biblioteca, es muy común que todo esté dentro de un namespace común (por ejemplo, libname). En este caso toda la documentación estará plagada de libname::, libname::, libname::… y no es precisamente agradable a la vista ni fácil de leer.

Buscando en Google, rápidamente di con una posible solución (como verán, la he adaptado para incluir mi versión, pero la seguiré contando acá de todas formas):

http://stackoverflow.com/questions/6811816/doxygen-strip-top-level-namespace/11231222

El código propuesto consiste en definir una serie de macros, y reemplazar el uso del namespace principal por ellas.


#ifndef DOXY_PARSER
#define LIB_NAMESPACE_STARTS namespace lib_namespace { /##/
#define LIB_NAMESPACE_ENDS } /##/
#define LIB_NAMESPACE lib_namespace
#else
#define LIB_NAMESPACE_STARTS /##/
#define LIB_NAMESPACE_ENDS /##/
#define LIB_NAMESPACE
#endif

Con esta solución hay un problema, si usamos Qt en el proyecto (como podrán suponer era mi caso), el proceso de generación de los ficheros moc se salta por completo las macros (no las expande), y por ende el fichero .cxx generado no dispone de la información de los namespaces, lo cual, adivinaron, finaliza en un montón de errores por parte del comilador.

Una posible solución sería utilizar un #define QT_NAMESPACE lib_namespace, pero esto movería todo el espacio de nombres de Qt al de la biblioteca, y eso no es bueno.

La solución que encontré al final es más general, y funciona tanto en proyectos que utilizan Qt como los que no.


#ifndef DOXY_PARSER
#define LIB_NAMESPACE lib_namespace
#define LIB_NAMESPACE_STARTS namespace LIB_NAMESPACE { /##/
#if (defined MOCED_FILE)
#define LIB_NAMESPACE_ENDS } using namespace LIB_NAMESPACE; /##/
#else
#define LIB_NAMESPACE_ENDS } /##/
#endif
#define USING_LIB_NAMESPACE using namespace LIB_NAMESPACE; /##/
#else
#define LIB_NAMESPACE_STARTS /##/
#define LIB_NAMESPACE_ENDS /##/
#define LIB_NAMESPACE
#define USING_LIB_NAMESPACE /##/
#endif

Donde `MOCED_FILE` es una macro definida únicamente para los ficheros moc. Esto es sencillo si se utiliza CMake:


QT4_WRAP_CPP(mocSources qt_file1.h qt_file2.h)
SET_SOURCE_FILES_PROPERTIES(${mocSources} PROPERTIES COMPILE_FLAGS "-DMOCED_FILE")

Si bien la intención original del post era comentar mis andanzas con este problema de los namespaces, decidí agregar una pequeña introducción al uso de Doxygen con CMake ya que por la red no lo encontré a la primera.