Лучше указать исходные файлы с GLOB или каждый файл отдельно в CMake?
CMake предлагает несколько способов указать исходные файлы для цели. Одним из них является использование globbing ( документация), например:
FILE (GLOB dir/*)
Другой способ - указывать каждый файл отдельно.
Какой способ предпочитать? Шарить кажется легким, но я слышал, что у него есть некоторые недостатки.
7 ответов
Полное раскрытие. Первоначально я предпочел глобальный подход из-за его простоты, но с годами я пришел к выводу, что явное перечисление файлов менее подвержено ошибкам для крупных проектов с несколькими разработчиками.
Оригинальный ответ:
Преимущества для шатания:
Добавить новые файлы легко, поскольку они перечислены только в одном месте: на диске. Не сглаживание создает дублирование.
Ваш файл CMakeLists.txt будет короче. Это большой плюс, если у вас много файлов. Отсутствие глобализации приводит к потере логики CMake среди огромных списков файлов.
Преимущества использования жестко закодированных списков файлов:
CMake будет правильно отслеживать зависимости нового файла на диске - если мы используем glob, то файлы, которые не были глобализированы в первый раз при запуске CMake, не будут выбраны
Вы гарантируете, что добавляются только те файлы, которые вы хотите. Globbing может забрать ненужные файлы, которые вы не хотите.
Чтобы обойти первую проблему, вы можете просто "дотронуться" до CMakeLists.txt, который выполняет глобус, либо с помощью команды touch, либо написав файл без изменений. Это заставит cmake перезапуститься и забрать новый файл.
Чтобы решить вторую проблему, вы можете аккуратно организовать свой код по каталогам, что вы, вероятно, делаете в любом случае. В худшем случае вы можете использовать команду list(REMOVE_ITEM), чтобы очистить список файлов в виде глобуса:
file(GLOB to_remove file_to_remove.cpp)
list(REMOVE_ITEM list ${to_remove})
Единственная реальная ситуация, когда это может вас укусить, это если вы используете что-то вроде git-bisect, чтобы попробовать более старые версии вашего кода в той же директории сборки. В этом случае вам может потребоваться очистить и скомпилировать больше, чем необходимо, чтобы гарантировать, что вы получите нужные файлы в списке. Это такой угловой случай, и тот, в котором вы уже находитесь на цыпочках, что на самом деле это не проблема.
Лучший способ указать исходные файлы в CMake - это явно перечислить их.
Сами создатели CMake советуют не использовать globbing.
Смотрите: http://www.cmake.org/cmake/help/v3.3/command/file.html?highlight=glob
(Мы не рекомендуем использовать GLOB для сбора списка исходных файлов из вашего исходного дерева. Если файл CMakeLists.txt не изменяется при добавлении или удалении источника, сгенерированная система сборки не может знать, когда попросить CMake сгенерировать заново.)
Конечно, вы можете знать, что минусы - читайте дальше!
Когда срывается глобализация:
Большим недостатком глобализации является то, что создание / удаление файлов не приведет к автоматическому обновлению системы сборки.
Если вы являетесь пользователем, добавляющим файлы, это может показаться приемлемым компромиссом, однако это создает проблемы для других людей, создающих ваш код, они обновляют проект из системы контроля версий, запускают сборку, а затем связываются с вами, жалуясь на то, что
"сборка сломана".
Что еще хуже, сбой, как правило, дает некоторую ошибку связывания, которая не дает никаких указаний на причину проблемы, а время на ее устранение теряется.
В проекте, над которым я работал, мы начинали с глобализации, но получали столько жалоб при добавлении новых файлов, что было достаточно оснований для явного перечисления файлов вместо глобализации.
Это также нарушает общие рабочие процессы git
(git bisect
и переключение между функциональными ветвями).
Поэтому я не могу порекомендовать это, проблемы, которые это вызывает, перевешивают удобство, когда кто-то из-за этого не может создать ваше программное обеспечение, он может потерять много времени, чтобы отследить проблему или просто сдаться.
И еще одна заметка, просто не забывая прикасаться CMakeLists.txt
не всегда достаточно, с автоматизированными сборками, которые используют globbing, мне пришлось запустить cmake
перед каждой сборкой, поскольку файлы могли быть добавлены / удалены со времени последней сборки *.
Исключения из правила:
Есть моменты, когда предпочтительнее использовать шатание:
- Для настройки
CMakeLists.txt
файлы для существующих проектов, которые не используют CMake.
Это быстрый способ получить ссылки на весь источник (как только система сборки запустится - замените глобализацию на явные списки файлов). - Когда CMake не используется в качестве основной системы сборки, если, например, вы используете проект, который не использует CMake, и вы хотели бы сохранить для него собственную систему сборки.
- Для любой ситуации, когда список файлов меняется так часто, что его становится практически невозможно поддерживать. В этом случае это может быть полезно, но тогда вы должны принять запуск
cmake
генерировать файлы сборки каждый раз, чтобы получить надежную / правильную сборку (что противоречит намерению CMake - возможности отделить конфигурацию от сборки).
* Да, я мог бы написать код для сравнения дерева файлов на диске до и после обновления, но это не такой хороший обходной путь, и что-то лучше оставить для системы сборки.
В CMake 3.12 file(GLOB ...)
а такжеfile(GLOB_RECURSE ...)
Команды получили CONFIGURE_DEPENDS
опция, которая повторно запускает cmake, если значение глобуса изменяется. Поскольку это было основным недостатком глобализации для исходных файлов, теперь все в порядке:
# Whenever this glob's 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
работает надежно, все еще есть затраты на выполнение проверки при каждом восстановлении.
Лично я считаю, что нет необходимости вручную управлять списком исходных файлов, чтобы перевесить возможные недостатки. Если вам действительно нужно переключиться обратно на файлы, перечисленные вручную, это можно легко сделать, просто распечатав свернутый список источников и вставив его обратно.
Вы можете безопасно перемещаться (и, вероятно, должны) за счет дополнительного файла для хранения зависимостей.
Добавьте где-нибудь такие функции:
# 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's 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's 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})
Вы все еще работаете с явными зависимостями (и запускаете все автоматические сборки!), Как и раньше, только в двух файлах вместо одного.
Единственное изменение в процедуре - после того, как вы создали новый файл. Если вы не выполняете глобализацию, рабочий процесс заключается в изменении CMakeLists.txt из Visual Studio и перестройке, если вы делаете глоб, вы запускаете cmake явно - или просто дотрагиваетесь до CMakeLists.txt.
Укажите каждый файл индивидуально!
Я использую обычный CMakeLists.txt и скрипт Python для его обновления. Я запускаю скрипт python вручную после добавления файлов.
Смотрите мой ответ здесь: /questions/36343573/kak-sobirat-ishodnyie-fajlyi-s-pomoschyu-cmake-bez-sboev/36343591#36343591
Я не фанат globbing и никогда не использовал его для своих библиотек. Но недавно я посмотрел презентацию Роберта Шумахера (разработчик vcpkg), где он рекомендует рассматривать все исходники вашей библиотеки как отдельные компоненты (например, приватные исходники (.cpp), публичные заголовки (.h), тесты, примеры - есть все отдельные компоненты) и использовать для всех отдельные папки (аналогично тому, как мы используем пространства имен C++ для классов). В этом случае я думаю, что глобирование имеет смысл, потому что оно позволяет вам четко выразить этот компонентный подход и стимулировать других разработчиков следовать ему. Например, структура каталогов вашей библиотеки может быть следующей:
- /include - для общедоступных заголовков
- /src - для приватных заголовков и исходников
- /tests - для тестов
Очевидно, вы хотите, чтобы другие разработчики следовали вашему соглашению (т. е. размещали публичные заголовки в /include, а тесты — в /tests). file(glob) дает разработчикам подсказку о том, что все файлы из каталога имеют одинаковое концептуальное значение, и любые файлы, размещенные в этом каталоге, соответствующие регулярному выражению, также будут обрабатываться одинаково (например, установленные во время «make install», если мы говорить о публичных заголовках).
Это может быть полезным винтиком:
Это в powershell, но подойдет любой другой язык сценариев... Это всего лишь одно из возможных дополнений к тому, что упомянуто выше.
Получите рекурсивный список файлов кода:$res=$( Get-ChildItem -Path $root -Recurse -Attributes !Directory -Name -Include *.h,*.c,CMakeLists.txt )
Объедините каждую строку/элемент из возвращаемого Object[] в одну строку и вычислите для нее хэш. Сохраните хэш в файле в корне (любом), что вы будете запрашивать. Обычно это компоненты, основная папка и т. д. Каждый сценарий компиляции будет проверять свежевычисленный хэш на сохраненный, и в случае несоответствия (было изменение в макете файла) требуется перенастройка cmake и, естественно, сохранение свежего хэша (все еще немного тает), затем перейдите к 10.
Хэш из строки:
function stringhash {
PARAM (
[Parameter(Mandatory, Position = 0)]
[string]
$source)
$stringAsStream = [System.IO.MemoryStream]::new()
$writer = [System.IO.StreamWriter]::new($stringAsStream)
$writer.write("$($source)")
$writer.Flush()
$stringAsStream.Position = 0
$res = (Get-FileHash -InputStream $stringAsStream | Select-Object Hash)
$writer.Close()
$stringAsStream.Close()
return $res.Hash.ToUpper()
}