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


157

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

FILE(GLOB MY_SRCS dir/*)

Outro método é especificar cada arquivo individualmente.

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

Respostas:


185

Divulgação completa: originalmente eu preferia a abordagem globbing por sua simplicidade, mas ao longo dos anos reconheci que listar explicitamente os arquivos é menos suscetível a erros em projetos grandes e com vários desenvolvedores.

Resposta original:


As vantagens do globbing são:

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

  • 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 do CMake entre grandes listas de arquivos.

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

  • O CMake rastreará as dependências de um novo arquivo no disco corretamente - se usarmos glob, os arquivos que não apareceram na primeira vez em que você executou o CMake não serão recuperados

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

Para solucionar o primeiro problema, você pode simplesmente "tocar" o CMakeLists.txt que faz a glob, usando o comando touch ou escrevendo o arquivo sem alterações. Isso forçará o CMake a executar novamente e buscar o novo arquivo.

Para corrigir o segundo problema, você pode organizar seu código cuidadosamente em diretórios, que é o que você provavelmente faz de qualquer maneira. Na pior das hipóteses, você pode usar o list(REMOVE_ITEM)comando para limpar a lista global 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 incomodar é se você estiver usando algo como git-bisect para tentar versões mais antigas do seu código no mesmo diretório de compilação. Nesse caso, pode ser 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 esquina e onde você já está no seu pé, que não é realmente um problema.


1
Também é ruim no globbing: os arquivos difftool do git são armazenados como $ basename. $ Ext. $ Type. $ Pid. $ Ext, o que pode causar erros divertidos ao tentar compilar após uma única resolução de mesclagem.
mathstuf

9
Eu acho que essa resposta encobre as desvantagens de cmake que estão faltando novos arquivos; tudo Simply "touch" the CMakeLists.txtbem se você for o desenvolvedor, mas para outras pessoas que constroem seu software, pode ser realmente um ponto problemático que sua compilação falhe após a atualização e o ônus é deles investigar porque.
ideasman42

36
Você sabe o que? Desde que escrevi esta resposta há 6 anos , mudei de idéia um pouco e agora prefiro listar explicitamente os arquivos. Sua única desvantagem é "é um pouco mais trabalhoso adicionar um arquivo", mas economiza todo tipo de dor de cabeça. E de várias maneiras, explícito é melhor que implícito.
richq

1
@richq Esse gancho do Git faria você reconsiderar sua posição atual? :)
Antonio

8
Bem, como Antonio diz, os votos foram dados para defender a abordagem "globbing". Mudar a natureza da resposta é algo a fazer para esses eleitores. Como compromisso, adicionei uma edição para refletir minha opinião alterada. Peço desculpas à internet para causar uma tempestade em uma xícara de chá :-P
richq

113

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

Os próprios criadores do CMake aconselham a não usar globbing.

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

(Não recomendamos o uso do GLOB para coletar uma lista de arquivos de origem da sua árvore de origem. Se nenhum arquivo CMakeLists.txt for alterado quando uma fonte for adicionada ou removida, o sistema de compilação gerado não poderá saber quando pedir ao CMake para se regenerar.)

Claro, você pode querer saber quais são as desvantagens - continue lendo!


Quando o globbing falha:

A grande desvantagem do globbing é que a criação / exclusão de arquivos não atualizará automaticamente o sistema de compilação.

Se você é a pessoa que está adicionando os arquivos, isso pode parecer uma troca aceitável; no entanto, isso causa problemas para outras pessoas criarem seu código, elas atualizam o projeto do controle de versão, executam a construção e entram em contato com você, reclamando que
"a compilação está quebrado".

Para piorar a situação, a falha normalmente gera algum erro de vinculação que não fornece dicas para a causa do problema e o tempo é perdido para solucionar o problema.

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

Isso também quebra os fluxos de trabalho comuns do git
( git bisecte alterna entre ramificações de recursos).

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

E outra observação: lembrar que o toque CMakeLists.txtnem sempre é suficiente. Com compilações automatizadas que usam globbing, eu precisava executar cmakeantes de cada compilação, pois os arquivos podem ter sido adicionados / removidos desde a última compilação *.

Exceções à regra:

Há momentos em que o globbing é preferível:

  • Para configurar CMakeLists.txtarquivos para projetos existentes que não usam o CMake.
    É uma maneira rápida de obter toda a fonte de referência (uma vez que o sistema de compilação esteja funcionando - substitua o globbing por listas de arquivos explícitas).
  • Quando o CMake não é usado como o sistema de compilação primário , se, por exemplo, você está usando um projeto que não está usando o CMake, e gostaria de manter seu próprio sistema de compilação.
  • Para qualquer situação em que a lista de arquivos seja alterada com tanta frequência que se torne impraticável de manter. Nesse caso, pode ser útil, mas você deve aceitar a execução cmakepara gerar arquivos de compilação toda vez para obter uma compilação confiável / correta (o que contraria a intenção do CMake - a capacidade de dividir a configuração da compilação ) .

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


9
"A grande desvantagem do globbing é que a criação de novos arquivos não atualizará automaticamente o sistema de compilação". Mas não é verdade que, se você não faz glob, ainda precisa atualizar manualmente o CMakeLists.txt, o que significa que o cmake ainda não está atualizando automaticamente o sistema de compilação? Parece que você deve se lembrar de fazer algo manualmente para que os novos arquivos sejam criados. Tocar em CMakeLists.txt parece mais fácil do que abri-lo e editá-lo para adicionar o novo arquivo.
Dan

17
@ Dan, para o seu sistema - claro, se você desenvolver sozinho, tudo bem, mas e quanto a todos os que constroem seu projeto? você os enviará por e-mail para tocarem manualmente no arquivo CMake? toda vez que um arquivo é adicionado ou removido? - Armazenar a lista de arquivos no CMake garante que a compilação esteja sempre usando os mesmos arquivos que a Vcs conhece. Acredite em mim - este não é apenas um detalhe sutil - Quando sua compilação falha para muitos desenvolvedores - eles enviam listas de correio eletrônico e perguntam ao IRC que o código está quebrado. Nota: (Mesmo em seu próprio sistema, você pode voltar ao histórico do git, por exemplo, e não pensar em entrar e tocar nos arquivos do CMake)
ideasman42

2
Ah, eu não tinha pensado nesse caso. Essa é a melhor razão que ouvi contra globbing. Desejo que os documentos do cmake sejam expandidos sobre o motivo pelo qual eles recomendam que as pessoas evitem globbing.
Dan

1
Eu estive pensando sobre a solução de escrever carimbo de data e hora da última execução cmake em arquivo. Os únicos problemas são: 1) provavelmente tem que ser feito pelo cmake para ter uma plataforma cruzada e, portanto, precisamos evitar que o cmake funcione de alguma maneira pela segunda vez. 2) Possivelmente mais conflitos de mesclagem (que ainda acontecem com a lista de arquivos btw). Na verdade, eles poderiam ser resolvidos trivialmente nesse caso, usando o carimbo de data e hora mais tarde.
Predelnik 16/02

2
@ tim-mb, "Mas seria bom se o CMake criasse um arquivo filetree_updated que você pudesse fazer check-in, que mudaria automaticamente cada vez que o globo de arquivos fosse atualizado." - você acabou de descrever exatamente o que minha resposta faz.
Glen Knowles

22

No CMake 3.12, os comandos file(GLOB ...)efile(GLOB_RECURSE ...) ganharam uma CONFIGURE_DEPENDSopção que executa novamente cmake se o valor da glob mudar. Como essa foi a principal desvantagem de procurar por arquivos de origem, agora não há problema em fazê-lo:

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

No entanto, algumas pessoas ainda recomendam evitar procurar por fontes. De fato, a documentação afirma:

Não recomendamos o uso do GLOB para coletar uma lista de arquivos de origem da sua árvore de origem. ... O CONFIGURE_DEPENDSsinalizador pode não funcionar de maneira confiável em todos os geradores ou, se um novo gerador for adicionado no futuro e não puder suportá-lo, os projetos que o utilizarem ficarão bloqueados. Mesmo que CONFIGURE_DEPENDSfuncione de maneira confiável, ainda há um custo para executar a verificação em cada reconstrução.

Pessoalmente, considero os benefícios de não ter que gerenciar manualmente a lista de arquivos de origem para compensar as possíveis desvantagens. Se você precisar voltar para os arquivos listados manualmente, isso pode ser facilmente alcançado apenas imprimindo a lista de fontes globbed e colando-a novamente.


Se o seu sistema de compilação executa um ciclo cmake e build completo (exclua o diretório de compilação, execute cmake a partir daí e invoque o makefile), desde que eles não tragam arquivos indesejados, certamente não há desvantagens em usar fontes GLOBbed? Na minha experiência, a parte cmake é executada muito mais rapidamente que a compilação, portanto, de qualquer maneira, isso não representa muita sobrecarga
Den-Jason

9

Você pode acessar com segurança (e provavelmente deveria) pelo custo de um arquivo adicional para manter as dependências.

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 então vá globbing:

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

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

A única alteração no procedimento ocorre após a criação de um novo arquivo. Se você não usar o glob, o fluxo de trabalho é modificar o CMakeLists.txt a partir do Visual Studio e reconstruir, se você executar o cmake explicitamente - ou apenas toque em CMakeLists.txt.


No começo, pensei que era uma ferramenta que atualizaria automaticamente os Makefiles quando um arquivo de origem fosse adicionado, mas agora vejo qual é o seu valor. Agradável! Isso resolve a preocupação de alguém atualizando a partir do repositório e makedando erros estranhos no vinculador.
Cris Luengo 14/07

1
Eu acredito que este poderia ser um bom método. É claro que ainda é preciso lembrar de acionar o cmake após adicionar ou remover um arquivo, e também é necessário confirmar esse arquivo de dependência, para que seja necessária alguma educação do lado do usuário. A principal desvantagem pode ser que esse arquivo de dependência possa originar conflitos desagradáveis ​​de mesclagem que podem ser difíceis de resolver sem exigir novamente que o desenvolvedor tenha alguma compreensão do mecanismo.
Antonio

1
Isso não funcionará se o seu projeto tiver incluído arquivos condicionalmente (por exemplo, alguns arquivos que são usados ​​apenas quando um recurso está ativado ou usado apenas para um sistema operacional específico). É comum o suficiente para o software portátil que alguns arquivos sejam usados ​​apenas para plataformas específicas.
ideasman42

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.