O exemplo CMake mais simples, mas completo

117

De alguma forma, estou totalmente confuso com a forma como o CMake funciona. Cada vez que penso que estou chegando mais perto de entender como CMake deve ser escrito, ele desaparece no próximo exemplo que leio. Tudo que eu quero saber é como devo estruturar meu projeto, para que meu CMake exija o mínimo de manutenção no futuro. Por exemplo, não quero atualizar meu CMakeList.txt quando estou adicionando uma nova pasta em minha árvore src, que funciona exatamente como todas as outras pastas src.

É assim que imagino a estrutura do meu projeto, mas, por favor, este é apenas um exemplo. Se a forma recomendada for diferente, diga-me e diga-me como fazê-lo.

myProject
    src/
        module1/
            module1.h
            module1.cpp
        module2/
            [...]
        main.cpp
    test/
        test1.cpp
    resources/
        file.png
    bin
        [execute cmake ..]

A propósito, é importante que meu programa saiba onde estão os recursos. Gostaria de saber a forma recomendada de gerenciamento de recursos. Não desejo acessar meus recursos com "../resources/file.png"

Arne
fonte
1
For example I don't want to update my CMakeList.txt when I am adding a new folder in my src treevocê pode dar um exemplo de IDE que coleta fontes automaticamente?
7
nenhum ide normalmente não coleta fontes automaticamente, porque eles não precisam. Quando adiciono um novo arquivo ou pasta, faço isso dentro do ide e o projeto é atualizado. Um sistema de compilação do outro lado não percebe quando eu mudo alguns arquivos, então é um comportamento desejado que ele colete todos os arquivos de origem automaticamente
Arne
4
Se eu vir esse link, tenho a impressão de que o CMake falhou na tarefa mais importante que queria resolver: tornar fácil um sistema de compilação de plataforma cruzada.
Arne

Respostas:

94

depois de alguma pesquisa, agora tenho minha própria versão do exemplo cmake mais simples, mas completo. Aqui está, e ele tenta cobrir a maior parte do básico, incluindo recursos e pacotes.

uma coisa que ele faz fora do padrão é o manuseio de recursos. Por padrão, o cmake deseja colocá-los em / usr / share /, / usr / local / share / e algo equivalente no Windows. Eu queria ter um zip / tar.gz simples que você pudesse extrair de qualquer lugar e executar. Portanto, os recursos são carregados em relação ao executável.

a regra básica para entender os comandos cmake é a seguinte sintaxe: <function-name>(<arg1> [<arg2> ...])sem vírgula ou semicolor. Cada argumento é uma string. foobar(3.0)e foobar("3.0")é o mesmo. você pode definir listas / variáveis ​​com set(args arg1 arg2). Com esta variável definida foobar(${args}) e foobar(arg1 arg2)são efetivamente os mesmos. Uma variável inexistente é equivalente a uma lista vazia. Uma lista é internamente apenas uma string com ponto e vírgula para separar os elementos. Portanto, uma lista com apenas um elemento é, por definição, apenas esse elemento, nenhum boxing ocorre. As variáveis ​​são globais. Funções embutidas oferecem alguma forma de argumentos nomeados pelo fato de que eles esperam alguns ids como PUBLICouDESTINATIONem sua lista de argumentos, para agrupar os argumentos. Mas isso não é um recurso de linguagem, esses ids também são apenas strings e analisados ​​pela implementação da função.

você pode clonar tudo do github

cmake_minimum_required(VERSION 3.0)
project(example_project)

###############################################################################
## file globbing ##############################################################
###############################################################################

