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 cmake
no 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 cmake
em 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.txt
reside 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.txt
apontam para eles usando o comando add_subdirectory(<folder>)
. Este comando diz ao CMake para procurar outro CMakeLists.txt
arquivo na referida pasta e executar esse script, de modo que cada subdiretório adicionado desta forma deve ter um CMakeLists.txt
arquivo dentro. Neste projeto, simple/src/CMakeLists.txt
descreve como construir o executável real e simple/lib/CMakeLists.txt
descreve como construir a biblioteca. Cada destino que um CMakeLists.txt
descreve 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.txt
dentro, 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.txt
s 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.h
nã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.txt
s 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.txt
arquivo). Lembre-se de c
configurar quando terminar as mudanças e, em seguida, g
gerar 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_DIR
nessa árvore de construção se tornariam /foo/bar/etc
.
CMAKE_CURRENT_SOURCE_DIR
: O diretório no qual a corrente CMakeLists.txt
está. Isso significa que ela muda: imprimindo a partir das simple/CMakeLists.txt
safras /simple
e imprimindo a partir das simple/src/CMakeLists.txt
safras /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.txt
da 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}/lib
vez disso.
CMAKE_CXX_FLAGS
: Sinalizadores para passar para o compilador, neste caso o compilador C ++. Também vale a pena observar CMAKE_CXX_FLAGS_DEBUG
qual será usado se CMAKE_BUILD_TYPE
estiver 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.txt
arquivo 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.