Como o CMake é usado? [fechadas]

95

É notoriamente difícil obter informações úteis sobre o CMake como um iniciante. Até agora, vi alguns tutoriais sobre como configurar um ou outro projeto muito básico. No entanto, nenhum deles explica o raciocínio por trás de tudo o que é mostrado neles, sempre deixando muitos buracos para preencher.

O que chama CMake em um CMakeLists significa ? É suposto ser chamado uma vez por árvore de construção ou o quê? Como faço para usar configurações diferentes para cada compilação se todos eles usam o mesmo arquivo CMakeLists.txt da mesma fonte? Por que cada subdiretório precisa de seu próprio arquivo CMakeLists? Faria sentido usar o CMake em um CMakeLists.txt diferente daquele na raiz do projeto? Em caso afirmativo, em que casos? Qual é a diferença entre especificar como construir um executável ou biblioteca do arquivo CMakeLists em seu próprio subdiretório e fazer isso no arquivo CMakeLists na raiz de todas as fontes? Posso fazer um projeto para Eclipse e outro para Visual Studio, mudando apenas a -Gopção ao chamar o CMake? É assim mesmo que é usado?

Nenhum dos tutoriais, páginas de documentação ou perguntas / respostas que vi até agora fornecem qualquer visão útil para a compreensão de como usar o CMake. Os exemplos simplesmente não são completos. Não importa quais tutoriais eu leio, sinto que estou perdendo algo importante.

Existem muitas perguntas feitas por novatos do CMake como eu que não perguntam isso explicitamente, mas tornam óbvio o fato de que, como novatos, não temos ideia de como lidar com o CMake ou o que fazer com ele.

leinaD_natipaC
fonte
8
Talvez você deva escrever uma postagem no blog em vez disso.
steveire
2
Estou tentado a fechar o voto "amplo demais" ... Isso parece mais um ensaio pessoal sobre CMake do que um bom ajuste para stackoverflow. Por que você não colocou todas essas perguntas em perguntas separadas? E como é a sua pergunta de resposta + diferente de muitos outros, por exemplo stackoverflow.com/q/20884380/417197 , stackoverflow.com/q/4745996/417197 , stackoverflow.com/q/4506193/417197 ?
André
13
@Andre Por que esta pergunta: Passei uma semana tentando entender o CMake e nenhum tutorial que encontrei estava completo. Esses links que você apontou são bons, mas para alguém que encontrou o CMake porque recebeu um projeto com um monte de CMakeLists dentro, eles não esclarecem muito como o CMake funciona. Com toda a honestidade, tentei escrever uma resposta que gostaria de poder enviar para mim mesmo quando comecei a tentar aprender CMake. E as subquestões pretendem mostrar que estou realmente tentando perguntar o que é que devo saber para não ter essas dúvidas. Fazer a pergunta certa é DIFÍCIL.
leinaD_natipaC
2
Não é uma resposta, mas convido você a dar uma olhada em jaws.rootdirectory.de . Considerando que a documentação do CMake (e a resposta aceita ...) mostra muitos trechos pequenos do que você poderia fazer, eu escrevi uma configuração básica (de forma alguma "completa" ou "perfeita"), incluindo comentários, que IMHO mostra o que CMake (e CTest e CPack e CDash ...) podem fazer por você. Ele vem pronto para uso e você pode facilmente adaptá-lo / estendê-lo às necessidades do seu projeto.
DevSolar de
9
É uma pena que essas questões sejam encerradas hoje em dia. Eu acho que questões amplas que requerem respostas como tutoriais, mas são sobre tópicos mal / obscuramente documentados (outro exemplo seria a Torch7 C api) e questões de nível de pesquisa devem permanecer abertas. Esses são bem mais interessantes que o típico "ei, tenho um segfault ao fazer algum projeto de brinquedo" que assola o site.
Ash

Respostas:

137

Para que serve o CMake?

De acordo com a Wikipedia:

CMake é um [...] software para gerenciar o processo de construção de software usando um método independente do compilador. Ele é projetado para oferecer suporte a hierarquias de diretório e aplicativos que dependem de várias bibliotecas. É usado em conjunto com ambientes de construção nativos, como make, Apple's Xcode e Microsoft Visual Studio.

