A07 - Encontrando e usando dependências
Questão
Como posso usar o CMake para detectar e usar as dependências do meu projeto?
Objetivos
Aprender como usar
find_package.Saiba quais outras alternativas de detecção existem.
A grande maioria dos projetos de software não acontece no vácuo: eles terão dependências de frameworks e bibliotecas existentes. Uma boa documentação instruirá seus usuários a garantir que eles sejam satisfeitos em seu ambiente de programação. O sistema de compilação é o local apropriado para verificar se essas pré-condições são atendidas e se seu projeto pode ser construído corretamente. Neste episódio, mostraremos alguns exemplos de como detectar e usar dependências em seu sistema de compilação CMake.
Encontrando dependências
O CMake oferece uma família de comandos para encontrar artefatos instalados em seu sistema:
find_filepara recuperar o caminho completo para um arquivo.find_librarypara encontrar uma biblioteca, compartilhada ou estática.find_packagepara encontrar e carregar configurações de um projeto externo.find_pathpara encontrar o diretório que contém um arquivo.find_programpara encontrar um executável.
A principal ferramenta para a descoberta de dependência é find_package,
que cobrirá suas necessidades em quase todos os casos de uso.
find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[NO_POLICY_SCOPE])
Este comando tentará encontrar o pacote com o nome <PackageName> pesquisando
em várias pastas predefinidas.
É possível solicitar uma versão mínima ou exata. Se REQUIRED for fornecido, uma pesquisa
com falha acionará um erro fatal. As regras para a busca são obtidas em módulos chamados
Find<PackageName>.cmake.
Os pacotes também podem ter componentes e você pode pedir para detectar apenas alguns deles.
você deve somente usar os outros comandos da família find_
em circunstâncias muito especiais e restritas. Por quê?
Para uma grande variedade de dependências comuns, os módulos
Find<PackageName>.cmakeenviados com o CMake funcionam perfeitamente e são mantidos pelos desenvolvedores do CMake. Isso elimina o fardo de programar seus próprios módulos de detecção de dependência.find_packageirá configurar alvos importados: alvos definidos fora do seu projeto que podem ser usados com seus próprios alvos. As propriedades nos destinos importados definem requisitos de uso para as dependências. Um comando como:target_link_libraries(your-target PUBLIC imported-target )
definirá flags de compilador, definições, diretórios de inclusão e bibliotecas de links de
imported-targetparayour-targete para todos os outros destinos em seu projeto que usarãoyour-target.
Esses dois pontos simplificam enormemente a detecção de dependência e uso consistente em um projeto de várias pastas.
Usando find_package
Ao tentar a detecção de dependência com find_package, você deve certificar-se de que:
Um modulo
Find<PackageName>.cmakeexista,Quais componentes, se houver, ele fornece e
Quais destinos importados ele configurará.
Uma lista completa de Find<PackageName>.cmake pode ser encontrada na interface de linha de comando:
.. code-block:: bash
$ cmake –help-module-list | grep “Find”
Usando OpenMP.
Queremos compilar o seguinte código de exemplo do OpenMP: [1]
// example adapted from
// http://www.openmp.org/wp-content/uploads/openmp-examples-4.5.0.pdf page 85
#include <cstdlib>
#include <iostream>
void long_running_task(){
// do something
std::cout << "long_running_task" << std::endl;
};
void loop_body(int i, int j){
// do something
std::cout << "i = " << i << " j = " << j << std::endl;
};
void parallel_work() {
int i, j;
#pragma omp taskgroup
{
#pragma omp task
long_running_task(); // can execute concurrently
#pragma omp taskloop private(j) grainsize(500) nogroup
for (i = 0; i < 100; i++) { // can execute concurrently
for (j = 0; j < i; j++) {
loop_body(i, j);
}
}
}
}
int main() {
parallel_work();
return EXIT_SUCCESS;
}
Observe o uso da construção taskloop, que foi introduzida no OpenMP 4.5:
precisamos ter certeza de que nosso compilador C++ é compatível com pelo menos
essa versão do padrão.
Da documentação do módulo FindOpenMP.cmake:
$ cmake --help-module FindOpenMP | less
descobrimos que o módulo fornece os componentes C, CXX e Fortran
e que o destino OpenMP::OpenMP_CXX será fornecido,
se a detecção for bem sucedida. Assim, fazemos o seguinte:
find_package(OpenMP 4.5 REQUIRED COMPONENTS CXX)
target_link_libraries(task-loop PRIVATE OpenMP::OpenMP_CXX)
Podemos configurar e construir detalhadamente. [2] Observe que os sinalizadores do compilador, diretórios de inclusão e bibliotecas de links são resolvidos corretamente pelo CMake.
Você pode encontrar o exemplo completo de trabalho em source/code/day-2/22_taskloop/solution.
Exercício 23: Usando MPI
Neste exercício, você tentará compilar um programa “Hello, world” que usa a interface de passagem de mensagens (MPI).
Verifique se existe um módulo
FindMPI.cmakena biblioteca de módulos embutida.Familiarize-se com seus componentes e as variáveis e destinos importados que ele define.
O projeto base está em source/code/day-2/23_mpi-cxx.
Compile o arquivo de origem em um executável.
Vincule para o destino importado MPI.
Invoque uma compilação detalhada e observe como o CMake compila e vincula.
Um exemplo funcional está na subpasta solution.
O projeto base está em
content/code/day-2/23_mpi-f.
Compile o arquivo de origem em um executável.
Vincule para o destino importado MPI.
Invoque uma compilação detalhada e observe como o CMake compila e vincula
Um exemplo funcional está na subpasta
solution.
Alternativas: scripts Config e pkg-config
O que fazer quando não há um módulo Find<PackageName>.cmake embutido para um pacote do qual você depende?
Os desenvolvedores de pacotes podem já estar preparados para ajudá-lo:
Eles disponibilizam o arquivo específico do CMake
<PackageName>Config.cmakeque descreve como o destino importado deve ser usada pelo seu pacote. Neste caso, você precisa apontar o CMake para a pasta que contém o arquivoConfigusando a variável especial<PackageName>_DIR:$ cmake -S. -Bbuild -D<PackageName>_DIR=/folder/containing/<PackageName>Config.cmake
Eles incluem um arquivo
.pc, que, em plataformas do tipo Unix, pode ser detectado com o utilitáriopkg-config. Você pode então aproveitar opkg-configatravés do CMake:# find pkg-config find_package(PkgConfig REQUIRED) # ask pkg-config to find the UUID library and prepare an imported target pkg_search_module(UUID REQUIRED uuid IMPORTED_TARGET) # use the imported target if(TARGET PkgConfig::UUID) message(STATUS "Found libuuid") endif()
Esta foi a estratégia adotada em A05 - Compilação, vinculação e execução ao testar o uso da biblioteca UUID.
Resumo
O CMake possui um rico ecossistema de módulos para encontrar dependências de software. Eles são chamados de
Find<package>.cmake.Os módulos
Find<package>.cmakesão usados através defind_package(<package>).- Você também pode usar a ferramenta clássica do Unix
pkg-config para encontrar dependências de software, mas isso não é tão robusto quanto os módulos
Find<package>nativos do CMake.
- Você também pode usar a ferramenta clássica do Unix
Footnotes