Дисклеймер: статья написана разработчиком некоторого кода на Java (и в разработке использовалась соответсвующая экосистема, вроде системы сборки Maven), соответственно, у автора выработался некоторый набор привычек и ожиданий, который может показаться странным человеку, имеющему опыт работы с CMake.

В ходе разработки некоторого проекта на C++ у меня возникла потребность в использовании в коде некоторых ресурсов из файловой системы, не являющихся кодом (допустим, это были некоторые конфигурационные файлы для исходного кода — в данном контексте это не принципиально). Подразумевался некоторый набор файлов для разработки/тестирования, который затем может быть изменен при поставке итогового продукта. Как разработчик на Java, я привык помещать подобные файлы в каталог resources (являющийся каталогом по умолчанию для подобного рода файлов в плагине maven-resources-plugin системы сборки Maven), и при необходимости конфигурировать maven-resources-plugin для фильтрации этих ресурсов (например, в продукционной и тестовой сборках). И я был не очень приятно удивлен, когда обнаружил, что в используемой системе сборки подобная роскошь отсутствует. Пришлось окунуться с головой в ответы на StackOverflow документацию в поисках решения.
Превое, на что можно наткнуться при поиске — команда file. К сожаленю, она исполняется лишь при запуске cmake'а, что не подходит для задачи — содержимое файлов ресурсов может меняться, и при этом они должны быть скопированы в нужное место при следующей сборке с помощью make'а. Потому поиск пришлось продолжить.
Следующее, что было найдено — это команда configure_file. На первый взгляд, она делает то, что нужно — создает зависимость от файла, что приведет к его копированию в указанное место при запуске make'а (в случае, если он был изменен). Команда может даже немного больше ‒ например, заменять в копируемом файле переменные вида ${VAR}
и @VAR@
. А вот чего она не может — это копировать целые каталоги.
Учитывая специфику команды configure_file, возникла необходимость в написании собственного макроса CMake (благо, в нем есть такая возможность), а для этого нужно хоть как-то формализовать свои требования к нему.
- Макросу передается два пути — исходный и целевой каталоги.
- Относительный путь исходного каталога разрешается относительно исходного каталога (переменная
CMAKE_CURRENT_SOURCE_DIR
).
- Относительный путь целевого каталога разрешается относительно каталога с скомпилированными файлами (
CMAKE_CURRENT_BINARY_DIR
).
- Каталог с ресурсами копируется рекурсивно.
- Изменение содержимого файлов должно приводить к их повторному копированию при следующем запуске make'а.
- Изменения в структуре каталога ресурсов (добавление новых файлов/каталогов и т.п.) обрабатываются только при запуске cmake'а.
- Никаких требований по обработке символических ссылок не выдвигается.
В итоге задача сводится к рекурсивному обходу каталога ресурсов и создания зависимостей от каждого файла в нем с помощью configure_file. В результате был получен следующий макрос:
macro(all_subdirs result source)
set(dirlist "")
set(res "")
list(APPEND dirlist ${source})
list(LENGTH dirlist len)
while (${len} GREATER 0)
list(GET dirlist 0 curdir)
file(GLOB children RELATIVE ${source} ${curdir}/*)
foreach(child ${children})
if(IS_DIRECTORY ${source}/${child})
list(APPEND dirlist ${source}/${child})
list(APPEND res ${child})
endif()
endforeach()
list(REMOVE_AT dirlist 0)
list(LENGTH dirlist len)
endwhile()
set(${result} ${res})
endmacro()
#Copy all files from specified source to target
#Source relative path is treated with respect to the value of CMAKE_CURRENT_SOURCE_DIR
#Target relative path is treated with respect to the value of CMAKE_CURRENT_BINARY_DIR
#If target already exists and it is not CMAKE_CURRENT_BINARY_DIR, it will be removed
macro(configure_resources source target)
if (NOT IS_ABSOLUTE ${source})
set(abs_source ${CMAKE_CURRENT_SOURCE_DIR}/${source})
else()
set(abs_source ${source})
endif()
if (NOT IS_ABSOLUTE ${target})
set(abs_target ${CMAKE_CURRENT_BINARY_DIR}/${target})
else()
set(abs_target ${target})
endif()
if (NOT EXISTS ${abs_source})
message(SEND_ERROR "Source resources do not exist")
else()
if (NOT IS_DIRECTORY ${abs_source})
message(SEND_ERROR "Source is not directory")
else()
if (EXISTS ${abs_target})
message("Clean target")
if (NOT ${abs_target} STREQUAL ${CMAKE_CURRENT_BINARY_DIR})
file(REMOVE_RECURSE ${abs_target})
endif()
endif()
file(MAKE_DIRECTORY ${abs_target})
all_subdirs(subs ${abs_source})
list(INSERT subs 0 ".")
foreach(sub ${subs})
set(curdir ${abs_source}/${sub})
set(targetdir ${abs_target}/${sub})
file(MAKE_DIRECTORY ${targetdir})
file(GLOB contents RELATIVE ${curdir} ${curdir}/*)
foreach(child ${contents})
if (NOT IS_DIRECTORY ${curdir}/${child})
configure_file(${curdir}/${child} ${targetdir}/${child} COPYONLY)
endif()
endforeach()
endforeach()
endif()
endif()
endmacro()
Спасибо за внимание.
Засим откланиваюсь, прощайте.