Com CMake, você não precisa mais manter configurações separadas específicas para seu ambiente de compilação / construção. Você tem uma configuração que funciona para muitos ambientes.

CMake pode gerar uma solução Microsoft Visual Studio, um projeto Eclipse ou um labirinto Makefile a partir dos mesmos arquivos sem alterar nada neles.

Dado um monte de diretórios com código neles, CMake gerencia todas as dependências, pedidos de construção e outras tarefas que seu projeto precisa ser feito antes de poder ser compilado. Na verdade, ele NÃO compila nada. Para usar o CMake, você deve dizer a ele (usando arquivos de configuração chamados CMakeLists.txt) quais executáveis ​​você precisa compilar, a quais bibliotecas eles se vinculam, quais diretórios existem em seu projeto e o que está dentro deles, bem como quaisquer detalhes como sinalizadores ou qualquer outra coisa que você precise (CMake é bastante poderoso).

Se estiver configurado corretamente, você usa o CMake para criar todos os arquivos que seu "ambiente de construção nativo" de escolha precisa para fazer seu trabalho. No Linux, por padrão, isso significa Makefiles. Assim, uma vez que você execute o CMake, ele criará vários arquivos para seu próprio uso, além de alguns programas Makefile. Tudo o que você precisa fazer depois disso é digitar "make" no console a partir da pasta raiz toda vez que terminar de editar seu código e, bam, um executável compilado e vinculado é feito.

Como funciona o CMake? O que isso faz?

Aqui está um exemplo de configuração de projeto que usarei ao longo de:

simple/
  CMakeLists.txt
  src/
    tutorial.cxx
    CMakeLists.txt
  lib/
    TestLib.cxx
    TestLib.h
    CMakeLists.txt
  build/

O conteúdo de cada arquivo é mostrado e discutido posteriormente.

CMake configura seu projeto de acordo com a raiz CMakeLists.txt do seu projeto, e o faz em qualquer diretório que você executou cmakeno console. Fazer isso de uma pasta que não é a raiz de seu projeto produz o que é chamado de compilação fora do código-fonte , o que significa que os arquivos criados durante a compilação (arquivos obj, arquivos lib, executáveis, você sabe) serão colocados nessa pasta , mantido separado do código real. Ajuda a reduzir a desordem e também é preferido por outros motivos, que não discutirei.

Não sei o que acontece se você executar cmakeem qualquer outro que não seja o root CMakeLists.txt.

Neste exemplo, como quero que tudo seja colocado dentro da build/pasta, primeiro tenho que navegar até lá e depois passar ao CMake o diretório no qual CMakeLists.txtreside a raiz .

cd build
cmake ..

Por padrão, isso configura tudo usando Makefiles como eu disse. Esta é a aparência da pasta de construção agora:

simple/build/
  CMakeCache.txt
  cmake_install.cmake
  Makefile
  CMakeFiles/
    (...)
  src/
    CMakeFiles/
      (...)
    cmake_install.cmake
    Makefile
  lib/
    CMakeFiles/
      (...)
    cmake_install.cmake
    Makefile

O que são todos esses arquivos? A única coisa com que você precisa se preocupar é o Makefile e as pastas do projeto .

Observe as pastas src/e lib/. Eles foram criados porque simple/CMakeLists.txtapontam para eles usando o comando add_subdirectory(<folder>). Este comando diz ao CMake para procurar outro CMakeLists.txtarquivo na referida pasta e executar esse script, de modo que cada subdiretório adicionado desta forma deve ter um CMakeLists.txtarquivo dentro. Neste projeto, simple/src/CMakeLists.txtdescreve como construir o executável real e simple/lib/CMakeLists.txtdescreve como construir a biblioteca. Cada destino que um CMakeLists.txtdescreve será colocado por padrão em seu subdiretório dentro da árvore de construção. Então, depois de um rápido

make

no console feito de build/, alguns arquivos são adicionados:

simple/build/
  (...)
  lib/
    libTestLib.a
    (...)
  src/
    Tutorial
    (...)

