null

Автогенерация кода и CMake

Доброе утро!

Для некоторых целей бывает удобно сгенерировать код автоматически и использовать его с далее. Возникает вопрос: как подружить такую штуку с CMake? Попробуем на него ответить.

В моём случае возникла необходимость генерировать парсеры и классы из описания некоторых тегов. На этом примере и рассмотрим решение данной задачи.

Был выставлен ряд требований:

  • возможность обновления каталога сборки при изменении описания;
  • возможность обновления каталога сборки при изменении генератора;
  • интегрируемость с QtCreator.

Последнее требование выставлено в связи с тем, что я пользуюсь данной средой разработки, а она, в свою очередь, разбирает каталог сборки, созданный CMake.

\\

Прикинем, как это всё реализовать. Очевидно, что нужен генератор кода. В моём случае он будет называться tag_data_gen.pl. Интерфейс у него следующий: первым аргументом командной строки передаётся путь к каталогу для сохранения, остальными — пути к файлам с описанием, по которому генерировать. Вывод его выглядит следующим образом:

set(generated_files
  tag1.td.hpp
  tag1.td.cpp
  tag2.td.hpp
  tag2.td.hpp
)

Конкретная реализация генератора и формата описания в рамках данной задачи не принципиальна.

\\

Пусть сами исходники проекта находятся в src/. Тогда создадим каталог src/tag_data/, в котором будут располагаться наши файлы с описанием для генератора.

Добавим в CMakeLists.txt следующие строки:

set(src src)                                                                            
                                                                                  
include_directories(${src})                                                            
                                                                                  
set(tag_data                                                                      
  ${src}/tag_data/tag1.td                                                      
  ${src}/tag_data/tag2.td                                                        
  )                                                                               
                                                                                  
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS                    
  ${tag_data}                                                                   
  tag_data_gen.pl                                                                 
  ) 

Таким образом мы научили CMake воспринимать эти файлы как часть структуры проекта. Обновление их должно вести к пересборке зависимостей.

Следующим этапом необходимо вызвать генератор. Для этого создадим каталог сгенерированных файлов и вызовем генератор, перенаправив его вывод (см. пример выше) в файл. Смысл сего действия будет пояснён далее. Достигается это парой нехитрых команд:

make_directory("${CMAKE_BINARY_DIR}/generated")                                   
execute_process(                                                                  
  COMMAND perl tag_data_gen.pl ${CMAKE_BINARY_DIR}/generated ${tag_data}          
  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}                                        
  OUTPUT_FILE generated_files.cmake                                            
  )

Теперь вывод вида set(generated_files …) будет находиться в файле generated_files.cmake.  Это даёт нам возможность получить список сгенерированных файлов в виде листовой переменной в одну команду:

include(generated_files.cmake)

Ну и можем сделать что-нибудь, например, такое:

add_library("generated" SHARED ${generated_files})
get_filename_component(gendirpath ${CMAKE_BINARY_DIR}/generated ABSOLUTE)      
include_directories(${gendirpath})  

Теперь мы получили отдельную единицу трансляцию (в данном случае — динамическую библиотеку) из наших сгенерированных файлов, заодно добавив их в структуру проекта, что позволит их безболезненно #include "…" и видеть их в QtCreator.

\\

Как оказалось, не так уж и сложно.