# these instructions search the directory tree when cmake is
# invoked and put all files that match the pattern in the variables 
# `sources` and `data`
file(GLOB_RECURSE sources      src/main/*.cpp src/main/*.h)
file(GLOB_RECURSE sources_test src/test/*.cpp)
file(GLOB_RECURSE data resources/*)
# you can use set(sources src/main.cpp) etc if you don't want to
# use globing to find files automatically

###############################################################################
## target definitions #########################################################
###############################################################################

# add the data to the target, so it becomes visible in some IDE
add_executable(example ${sources} ${data})

# just for example add some compiler flags
target_compile_options(example PUBLIC -std=c++1y -Wall -Wfloat-conversion)

# this lets me include files relative to the root src dir with a <> pair
target_include_directories(example PUBLIC src/main)

# this copies all resource files in the build directory
# we need this, because we want to work with paths relative to the executable
file(COPY ${data} DESTINATION resources)

###############################################################################
## dependencies ###############################################################
###############################################################################

# this defines the variables Boost_LIBRARIES that contain all library names
# that we need to link to
find_package(Boost 1.36.0 COMPONENTS filesystem system REQUIRED)

target_link_libraries(example PUBLIC
  ${Boost_LIBRARIES}
  # here you can add any library dependencies
)

###############################################################################
## testing ####################################################################
###############################################################################

# this is for our testing framework
# we don't add REQUIRED because it's just for testing
find_package(GTest)

if(GTEST_FOUND)
  add_executable(unit_tests ${sources_test} ${sources})

  # we add this define to prevent collision with the main
  # this might be better solved by not adding the source with the main to the
  # testing target
  target_compile_definitions(unit_tests PUBLIC UNIT_TESTS)

  # this allows us to use our executable as a link library
  # therefore we can inherit all compiler options and library dependencies
  set_target_properties(example PROPERTIES ENABLE_EXPORTS on)

  target_link_libraries(unit_tests PUBLIC
    ${GTEST_BOTH_LIBRARIES}
    example
  )

  target_include_directories(unit_tests PUBLIC
    ${GTEST_INCLUDE_DIRS} # doesn't do anything on Linux
  )
endif()

###############################################################################
## packaging ##################################################################
###############################################################################

# all install commands get the same destination. this allows us to use paths
# relative to the executable.
install(TARGETS example DESTINATION example_destination)
# this is basically a repeat of the file copy instruction that copies the
# resources in the build directory, but here we tell cmake that we want it
# in the package
install(DIRECTORY resources DESTINATION example_destination)

# now comes everything we need, to create a package
# there are a lot more variables you can set, and some
# you need to set for some package types, but we want to
# be minimal here
set(CPACK_PACKAGE_NAME "MyExample")
set(CPACK_PACKAGE_VERSION "1.0.0")

# we don't want to split our program up into several things
set(CPACK_MONOLITHIC_INSTALL 1)

# This must be last
include(CPack)
Arne
fonte
8
@SteveLorimer Eu apenas discordo, esse arquivo globbing é um estilo ruim, acho que copiar manualmente a árvore de arquivos para o CMakeLists.txt é um estilo ruim porque é redundante. Mas eu sei que as pessoas discordam neste tópico, portanto deixei um comentário no código, onde você pode substituir o globbing por uma lista que contém todos os arquivos de origem explicitamente. Pesquise set(sources src/main.cpp).
Arne
3
@SteveLorimer sim, muitas vezes eu tive que invocar cmake novamente. Sempre que adiciono algo na árvore de diretórios, preciso reinvocar o cmake manualmente, para que o get globbing seja reavaliado. Se você colocar os arquivos no CMakeLists.txt, então um make normal (ou ninja) irá acionar a reinvocação do cmake, então você não pode esquecê-lo. Também é um pouco mais amigável com a equipe, porque assim os membros da equipe também não podem se esquecer de executar o cmake. Mas eu acho que um makefile não precisa ser alterado, só porque alguém adicionou um arquivo. Escreva uma vez e ninguém precisará pensar sobre isso nunca mais.
Arne
3
@SteveLorimer Eu também discordo do padrão de colocar um CMakeLists.txt em todos os diretórios dos projetos, ele apenas espalha a configuração do projeto por todos os lados, acho que um arquivo para fazer tudo isso deve ser suficiente, caso contrário você perde a visão geral, do que é realmente feito no processo de construção. Isso não significa que não pode haver subdiretórios com seus próprios CMakeLists.txt, só acho que deve ser uma exceção.
Arne
2
Assumindo que "VCS" é uma abreviação de "sistema de controle de versão" , isso é irrelevante. O problema não é que os artefatos não serão adicionados ao controle de origem. O problema é que o CMake não conseguirá reavaliar os arquivos de origem adicionados. Ele não irá regenerar os arquivos de entrada do sistema de compilação. O sistema de compilação ficará feliz com os arquivos de entrada desatualizados, levando a erros (se você tiver sorte) ou passando despercebido, se você ficar sem sorte. GLOBbing produz uma lacuna na cadeia de cálculo de dependência. Este é um problema significativo e um comentário não reconhece isso de forma adequada.
Inspecionável
2
O CMake e um VCS operam em completo isolamento. O VCS não tem conhecimento do CMake e o CMake não tem conhecimento de nenhum VCS. Não há ligação entre eles. A menos que você sugira que os desenvolvedores devem seguir etapas manuais, retirando informações do VCS e com base em alguma heurística limpa e reexecutada CMake. Isso não escala, obviamente, e é suscetível à falácia peculiar aos humanos. Não, desculpe, você não fez um argumento convincente para arquivos GLOBbing até agora.
Inspecionável
39

O exemplo mais básico, mas completo, pode ser encontrado no tutorial CMake :

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable(Tutorial tutorial.cxx)

Para o exemplo do seu projeto, você pode ter:

cmake_minimum_required (VERSION 2.6)
project (MyProject)
add_executable(myexec src/module1/module1.cpp src/module2/module2.cpp src/main.cpp)
add_executable(mytest test1.cpp)

Para sua pergunta adicional, uma maneira de prosseguir é novamente no tutorial: crie um arquivo de cabeçalho configurável que você inclui em seu código. Para isso, faça um arquivo configuration.h.incom o seguinte conteúdo:

#define RESOURCES_PATH "@RESOURCES_PATH@"

Em seguida, em seu CMakeLists.txtadd:

set(RESOURCES_PATH "${PROJECT_SOURCE_DIR}/resources/"
# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
  "${PROJECT_SOURCE_DIR}/configuration.h.in"
  "${PROJECT_BINARY_DIR}/configuration.h"
)

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")

Finalmente, onde precisar do caminho em seu código, você pode fazer:

#include "configuration.h"

...

string resourcePath = string(RESOURCE_PATH) + "file.png";
sgvd
fonte
muito obrigado, principalmente pelo RESOURCE_PATH, de alguma forma não entendi que configure_file é o que estava procurando. Mas você adicionou todos os arquivos do projeto manualmente. Existe uma maneira melhor de simplesmente definir um padrão no qual todos os arquivos são adicionados da árvore src?
Arne
Veja a resposta de Dieter, mas também meus comentários sobre por que você não deve usá-lo. Se você realmente deseja automatizá-lo, uma abordagem melhor pode ser escrever um script que você pode executar para gerar novamente a lista de arquivos de origem (ou usar um IDE ciente do cmake que faz isso para você; não estou familiarizado com nenhum).
sgvd de
3
@sgvd string resourcePath = string(RESOURCE_PATH) + "file.png"IMHO, é uma má ideia codificar o caminho absoluto para o diretório de origem. E se você precisar instalar seu projeto?
2
Eu sei que reunir fontes automaticamente parece bom, mas pode levar a todos os tipos de complicações. Veja esta pergunta de um tempo atrás para uma breve discussão: stackoverflow.com/q/10914607/1401351 .
Peter
2
Você obterá exatamente o mesmo erro se não executar o cmake; adicionar arquivos manualmente leva um segundo uma vez, rodando cmake a cada compilação leva um segundo a cada vez; você realmente quebra um recurso do cmake; alguém que trabalhe no mesmo projeto e obtenha suas alterações faria: executa make -> obter referências indefinidas -> espero que lembre-se de executar cmake novamente ou bug de arquivos com você -> executa cmake -> executa make com sucesso, enquanto se você adicionar arquivo à mão ele faz: executa, faz com sucesso -> passa o tempo com a família. Resumindo, não seja preguiçoso e poupe a si mesmo e aos outros de uma dor de cabeça no futuro.
sgvd de
2

Aqui, escrevo um exemplo de arquivos CMakeLists.txt mais simples, mas completo.

Código fonte

  1. Tutoriais de hello world para plataforma cruzada Android / iOS / Web / Desktop.
  2. Cada plataforma lançou um aplicativo de amostra.
  3. A estrutura do arquivo 08-cross_platform é verificada pelo meu trabalho
  4. Pode não ser perfeito, mas útil e a melhor prática para uma equipe sozinha

Depois disso, ofereci documento para os detalhes.

Se você tiver alguma dúvida, pode entrar em contato comigo e eu gostaria de explicar.

MinamiTouma
fonte