O projeto está construído e o executável está pronto para ser executado. O que você faz se quiser que os executáveis ​​sejam colocados em uma pasta específica? Defina a variável CMake apropriada ou altere as propriedades de um destino específico . Mais sobre variáveis ​​CMake posteriormente.

Como posso dizer ao CMake como construir meu projeto?

Aqui está o conteúdo, explicado, de cada arquivo no diretório de origem:

simple/CMakeLists.txt:

cmake_minimum_required(VERSION 2.6)

project(Tutorial)

# Add all subdirectories in this project
add_subdirectory(lib)
add_subdirectory(src)

A versão mínima necessária deve sempre ser definida, de acordo com o aviso que o CMake lança quando você não o faz. Use qualquer que seja a sua versão do CMake.

O nome do seu projeto pode ser usado posteriormente e indica o fato de que você pode gerenciar mais de um projeto a partir dos mesmos arquivos CMake. Não vou me aprofundar nisso, no entanto.

Como mencionado antes, add_subdirectory()adiciona uma pasta ao projeto, o que significa que o CMake espera que ele tenha um CMakeLists.txtdentro, que será executado antes de continuar. A propósito, se acontecer de você ter uma função CMake definida, você pode usá-la de outros CMakeLists.txts em subdiretórios, mas você tem que defini-la antes de usar add_subdirectory()ou ela não a encontrará. O CMake é mais inteligente com relação a bibliotecas, portanto, esta é provavelmente a única vez em que você encontrará esse tipo de problema.

simple/lib/CMakeLists.txt:

add_library(TestLib TestLib.cxx)

Para fazer sua própria biblioteca, você dá um nome a ela e lista todos os arquivos a partir dos quais ela foi construída. Direto. Se fosse necessário outro arquivo foo.cxx,, para ser compilado, você deveria escrever add_library(TestLib TestLib.cxx foo.cxx). Isso também funciona para arquivos em outros diretórios, por exemplo add_library(TestLib TestLib.cxx ${CMAKE_SOURCE_DIR}/foo.cxx). Mais sobre a variável CMAKE_SOURCE_DIR posteriormente.

Outra coisa que você pode fazer com isso é especificar que deseja uma biblioteca compartilhada. O exemplo: add_library(TestLib SHARED TestLib.cxx). Não tenha medo, é aqui que CMake começa a tornar sua vida mais fácil. Quer seja compartilhada ou não, agora tudo que você precisa fazer para usar uma biblioteca criada dessa maneira é o nome que você deu a ela aqui. O nome desta biblioteca agora é TestLib e você pode fazer referência a ela de qualquer lugar no projeto. CMake vai encontrar.

Existe uma maneira melhor de listar dependências? Definitivamente sim . Verifique abaixo para mais informações sobre isso.

simple/lib/TestLib.cxx:

#include <stdio.h>

void test() {
  printf("testing...\n");
}

simple/lib/TestLib.h:

#ifndef TestLib
#define TestLib

void test();

#endif

simple/src/CMakeLists.txt:

# Name the executable and all resources it depends on directly
add_executable(Tutorial tutorial.cxx)

# Link to needed libraries
target_link_libraries(Tutorial TestLib)

# Tell CMake where to look for the .h files
target_include_directories(Tutorial PUBLIC ${CMAKE_SOURCE_DIR}/lib)

O comando add_executable()funciona exatamente da mesma forma add_library(), exceto, é claro, que irá gerar um executável. Este executável agora pode ser referenciado como um destino para coisas como target_link_libraries(). Como tutorial.cxx usa o código encontrado na biblioteca TestLib, você aponta isso para o CMake conforme mostrado.

Da mesma forma, qualquer arquivo .h # incluído por qualquer fonte add_executable()que não esteja no mesmo diretório da fonte deve ser adicionado de alguma forma. Se não fosse pelo target_include_directories()comando, lib/TestLib.hnão seria encontrado ao compilar o Tutorial, então a lib/pasta inteira é adicionada aos diretórios de inclusão a serem pesquisados ​​por #includes. Você também pode ver o comando include_directories()que age de maneira semelhante, exceto que não precisa que você especifique um destino, já que o define globalmente, para todos os executáveis. Mais uma vez, explicarei CMAKE_SOURCE_DIR mais tarde.

simple/src/tutorial.cxx:

