Pull to refresh

Работа c Doxygen в CMake

Reading time5 min
Views11K
Недавно задался вопросом ведения документации по исходному коду и сборки её используя Doxygen и CMake. Столкнулся сразу с неприятной проблемой. Дело в том, что в своих проектах я использую следующую структуру:
  build/
  src/
  CMakeLists.txt
  Doxyfile
Сборка, в моём случае, происходит в каталоге build. Но сборка может быть выполнена из любого места. Так вот, если изменить каталог сборки, документация не будет собрана, т.к. Doxygen не найдет исходников по которым нужно собрать эту самую документацию. Тогда то я и задумался, как же управлять процессом сборки документации в связке CMake и Doxygen? Что если мне нужно получить несколько видов документации: пользователя и разработчика? Держать два файла конфигурации для Doxygen? Мне такой вариант не нравится, т.к. файлы будут отличаться значением только одной переменной ENABLED_SECTIONS. Ниже я расскажу о там как можно управлять сборкой документации.

Способ первый (CONFIGURE_FILE)


На просторах Интернета и в самих исходниках CMake можно найти примерно следующий способ работы с Doxygen в CMake.
  1. FIND_PACKAGE(Doxygen)
  2.  IF(DOXYGEN_FOUND)
  3.     SET(DOXYGEN_INPUT ${CMAKE_SOURCE_DIR})
  4.     CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/doxygen.conf.in ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf)
  5.     ADD_CUSTOM_TARGET(doc COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf)
  6.  ELSE(DOXYGEN_FOUND)
  7.     MESSAGE(STATUS "WARNING: Doxygen not found - Reference manual will not be created")
  8. ENDIF(DOXYGEN_FOUND)
* This source code was highlighted with Source Code Highlighter.

Вся хитрость кроется в 3 и 4 строках приведённого выше кода. Разберём что в них происходит. Команда ADD_CUSTOM_TARGET копирует файл переданный первым параметром в файл переданный вторым параметром, заменяя при этом переменные вида @VAR@ или ${VAR} в исходном тексте на значения соответствующих переменных определённых в Ваших CMake файлах (более подробно о ADD_CUSTOM_TARGET читайте в документации). Т.е. если в doxygen.conf.in файле у нас имеется строка вида INPUT = @DOXYGEN_INPUT@, то в файле doxygen.conf она будет заменена и вместо @DOXYGEN_INPUT@ подставится значение переменной CMAKE_SOURCE_DIR (см. 3 строку приведённого выше кода).

Приведённый способ даёт возможность как то управлять процессом сборки документации и вполне подходит для большинства проектов. Тем не менее в нём есть несколько серьёзных, на мой скромный взгляд, недостатков:
  1. Всем переменным которыми мы хотим управлять нужно заранее выставить значение вида @VAR@ или ${VAR}
  2. Мы не можем использовать для этого Doxywizard. И вообще про Doxywizard можно забыть, т.к. формат файла становится с ним не совместимым. Пересохранение файла из Doxywizard просто поломает все наши настройки.
  3. Ну и главное, подобный файл конфигурации становится не совместимым с самим Doxygen и без CMake мы не сможем им воспользоваться и получить правильную документацию.

Способ другой (мой вариант)


Что бы обойти перечисленные выше недостатки я решил написать свои макросы для управления процессом сборки документации.
  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.

Простейший способ использования
  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.

Рассмотрим что здесь происходит. Макрос CONFIGURE_DOXYGEN_FILE в чём то похож на команду CMake configure_file. Она создает копию файла конфигурации Doxygen, но при этом не требует ни каких специальных изменений в нём. Она читает файл конфигурации построчно, отыскивает параметры Doxygen и проверяет, есть ли в CMake параметр с аналогичным именем и префиксом DOXY_ (строка 13). Если такой параметр существует, его значение подставляется как значение параметра Doxygen (строка 14).

Т.о. мы можем манипулировать любыми параметрами Doxygen в CMake и при этом не теряем совместимости исходного файла конфигурации с Doxygen и Doxywizard. Получаемая копия файла полностью сохраняет структуру оригинала.

Ну и в завершение пример использования посложнее. Вариант того как может выглядеть одновременная сборка документации пользователя и разработчика. Для это в исходных кодах Вы должны отметить соответствующие секции используя команду \if
  1. SET(DOXY_OUTPUT_LANGUAGE "Russian")
  2. SET(DOXY_INPUT ${PROJECT_SOURCE_DIR})
  3.  
  4. SET(DOXY_ENABLED_SECTIONS "user_sec")
  5. SET(DOXY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/doc-user")
  6. ADD_DOCUMENTATION(user_doc Doxyfile)
  7.  
  8. SET(DOXY_ENABLED_SECTIONS "developer_sec")
  9. SET(DOXY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/doc-developer")
  10. ADD_DOCUMENTATION(developer_doc Doxyfile) 
  11.  
  12. ADD_CUSTOM_TARGET(doc DEPENDS user_doc developer_doc)
* This source code was highlighted with Source Code Highlighter.
Tags:
Hubs:
+19
Comments3

Articles