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

157

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

FILE(GLOB MY_SRCS dir/*)

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

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

Marenz
источник

Ответы:

185

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

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


Преимущества для шатания:

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

  • Ваш файл 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, чтобы попробовать более старые версии вашего кода в той же директории сборки. В этом случае вам может потребоваться очистить и скомпилировать больше, чем необходимо, чтобы гарантировать, что вы получите нужные файлы в списке. Это такой угловой случай, и тот, в котором вы уже находитесь в затруднительном положении, что на самом деле это не проблема.

richq
источник
1
Также плохо с globbing: файлы difftool в git хранятся как $ basename. $ Ext. $ Type. $ Pid. $ Ext, что может вызвать забавные ошибки при попытке компиляции после одного разрешения слияния.
mathstuf
9
Я думаю, что этот ответ затмевает недостатки отсутствия новых файлов в cmake, Simply "touch" the CMakeLists.txtэто нормально, если вы разработчик, но для других, строящих ваше программное обеспечение, это может быть проблемой, когда ваша сборка завершается неудачно после обновления, и бремя их изучения Зачем.
ideasman42
36
Знаешь что? С момента написания этого ответа 6 лет назад я немного передумал и теперь предпочитаю явно перечислять файлы. Единственным недостатком является то, что «добавить файл - это немного больше работы», но это избавит вас от всех видов головной боли. И во многих отношениях явное лучше, чем неявное.
Ричк
1
@richq Может ли этот мерзавец пересмотреть ваше текущее положение? :)
Антонио
8
Хорошо, как говорит Антонио, голоса были отданы за пропаганду «глобального» подхода. Изменение характера ответа - это то, что нужно сделать для этих избирателей. В качестве компромисса я добавил правку, чтобы отразить мое изменившееся мнение. Я извиняюсь перед интернетом за то, что вызвал такую ​​бурю в стакане воды :-P
richq
113

Лучший способ указать исходные файлы в 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 - возможности отделить конфигурацию от сборки) .

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

ideasman42
источник
9
«Большим недостатком глобализации является то, что создание новых файлов не приведет к автоматическому обновлению системы сборки». Но не правда ли, что, если вы не работаете с глобусом, вам все равно придется вручную обновить CMakeLists.txt, то есть cmake все еще не обновляет систему сборки автоматически? Похоже, в любом случае вы должны помнить, что нужно что-то делать вручную для создания новых файлов. Прикосновение к CMakeLists.txt кажется проще, чем открыть его и отредактировать, чтобы добавить новый файл.
Дан
17
@ Дэн, для вашей системы - конечно, если вы разрабатываете в одиночку, это нормально, но как насчет всех остальных, кто создает ваш проект? Собираетесь ли вы отправить их по электронной почте и вручную коснуться файла CMake? каждый раз, когда файл добавляется или удаляется? - Хранение списка файлов в CMake гарантирует, что сборка всегда использует те же файлы, о которых знает vcs. Поверьте мне - это не просто какая-то тонкая деталь - Когда ваша сборка не удается для многих разработчиков - они отправляют списки по почте и спрашивают в IRC, что код не работает. Примечание: (Даже в вашей собственной системе вы можете вернуться в историю git, например, и не думать о том, чтобы войти и коснуться файлов CMake)
ideasman42
2
Ах, я не думал об этом случае. Это лучшая причина, которую я слышал против тусовки. Я хотел бы, чтобы в документах cmake было больше объяснений, почему они рекомендуют людям избегать тряски.
Дан
1
Я думал о решении записи времени последнего выполнения cmake в файл. Единственные проблемы: 1) это, вероятно, должно быть сделано cmake, чтобы быть кроссплатформенным, и поэтому мы должны как-то избежать запуска cmake во второй раз. 2) Возможно, больше конфликтов слияния (которые все еще случаются со списком файлов между прочим). В этом случае их можно было бы решить тривиально, используя более позднюю временную метку.
Предельник
2
@ tim-mb, «Но было бы неплохо, если бы CMake создал файл filetree_updated, который вы могли бы зарегистрировать, и он автоматически менялся бы каждый раз при обновлении глобуса файлов». - Вы только что точно описали, что делает мой ответ.
Глен Ноулз
22

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

# 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работает надежно, проверка каждой перестройки все еще требует затрат.

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

Джастин
источник
Если ваша система сборки выполняет полный цикл cmake и build (удалите каталог сборки, запустите cmake оттуда и затем вызовите make-файл), при условии, что они не извлекают ненужные файлы, неужели нет никаких недостатков в использовании источников GLOBbed? По моему опыту, часть cmake выполняется намного быстрее, чем сборка, так что в любом случае это не такая уж большая нагрузка
Ден-Джейсон
9

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

Добавьте где-нибудь такие функции:

# 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.

Глен Ноулз
источник
Сначала я подумал, что это инструмент, который будет автоматически обновлять Makefiles при добавлении исходного файла, но теперь я вижу, каково его значение. Ницца! Это решает проблему обновления кем-либо из репозитория и makeвыдачи странных ошибок компоновщика.
Крис Луенго
1
Я считаю, что это может быть хорошим методом. Конечно, еще нужно помнить, что cmake должен вызывать после добавления или удаления файла, и также требуется фиксация этого файла зависимостей, поэтому необходимо некоторое обучение на стороне пользователя. Основным недостатком может быть то, что этот файл зависимостей может привести к неприятным конфликтам слияния, которые могут быть трудно разрешаемыми без необходимости повторного понимания разработчиком механизма.
Антонио
1
Это не будет работать, если ваш проект содержит условно включенные файлы (например, некоторые файлы, которые используются только при включенной функции или только для конкретной операционной системы). Для портативного программного обеспечения достаточно распространено, что некоторые файлы используются только для специальных платформ.
ideasman42
0

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

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

Смотрите мой ответ здесь: https://stackoverflow.com/a/48318388/3929196

Палфи
источник