#include <stdio.h>
#include "TestLib.h"
int main (int argc, char *argv[])
{
  test();
  fprintf(stdout, "Main\n");
  return 0;
}

Observe como o arquivo "TestLib.h" está incluído. Não há necessidade de incluir o caminho completo: CMake cuida de tudo isso nos bastidores graças a target_include_directories().

Tecnicamente falando, em uma árvore de código-fonte simples como essa você pode fazer sem o CMakeLists.txts sob lib/e src/e apenas adicionar algo semelhante add_executable(Tutorial src/tutorial.cxx)a simple/CMakeLists.txt. Depende de você e das necessidades do seu projeto.

O que mais devo saber para usar corretamente o CMake?

(Tópicos também relevantes para a sua compreensão)

Encontrar e usar pacotes : a resposta a esta pergunta explica isso melhor do que eu jamais poderia.

Declarando variáveis ​​e funções, usando controle de fluxo, etc .: confira este tutorial que explica o básico do que o CMake tem a oferecer, além de ser uma boa introdução em geral.

Variáveis ​​CMake : existem muitas, então o que se segue é um curso intensivo para colocá-lo no caminho certo. O wiki CMake é um bom lugar para obter informações mais detalhadas sobre variáveis e, aparentemente, outras coisas também.

Você pode querer editar algumas variáveis ​​sem reconstruir a árvore de construção. Use ccmake para isso (edite o CMakeCache.txtarquivo). Lembre-se de cconfigurar quando terminar as mudanças e, em seguida, ggerar makefiles com a configuração atualizada.

Leia o tutorial mencionado anteriormente para aprender sobre o uso de variáveis, mas é uma longa história curta: set(<variable name> value)para alterar ou criar uma variável. ${<variable name>}para usá-lo.

  • CMAKE_SOURCE_DIR: O diretório raiz da fonte. No exemplo anterior, isso é sempre igual a/simple
  • CMAKE_BINARY_DIR: O diretório raiz da construção. No exemplo anterior, isso é igual a simple/build/, mas se você executou cmake simple/de uma pasta como foo/bar/etc/, todas as referências a CMAKE_BINARY_DIRnessa árvore de construção se tornariam /foo/bar/etc.
  • CMAKE_CURRENT_SOURCE_DIR: O diretório no qual a corrente CMakeLists.txtestá. Isso significa que ela muda: imprimindo a partir das simple/CMakeLists.txtsafras /simplee imprimindo a partir das simple/src/CMakeLists.txtsafras /simple/src.
  • CMAKE_CURRENT_BINARY_DIR: Você entendeu a ideia. Esse caminho dependeria não apenas da pasta em que o build está, mas também CMakeLists.txtda localização do script atual .

Por que isso é importante? Os arquivos fonte obviamente não estarão na árvore de construção. Se você tentar algo como target_include_directories(Tutorial PUBLIC ../lib)no exemplo anterior, esse caminho será relativo à árvore de construção, ou seja, será como escrever ${CMAKE_BINARY_DIR}/lib, que olhará para dentro simple/build/lib/. Não há arquivos .h lá; no máximo você encontrará libTestLib.a. Você quer em ${CMAKE_SOURCE_DIR}/libvez disso.

  • CMAKE_CXX_FLAGS: Sinalizadores para passar para o compilador, neste caso o compilador C ++. Também vale a pena observar CMAKE_CXX_FLAGS_DEBUGqual será usado se CMAKE_BUILD_TYPEestiver definido como DEBUG. Existem mais como estes; verifique o wiki CMake .
  • CMAKE_RUNTIME_OUTPUT_DIRECTORY: Diga ao CMake onde colocar todos os executáveis ​​quando compilados. Esta é uma configuração global. Você pode, por exemplo, configurá-lo bin/e ter tudo bem colocado lá. EXECUTABLE_OUTPUT_PATHé semelhante, mas obsoleto, caso você tropece nele.
  • CMAKE_LIBRARY_OUTPUT_DIRECTORY: Da mesma forma, uma configuração global para dizer ao CMake onde colocar todos os arquivos de biblioteca.

