📜 ⬆️ ⬇️

Work with Doxygen in CMake

Recently I wondered how to maintain documentation on the source code and build it using Doxygen and CMake . Faced immediately with an unpleasant problem. The fact is that in my projects I use the following structure:
  build /
   src /
   CMakeLists.txt
   Doxyfile 
The build, in my case, occurs in the build directory. But the assembly can be done from anywhere. So, if you change the build directory, the documentation will not be collected, because Doxygen will not find the sources for which you need to collect this very documentation. Then I wondered how to manage the process of assembling documentation in conjunction with CMake and Doxygen ? What if I need to get several types of documentation: a user and a developer? Keep two configuration files for Doxygen ? I do not like this option, because files will differ in the value of only one ENABLED_SECTIONS variable. Below I will tell you about how you can manage the assembly of documentation.

Method One (CONFIGURE_FILE)


On the Internet and in the CMake sources themselves, you can find something like the following way of working with Doxygen in CMake .
FIND_PACKAGE(Doxygen) IF (DOXYGEN_FOUND) SET (DOXYGEN_INPUT ${CMAKE_SOURCE_DIR}) CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/doxygen.conf. in ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ADD_CUSTOM_TARGET(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ELSE (DOXYGEN_FOUND) MESSAGE(STATUS "WARNING: Doxygen not found - Reference manual will not be created" ) ENDIF (DOXYGEN_FOUND) * This source code was highlighted with Source Code Highlighter .
  1. FIND_PACKAGE(Doxygen) IF (DOXYGEN_FOUND) SET (DOXYGEN_INPUT ${CMAKE_SOURCE_DIR}) CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/doxygen.conf. in ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ADD_CUSTOM_TARGET(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ELSE (DOXYGEN_FOUND) MESSAGE(STATUS "WARNING: Doxygen not found - Reference manual will not be created" ) ENDIF (DOXYGEN_FOUND) * This source code was highlighted with Source Code Highlighter .
  2. FIND_PACKAGE(Doxygen) IF (DOXYGEN_FOUND) SET (DOXYGEN_INPUT ${CMAKE_SOURCE_DIR}) CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/doxygen.conf. in ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ADD_CUSTOM_TARGET(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ELSE (DOXYGEN_FOUND) MESSAGE(STATUS "WARNING: Doxygen not found - Reference manual will not be created" ) ENDIF (DOXYGEN_FOUND) * This source code was highlighted with Source Code Highlighter .
  3. FIND_PACKAGE(Doxygen) IF (DOXYGEN_FOUND) SET (DOXYGEN_INPUT ${CMAKE_SOURCE_DIR}) CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/doxygen.conf. in ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ADD_CUSTOM_TARGET(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ELSE (DOXYGEN_FOUND) MESSAGE(STATUS "WARNING: Doxygen not found - Reference manual will not be created" ) ENDIF (DOXYGEN_FOUND) * This source code was highlighted with Source Code Highlighter .
  4. FIND_PACKAGE(Doxygen) IF (DOXYGEN_FOUND) SET (DOXYGEN_INPUT ${CMAKE_SOURCE_DIR}) CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/doxygen.conf. in ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ADD_CUSTOM_TARGET(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ELSE (DOXYGEN_FOUND) MESSAGE(STATUS "WARNING: Doxygen not found - Reference manual will not be created" ) ENDIF (DOXYGEN_FOUND) * This source code was highlighted with Source Code Highlighter .
  5. FIND_PACKAGE(Doxygen) IF (DOXYGEN_FOUND) SET (DOXYGEN_INPUT ${CMAKE_SOURCE_DIR}) CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/doxygen.conf. in ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ADD_CUSTOM_TARGET(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ELSE (DOXYGEN_FOUND) MESSAGE(STATUS "WARNING: Doxygen not found - Reference manual will not be created" ) ENDIF (DOXYGEN_FOUND) * This source code was highlighted with Source Code Highlighter .
  6. FIND_PACKAGE(Doxygen) IF (DOXYGEN_FOUND) SET (DOXYGEN_INPUT ${CMAKE_SOURCE_DIR}) CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/doxygen.conf. in ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ADD_CUSTOM_TARGET(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ELSE (DOXYGEN_FOUND) MESSAGE(STATUS "WARNING: Doxygen not found - Reference manual will not be created" ) ENDIF (DOXYGEN_FOUND) * This source code was highlighted with Source Code Highlighter .
  7. FIND_PACKAGE(Doxygen) IF (DOXYGEN_FOUND) SET (DOXYGEN_INPUT ${CMAKE_SOURCE_DIR}) CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/doxygen.conf. in ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ADD_CUSTOM_TARGET(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ELSE (DOXYGEN_FOUND) MESSAGE(STATUS "WARNING: Doxygen not found - Reference manual will not be created" ) ENDIF (DOXYGEN_FOUND) * This source code was highlighted with Source Code Highlighter .
  8. FIND_PACKAGE(Doxygen) IF (DOXYGEN_FOUND) SET (DOXYGEN_INPUT ${CMAKE_SOURCE_DIR}) CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/doxygen.conf. in ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ADD_CUSTOM_TARGET(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ELSE (DOXYGEN_FOUND) MESSAGE(STATUS "WARNING: Doxygen not found - Reference manual will not be created" ) ENDIF (DOXYGEN_FOUND) * This source code was highlighted with Source Code Highlighter .
FIND_PACKAGE(Doxygen) IF (DOXYGEN_FOUND) SET (DOXYGEN_INPUT ${CMAKE_SOURCE_DIR}) CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/doxygen.conf. in ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ADD_CUSTOM_TARGET(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) ELSE (DOXYGEN_FOUND) MESSAGE(STATUS "WARNING: Doxygen not found - Reference manual will not be created" ) ENDIF (DOXYGEN_FOUND) * This source code was highlighted with Source Code Highlighter .

The whole trick is covered in 3 and 4 lines of the above code. Let us understand what is happening in them. The ADD_CUSTOM_TARGET command copies the file passed by the first parameter to the file passed by the second parameter, replacing variables of the form @ VAR @ or $ {VAR} in the source text with the values ​​of the corresponding variables defined in your CMake files (for more information about ADD_CUSTOM_TARGET, see the documentation ). Those. if in the doxygen.conf.in file we have a line of the form INPUT = @ DOXYGEN_INPUT @ , then in the file doxygen.conf it will be replaced and the value of the variable CMAKE_SOURCE_DIR will be substituted for @ DOXYGEN_INPUT @ (see. 3 line of the above code).

The above method makes it possible to somehow manage the process of assembling documentation and is well suited for most projects. Nevertheless, there are several serious, in my humble opinion, flaws:
  1. All variables that we want to control need to pre-set the value of the form @ VAR @ or $ {VAR}
  2. We cannot use the Doxywizard for this. And in general, you can forget about Doxywizard , because file format becomes incompatible with it. Re-saving the file from Doxywizard will simply break all of our settings.
  3. Well and most importantly, such a configuration file becomes incompatible with Doxygen itself and without CMake, we will not be able to use it and get the correct documentation.

The other way (my version)


To get around the disadvantages listed above, I decided to write my own macros to control the documentation build process.
  1. MACRO (CONFIGURE_DOXYGEN_FILE DOXYGEN_CONFIG_FILE FILE_NAME_SUFFIX)
  2. IF (EXISTS $ {PROJECT_SOURCE_DIR} / $ {DOXYGEN_CONFIG_FILE})
  3. FILE (REMOVE $ {CMAKE_CURRENT_BINARY_DIR} / doxy - $ {FILE_NAME_SUFFIX} .conf)
  4. FILE (READ $ {PROJECT_SOURCE_DIR} / $ {DOXYGEN_CONFIG_FILE} DOXYFILE_CONTENTS)
  5. STRING (REGEX REPLACE ";" "\\\\;" DOXYFILE_CONTENTS "$ {DOXYFILE_CONTENTS}" )
  6. STRING (REGEX REPLACE "\ n" ";" DOXYFILE_LINES "$ {DOXYFILE_CONTENTS}" )
  7. LIST (LENGTH DOXYFILE_LINES ROW)
  8. MATH (EXPR ROW "$ {ROW} - 1" )
  9. FOREACH (I RANGE $ {ROW})
  10. LIST ( GET DOXYFILE_LINES $ {I} LINE)
  11. IF (LINE STRGREATER "" )
  12. STRING (REGEX MATCH "^ [a-zA-Z] ([^]) +" DOXY_PARAM $ {LINE})
  13. IF (DEFINED DOXY _ $ {DOXY_PARAM})
  14. STRING (REGEX REPLACE "= ([^ \ n]) +" "= $ {DOXY _ $ {DOXY_PARAM}}" LINE $ {LINE})
  15. ENDIF (DEFINED DOXY _ $ {DOXY_PARAM})
  16. ENDIF ()
  17. FILE (APPEND $ {CMAKE_CURRENT_BINARY_DIR} / doxy - $ {FILE_NAME_SUFFIX} .conf "$ {LINE} \ n" )
  18. ENDFOREACH ()
  19. ELSE ()
  20. MESSAGE (SEND_ERROR "Doxygen configuration file '$ {DOXYGEN_CONFIG_FILE}' not found. Documentation will not be generated" )
  21. ENDIF ()
  22. ENDMACRO (CONFIGURE_DOXYGEN_FILE)
* This source code was highlighted with Source Code Highlighter .
  1. MACRO (ADD_DOCUMENTATION TARGET DOXYGEN_CONFIG_FILE)
  2. FIND_PACKAGE (Doxygen)
  3. IF (DOXYGEN_FOUND)
  4. CONFIGURE_DOXYGEN_FILE ($ {DOXYGEN_CONFIG_FILE} $ {TARGET})
  5. ADD_CUSTOM_TARGET ($ {TARGET} COMMAND $ {DOXYGEN_EXECUTABLE} $ {CMAKE_CURRENT_BINARY_DIR} / doxy - $ {TARGET} .conf)
  6. ELSE (DOXYGEN_FOUND)
  7. MESSAGE (STATUS "Doxygen not found. Documentation will not be generated" )
  8. ENDIF (DOXYGEN_FOUND)
  9. ENDMACRO (ADD_DOCUMENTATION)
* This source code was highlighted with Source Code Highlighter .

The easiest way to use
  1. SET (DOXY_OUTPUT_LANGUAGE "Russian" )
  2. SET (DOXY_INPUT $ {PROJECT_SOURCE_DIR})
  3. ADD_DOCUMENTATION (doc Doxyfile)
* This source code was highlighted with Source Code Highlighter .

Consider what happens here. The CONFIGURE_DOXYGEN_FILE macro is somewhat similar to the CMake configure_file command. It creates a copy of the Doxygen configuration file, but does not require any special changes to it. It reads the configuration file line by line, searches for Doxygen parameters, and checks if there is a parameter in CMake with the same name and the prefix DOXY_ (line 13). If such a parameter exists, its value is substituted as the value of the Doxygen parameter (line 14).

So we can manipulate any Doxygen parameters in CMake without losing the compatibility of the original configuration file with Doxygen and Doxywizard . The resulting copy of the file fully preserves the structure of the original.
')
Well, in the end, the usage example is more complicated. A variant of how the simultaneous assembly of user and developer documentation may look. For this, in source codes you should mark the corresponding sections using the \ if command
  1. SET (DOXY_OUTPUT_LANGUAGE "Russian" )
  2. SET (DOXY_INPUT $ {PROJECT_SOURCE_DIR})
  3. SET (DOXY_ENABLED_SECTIONS "user_sec" )
  4. SET (DOXY_OUTPUT_DIRECTORY "$ {CMAKE_CURRENT_BINARY_DIR} / doc-user" )
  5. ADD_DOCUMENTATION (user_doc Doxyfile)
  6. SET (DOXY_ENABLED_SECTIONS "developer_sec" )
  7. SET (DOXY_OUTPUT_DIRECTORY "$ {CMAKE_CURRENT_BINARY_DIR} / doc-developer" )
  8. ADD_DOCUMENTATION (developer_doc Doxyfile)
  9. ADD_CUSTOM_TARGET (doc DEPENDS user_doc developer_doc)
* This source code was highlighted with Source Code Highlighter .

Source: https://habr.com/ru/post/133512/


All Articles