É melhor especificar arquivos de origem com GLOB ou cada arquivo individualmente no CMake?

O CMake oferece várias maneiras de especificar os arquivos de origem para um destino. Uma é usar globbing ( documentação ), por exemplo:

FILE (GLOB dir/*) 

Outra é especificar cada arquivo individualmente.

Qual caminho é preferir? Globbing parece fácil, mas ouvi dizer que tem algumas desvantagens.

Divulgação completa: Eu preferi originalmente a abordagem globbing por sua simplicidade, mas ao longo dos anos eu percebi que listar explicitamente os arquivos é menos propenso a erros para projetos grandes e com vários desenvolvedores.

Resposta original:


As vantagens de globbing são:

  • É fácil adicionar novos arquivos, pois eles são listados em um único lugar: no disco. Não globbing cria duplicação.

  • Seu arquivo CMakeLists.txt será mais curto. Esta é uma grande vantagem se você tiver muitos arquivos. Não globbing faz com que você perca a lógica CMake entre grandes listas de arquivos.

As vantagens de usar listas de arquivos codificados são:

  • O CMake irá rastrear as dependencies de um novo arquivo no disco corretamente – se usarmos glob, então os arquivos que não tiverem sido gerados pela primeira vez quando você executou o CMake não serão captados

  • Você garante que apenas os arquivos desejados sejam adicionados. A globalização pode pegar arquivos perdidos que você não deseja.

Para contornar o primeiro problema, você pode simplesmente “tocar” no CMakeLists.txt que faz o glob, usando o comando touch ou gravando o arquivo sem alterações. Isso forçará o cmake a executar novamente e pegar o novo arquivo.

Para consertar o segundo problema, você pode organizar seu código cuidadosamente em diretórios, o que provavelmente fará de qualquer maneira. No pior dos casos, você pode usar o comando list (REMOVE_ITEM) para limpar a lista globbed de arquivos:

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

A única situação real em que isso pode te atrapalhar é se você estiver usando algo como o git-bisect para testar versões mais antigas do seu código no mesmo diretório de compilation. Nesse caso, talvez seja necessário limpar e compilar mais do que o necessário para garantir que você obtenha os arquivos corretos na lista. Este é um caso de canto, e um onde você já está em seus dedos do pé, que não é realmente um problema.

A melhor maneira de especificar arquivos de origem no CMake é listando-os explicitamente .

Os criadores do CMake recomendam não usar globbing.

Veja: http://www.cmake.org/cmake/help/v3.3/command/file.html?highlight=glob#file

(Não é recomendável usar o GLOB para coletar uma lista de arquivos de origem da tree de origem. Se nenhum arquivo CMakeLists.txt for alterado quando uma origem for adicionada ou removida, o sistema de construção gerado não poderá saber quando solicitar que o CMake seja gerado novamente.)

Claro, você pode querer saber quais são as desvantagens – continue a ler!


Quando a globulação falha:

A grande desvantagem do globbing é que criar / excluir arquivos não atualiza automaticamente o sistema de criação.

Se você é a pessoa adicionando os arquivos, isso pode parecer um compromisso aceitável, no entanto isso causa problemas para outras pessoas construindo seu código, eles atualizam o projeto do version control, executam o build, entram em contato com você, reclamam que
“a construção está quebrada”.

Para piorar a situação, a falha normalmente dá algum erro de binding que não dá nenhuma pista para a causa do problema e o tempo é perdido para resolvê-lo.

Em um projeto em que trabalhei, começamos a globbing, mas recebemos muitas reclamações quando novos arquivos foram adicionados, o que era motivo suficiente para listar explicitamente os arquivos em vez de globbing.

Isso também quebra streams de trabalho comuns do git
( git bisect e alternar entre ramos de recurso).

Então, eu não poderia recomendar isso, os problemas que causam superam em muito a conveniência, quando alguém não pode construir seu software por causa disso, eles podem perder muito tempo para rastrear o problema ou simplesmente desistir.

E outra nota, só de lembrar de tocar CMakeLists.txt nem sempre é suficiente, com compilações automatizadas que usam globbing, eu tive que rodar o cmake antes de cada build, já que os arquivos podem ter sido adicionados / removidos desde o último build *.

Exceções à regra:

Há momentos em que globbing é preferível:

  • Para configurar um arquivo CMakeLists.txt para projetos existentes que não usam o CMake.
    É uma maneira rápida de obter todas as fonts referenciadas (uma vez que o sistema de compilation está sendo executado – substitua globbing por listas de arquivos explícitas).
  • Quando o CMake não é usado como o sistema de compilation principal , se, por exemplo, você estiver usando um projeto que não esteja usando o CMake e quiser manter seu próprio sistema de compilation para ele.
  • Para qualquer situação em que a lista de arquivos é alterada com tanta frequência que se torna impraticável manter. Nesse caso, pode ser útil, mas você precisa aceitar a execução do cmake para gerar arquivos de construção toda vez para obter uma construção confiável / correta (que vai contra a intenção do CMake – a capacidade de dividir a configuração do edifício) .

* Sim, eu poderia ter escrito um código para comparar a tree de arquivos no disco antes e depois de uma atualização, mas isso não é uma solução tão agradável e algo melhor deixado para o sistema de compilation.

Você pode seguramente glob (e provavelmente deve) ao custo de um arquivo adicional para manter as dependencies.

Adicione funções como estas em algum lugar:

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

E depois vai globbing:

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

Você ainda está contornando as dependencies explícitas (e acionando todas as compilações automatizadas!) Como antes, apenas em dois arquivos em vez de um.

A única mudança no procedimento é depois que você criou um novo arquivo. Se você não glob o stream de trabalho é modificar CMakeLists.txt de dentro do Visual Studio e reconstruir, se você fizer glob você executar cmake explicitamente – ou basta tocar CMakeLists.txt.

Especifique cada arquivo individualmente!

Eu uso um CMakeLists.txt convencional e um script python para atualizá-lo. Eu corro o script python manualmente depois de adicionar arquivos.

Veja minha resposta aqui: https://stackoverflow.com/a/48318388/3929196