Propriedades do destino : você pode definir propriedades que afetam apenas um destino, seja um executável ou uma biblioteca (ou um arquivo ... essa é a ideia). Aqui está um bom exemplo de como usá-lo (com set_target_properties().

Existe uma maneira fácil de adicionar fontes a um destino automaticamente? Use GLOB para listar tudo em um determinado diretório sob a mesma variável. A sintaxe de exemplo é FILE(GLOB <variable name> <directory>/*.cxx).

Você pode especificar diferentes tipos de construção? Sim, embora eu não tenha certeza de como isso funciona ou das limitações disso. Provavelmente requer algum if / then'ning, mas o CMake oferece algum suporte básico sem configurar nada, como padrões para o CMAKE_CXX_FLAGS_DEBUG, por exemplo. Você pode definir seu tipo de construção de dentro do CMakeLists.txtarquivo via set(CMAKE_BUILD_TYPE <type>)ou chamando CMake do console com os sinalizadores apropriados, por exemplo cmake -DCMAKE_BUILD_TYPE=Debug.

Algum bom exemplo de projetos que usam CMake? A Wikipedia tem uma lista de projetos de código aberto que usam CMake, se você quiser dar uma olhada nisso. Os tutoriais online não têm sido nada além de uma decepção para mim a esse respeito, no entanto, esta questão do Stack Overflow tem uma configuração de CMake muito legal e fácil de entender. Vale a pena dar uma olhada.

Usando variáveis ​​do CMake em seu código : Aqui está um exemplo rápido e sujo (adaptado de algum outro tutorial ):

simple/CMakeLists.txt:

project (Tutorial)

# Setting variables
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 1)

# Configure_file(<input> <output>)
# Copies a file <input> to file <output> and substitutes variable values referenced in the file content.
# So you can pass some CMake variables to the source code (in this case version numbers)
configure_file (
  "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
  "${PROJECT_SOURCE_DIR}/src/TutorialConfig.h"
)

simple/TutorialConfig.h.in:

// Configured options and settings
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

O arquivo resultante gerado pelo CMake, simple/src/TutorialConfig.h:

// Configured options and settings
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 1

Com o uso inteligente deles, você pode fazer coisas legais como desligar uma biblioteca e tal. Eu recomendo dar uma olhada nesse tutorial, pois há algumas coisas um pouco mais avançadas que serão muito úteis em projetos maiores, mais cedo ou mais tarde.

Para todo o resto, Stack Overflow está repleto de perguntas específicas e respostas concisas, o que é ótimo para todos, exceto para os não iniciados.

leinaD_natipaC
fonte
2
Estou aceitando essa resposta, mas se outra pessoa escrever uma resposta melhor e mais curta, eu escolho essa. Eu faria isso sozinho, mas já sofri o suficiente com o CMake.
leinaD_natipaC
6
Obrigado por este trabalho incrível. Espero que isso consiga mais votos! :)
VAMOS,
4
O ponto bastante discreto aqui: com o CMake, você não precisa mais manter configurações separadas específicas para seu ambiente de compilação / compilação. Você tem uma configuração e ela funciona para (várias versões do) Visual Studio, Code Blocks, Makefiles Unix simples e muitos outros ambientes. É por isso que eu estava olhando para o CMake em primeiro lugar: tornou-se um grande problema manter as configurações do Autoconf, MSVC 2005/32 bits e MSVC 2010/64 bits em sincronia. E assim que peguei o jeito, adicionei muito mais coisas como geração de documentos, testes, empacotamento, etc., todos eles de uma forma multiplataforma.
DevSolar de
1
@DevSolar True. Gosto dessa linha mais do que da wikipedia, então vou adicioná-la à resposta se você não se importar. Não vou inserir nada sobre como usar o CMake corretamente para esse efeito. Trata-se mais de ser capaz de dizer o que está acontecendo.
leinaD_natipaC
1
@RichieHH Seguindo o exemplo, você primeiro configura o CMake para usar Makefiles, então usa o CMake para gerar os arquivos que seu projeto precisa ser construído e, finalmente, você para de se preocupar com o CMake porque seu trabalho aqui está concluído. Então, você "se preocupa" com o Makefile no sentido de que, de agora em diante, precisará usar makeno bash para, bem, fazer seu projeto.
leinaD_natipaC