Use bibliotecas habilitadas para CMake em seu projeto CMake (III)


Resumo:

Neste protip eu explico como nossa biblioteca pode ser incluída em outros projetos sem ter que instalá-la. Iremos gerar um arquivo de configuração que funcionará independentemente da biblioteca estar instalada ou não.

Nota: leia os artigos anteriores para contextualizar: I e II .


Se você leu meu artigo anterior e seu acompanhamento , deve ser muito fácil ter um projeto de várias bibliotecas instalado e funcionando,
apesar de todo o código clichê. Mas como podemos fazer com que seja fácil de integrar em outros projetos de várias bibliotecas? A mecânica para permitir que outras pessoas encontrem sua biblioteca são fáceis: forneça um arquivo chamado <name>-config.cmake, <name>Config.cmakeou Find<name.cmakeem seu pacote, defina variáveis ​​nele informando onde encontrar as bibliotecas, cabeçalhos, etc, e pronto. Mas como escrever um arquivo de configuração que é realocável e funciona quando você instala sua biblioteca ou quando você usa apenas suas fontes?

Esta é uma descrição do que funcionou para mim. Eu omito o código CMake não relacionado à geração do arquivo de configuração.

Implementação

Vou assumir que o pacote que estou configurando se chama “foo”. Os caminhos entre os colchetes angulares devem ser
substituídos por caminhos personalizados para os arquivos correspondentes. foo_LIBRARIEStem que ser definido também com .add_library/add_executable

Em primeiro lugar, usaremos CMakePackageConfigHelpers :

include ( CMakePackageConfigHelpers )

Este pacote nos permite escrever arquivos de configuração que podem ser realocados, ou seja, onde os caminhos não são codificados.
Em seguida, podemos escrever o arquivo de configuração que será usado se o pacote não estiver instalado :

# In my case the folder of includes can be any source folder,
# a practice very extended (in opposition to a single folder
# containing all the headers).
set ( foo_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}" )

# Destination of the installed config files (relative path):
set ( CMAKE_CONFIG_DEST "share/cmake/Modules" )

# We configure our template. The template is described later.
configure_package_config_file
(
"<PATH TO THE CONFIG TEMPLATE>/foo-config.cmake.in"
# Important to write in CMAKE_BINARY_DIR if you want the registry
# mechanism to work:
"${CMAKE_BINARY_DIR}/foo-config.cmake"
INSTALL_DESTINATION
"${CMAKE_CONFIG_DEST}"
PATH_VARS foo_INCLUDE_DIRS
)

# This file is included in our template:
export ( TARGETS foo_LIBRARIES FILE "${CMAKE_BINARY_DIR}/fooTargets.cmake" )

export ( PACKAGE foo )

Para notar que eu uso, configure_package_config_filemas o antigo configure_file teria funcionado bem aqui.

Até agora nós escrevemos apenas a parte correspondente ao uso in-source de nosso pacote, mas ainda temos que escrever o arquivo de configuração que será distribuído e instalado com nosso pacote. O processo é semelhante ao anterior, mas um pouco mais simples:

# We redefine this variable, using this time a relative path:
set ( foo_INCLUDE_DIRS "include" )

# We write in the 'export' folder in order not to collide with
# the previous config file:
configure_package_config_file
(
"<PATH TO THE CONFIG TEMPLATE>/foo-config.cmake.in"
"${CMAKE_BINARY_DIR}/export/foo-config.cmake.cmake"
INSTALL_DESTINATION
"${CMAKE_CONFIG_DEST}"
PATH_VARS foo_INCLUDE_DIRS
)

install
(
EXPORT
export
DESTINATION $
{CMAKE_CONFIG_DEST} FILE "fooTargets.cmake" )

Como você pode apreciar, as configurações in-source e instaladas são simétricas, quase iguais, mas ainda temos que usar 2 arquivos de configuração diferentes.

A única parte que falta é o modelo :foo-config.cmake.in

@PACKAGE_INIT@ 

set_and_check
( foo_INCLUDE_DIRS "@PACKAGE_foo_INCLUDE_DIRS@")

include
( "${CMAKE_CURRENT_LIST_DIR}/fooTargets.cmake" )

@PACKAGE_INIT@será inicializado por configure_package_config_filee precisamos apenas
definir as variáveis ​​do nosso projeto (usando set_and_check, definido em @PACKAGE_INIT@) e incluir nossos destinos.

Conclusão

Mostrei como podemos gerar dois arquivos de configuração, um para uso in-source e outro para uso “instalado”. No entanto, os dois arquivos usam o mesmo modelo e a criação de ambos os arquivos é quase idêntica. Espero que no futuro possamos usar o mesmo arquivo de configuração para os dois casos. Também no futuro acho que não precisaremos definir nossa variável, tornando-a ainda mais simples.*_INCLUDE_DIRS

Espero que o protocolo tenha sido útil! Como você gera seus arquivos de configuração? Pode-se fazer melhor?