Лучше ли указывать исходные файлы с GLOB или каждый файл отдельно в CMake?

CMake предлагает несколько способов указать исходные файлы для цели. Один из них - использование globbing (документация), например:

FILE (GLOB dir/*)

Другой - указать каждый файл отдельно.

Каким образом это предпочтительнее? Globbing кажется легким, но я слышал, что он имеет некоторые недостатки.

Ответ 1

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

Оригинальный ответ:


Преимуществами подглаживания являются:

  • Легко добавлять новые файлы, поскольку они перечислены только в одном месте: на диск. Не сглаживание создает дублирование.

  • Ваш файл CMakeLists.txt будет короче. Это большой плюс, если вы есть много файлов. Не сглаживание заставляет вас потерять логику CMake среди огромных списков файлов.

Преимущества использования списков жестких файлов:

  • CMake правильно отслеживает зависимости нового файла на диске - если мы используем glob, тогда файлы, которые не были всплыты в первый раз, когда вы запустили CMake, не получите поднял

  • Вы убедитесь, что добавлены только файлы, которые вы хотите добавить. Глобус может забирать бродячие файлы, которые вам не нужны.

Чтобы обойти первую проблему, вы можете просто "коснуться" CMakeLists.txt, который делает glob, либо с помощью команды touch, либо путем записи файла без изменений. Это заставит cmake повторно запустить и забрать новый файл.

Чтобы исправить вторую проблему, вы можете аккуратно упорядочить свой код в каталогах, что вы, вероятно, так или иначе делаете. В худшем случае вы можете использовать команду list (REMOVE_ITEM), чтобы очистить список файлов с глобусом:

file(GLOB to_remove file_to_remove.cpp)
list(REMOVE_ITEM list ${to_remove})

Единственная реальная ситуация, когда это может вас укусить, - если вы используете что-то вроде git-bisect, чтобы попробовать более старые версии вашего кода в тот же каталог сборки. В этом случае вам может потребоваться очистить и скомпилировать больше, чем необходимо, чтобы убедиться, что вы получили нужные файлы в списке. Это такой угловой случай, и тот, где вы уже находитесь на цыпочках, что это не проблема.

Ответ 2

Лучший способ указать исходные файлы в CMake - это явно перечислить их.

Сами создатели CMake советуют не использовать globbing.

См.: https://cmake.org/cmake/help/v3.15/command/file.html?highlight=glob#file.

(Мы не рекомендуем использовать GLOB для сбора списка исходных файлов из вашего исходного дерева. Если файл CMakeLists.txt не изменяется при добавлении или удалении источника, сгенерированная система сборки не может знать, когда попросить CMake сгенерировать заново.)

Конечно, вы можете знать, что минусы - читайте дальше!


Когда срывается глобализация:

Большим недостатком глобализации является то, что создание/удаление файлов не приведет к автоматическому обновлению системы сборки.

Если вы добавляете файлы, это может показаться приемлемым компромиссом, однако это создает проблемы для других людей, создающих ваш код, они обновляют проект из системы контроля версий, запускают сборку, а затем связываются с вами, жалуясь на то, что
"сборка сломана".

Что еще хуже, сбой, как правило, дает некоторую ошибку компоновки, которая не дает никаких указаний на причину проблемы, и теряется время на ее устранение.

В проекте, над которым я работал, мы начинали с глобализации, но получали столько жалоб при добавлении новых файлов, что было достаточно оснований для явного перечисления файлов вместо глобализации.

Это также нарушает общие рабочие процессы git
(git bisect и переключение между функциональными ветками).

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

И еще одно замечание: просто помнить, что нужно трогать CMakeLists.txt не всегда достаточно, с автоматическими сборками, использующими глобирование, мне приходилось запускать cmake перед каждой сборкой, так как файлы могли быть добавлены/удалены с момента последней сборки *.

Исключения из правила:

Есть моменты, когда предпочтительнее использовать шатание:

  • Для настройки файлов CMakeLists.txt для существующих проектов, которые не используют CMake.
    Это быстрый способ получить ссылку на весь источник (после запуска системы сборки - замените глобализацию на явные списки файлов).
  • Когда CMake не используется в качестве основной системы сборки, если, например, вы используете проект, который не использует CMake, и вы хотели бы сохранить свою собственную систему сборки для него.
  • Для любой ситуации, когда список файлов меняется так часто, что его становится практически невозможно поддерживать. В этом случае это может быть полезно, но тогда вы должны принять запуск cmake для генерации файлов сборки каждый раз, чтобы получить надежную/правильную сборку (что противоречит намерению CMake - возможности отделить конфигурацию от сборки).

* Да, я мог бы написать код для сравнения дерева файлов на диске до и после обновления, но это не такой хороший обходной путь, и что-то лучше оставить для системы сборки.

Ответ 3

Вы можете безопасно глотать (и, вероятно, следует) за счет дополнительного файла для хранения зависимостей.

Добавьте такие функции, как эти:

# Compare the new contents with the existing file, if it exists and is the 
# same we don't want to trigger a make by changing its timestamp.
function(update_file path content)
    set(old_content "")
    if(EXISTS "${path}")
        file(READ "${path}" old_content)
    endif()
    if(NOT old_content STREQUAL content)
        file(WRITE "${path}" "${content}")
    endif()
endfunction(update_file)

# Creates a file called CMakeDeps.cmake next to your CMakeLists.txt with
# the list of dependencies in it - this file should be treated as part of 
# CMakeLists.txt (source controlled, etc.).
function(update_deps_file deps)
    set(deps_file "CMakeDeps.cmake")
    # Normalize the list so it the same on every machine
    list(REMOVE_DUPLICATES deps)
    foreach(dep IN LISTS deps)
        file(RELATIVE_PATH rel_dep ${CMAKE_CURRENT_SOURCE_DIR} ${dep})
        list(APPEND rel_deps ${rel_dep})
    endforeach(dep)
    list(SORT rel_deps)
    # Update the deps file
    set(content "# generated by make process\nset(sources ${rel_deps})\n")
    update_file(${deps_file} "${content}")
    # Include the file so it tracked as a generation dependency we don't
    # need the content.
    include(${deps_file})
endfunction(update_deps_file)

И затем переходите к глобализации:

file(GLOB_RECURSE sources LIST_DIRECTORIES false *.h *.cpp)
update_deps_file("${sources}")
add_executable(test ${sources})

Вы по-прежнему перемещаете явные зависимости (и запускаете все автоматические сборки!), как раньше, только в двух файлах вместо одного.

Единственное изменение в процедуре - после создания нового файла. Если вы не glob рабочий процесс, чтобы изменить CMakeLists.txt из Visual Studio и перестроить, если вы делаете glob вы запускаете cmake явно - или просто нажмите CMakeLists.txt.

Ответ 4

В CMake 3.12 команды file(GLOB...) и file(GLOB_RECURSE...) получили параметр CONFIGURE_DEPENDS который повторно запускает cmake, если значение glob изменяется. Поскольку это было основным недостатком глобализации для исходных файлов, теперь все в порядке:

# Whenever this glob value changes, cmake will rerun and update the build with the
# new/removed files.
file(GLOB_RECURSE sources CONFIGURE_DEPENDS "*.cpp")

add_executable(my_target ${sources})

Тем не менее, некоторые люди по-прежнему рекомендуют избегать скупых источников. Действительно, в документации говорится:

Мы не рекомендуем использовать GLOB для сбора списка исходных файлов из вашего исходного дерева.... Флаг CONFIGURE_DEPENDS может работать не надежно на всех генераторах, или, если в будущем будет добавлен новый генератор, который не сможет его поддерживать, проекты, использующие его, будут заблокированы. Даже если CONFIGURE_DEPENDS работает надежно, проверка каждой перестройки все равно требует затрат.

Лично я считаю, что нет необходимости вручную управлять списком исходных файлов, чтобы перевесить возможные недостатки. Если вам действительно нужно переключиться обратно на файлы, перечисленные вручную, это можно легко сделать, просто распечатав свернутый список источников и вставив его обратно.

Ответ 5

Укажите каждый файл отдельно!

Я использую обычный CMakeLists.txt и скрипт python для его обновления. Я запускаю скрипт python вручную после добавления файлов.

См. Мой ответ здесь: fooobar.com/info/80762/...