CMake: Como construir projetos externos e incluir seus objetivos


113

Tenho um Projeto A que exporta uma biblioteca estática como destino:

install(TARGETS alib DESTINATION lib EXPORT project_a-targets)
install(EXPORT project_a-targets DESTINATION lib/alib)

Agora, quero usar o Projeto A como um projeto externo do Projeto B e incluir seus alvos construídos:

ExternalProject_Add(project_a
  URL ...project_a.tar.gz
  PREFIX ${CMAKE_CURRENT_BINARY_DIR}/project_a
  CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
)

include(${CMAKE_CURRENT_BINARY_DIR}/lib/project_a/project_a-targets.cmake)

O problema é que o arquivo de inclusão ainda não existe quando CMakeLists do Projeto B é executado.

Existe uma maneira de tornar a inclusão dependente do projeto externo que está sendo construído?

Atualização : escrevi um breve tutorial CMake por Exemplo baseado neste e em outros problemas comuns que encontrei.

Respostas:


67

Acho que você está misturando dois paradigmas diferentes aqui.

Como você notou, o ExternalProjectmódulo altamente flexível executa seus comandos em tempo de construção, então você não pode fazer uso direto do arquivo de importação do Projeto A, pois ele só é criado depois que o Projeto A foi instalado.

Se você deseja includeimportar o arquivo do Projeto A, você terá que instalar o Projeto A manualmente antes de invocar CMakeLists.txt do Projeto B - assim como qualquer outra dependência de terceiros adicionada desta forma ou via find_file/ find_library/ find_package.

Se quiser usar ExternalProject_Add, você precisará adicionar algo como o seguinte ao seu CMakeLists.txt:

ExternalProject_Add(project_a
  URL ...project_a.tar.gz
  PREFIX ${CMAKE_CURRENT_BINARY_DIR}/project_a
  CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
)

include(${CMAKE_CURRENT_BINARY_DIR}/lib/project_a/project_a-targets.cmake)

ExternalProject_Get_Property(project_a install_dir)
include_directories(${install_dir}/include)

add_dependencies(project_b_exe project_a)
target_link_libraries(project_b_exe ${install_dir}/lib/alib.lib)

2
Obrigado pela sua resposta. O que você sugere é semelhante ao que eu tinha antes. Eu esperava encontrar uma maneira de usar os destinos exportados, pois parecia uma interface melhor do que especificar os caminhos de lib manualmente ...
— mirkokiefer

7
Eu queria evitar ter que incluir a fonte de projetos externos em minha árvore de código-fonte. Seria ótimo se ExternalProject_Addapenas se comportasse como add_subdirectorye expusesse todos os alvos. A solução que você descreveu acima provavelmente ainda é a mais limpa.
— mirkokiefer

2
Considere fazer ambas as compilações ExternalProject e, em seguida, ter B depender de A, e então o arquivo CMakeLists para o projeto B incluiria o arquivo de destino do projeto A, mas seu "Super Build" CMakeLists iria apenas construir A e então B, ambos como ExternalProjects ...
— DLRdave

3
@DLRdave - Já vi a solução Super Build ser recomendada algumas vezes, mas acho que não tenho certeza de quais benefícios ela oferece ao invés de incluir apenas alguns projetos externos via ExternalProject. É consistência, ou mais canônica, ou outra coisa? Tenho certeza de que estou perdendo algo fundamental aqui.
— Fraser

6
Um dos problemas com esta solução é que acabamos de codificar o nome da biblioteca (alib.lib), o que torna o sistema de construção não multiplataforma, já que diferentes sistemas operacionais usam diferentes esquemas de nomenclatura para bibliotecas compartilhadas e adaptando-se a esses diferentes nomes esquemas é uma das características do CMake.
— nsg

22

Esta postagem tem uma resposta razoável:

CMakeLists.txt.in:

cmake_minimum_required(VERSION 2.8.2)

project(googletest-download NONE)

include(ExternalProject)
ExternalProject_Add(googletest
  GIT_REPOSITORY    https://github.com/google/googletest.git
  GIT_TAG           master
  SOURCE_DIR        "${CMAKE_BINARY_DIR}/googletest-src"
  BINARY_DIR        "${CMAKE_BINARY_DIR}/googletest-build"
  CONFIGURE_COMMAND ""
  BUILD_COMMAND     ""
  INSTALL_COMMAND   ""
  TEST_COMMAND      ""
)

CMakeLists.txt:

# Download and unpack googletest at configure time
configure_file(CMakeLists.txt.in
               googletest-download/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download )
execute_process(COMMAND ${CMAKE_COMMAND} --build .
  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download )

# Prevent GoogleTest from overriding our compiler/linker options
# when building with Visual Studio
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)

# Add googletest directly to our build. This adds
# the following targets: gtest, gtest_main, gmock
# and gmock_main
add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src
                 ${CMAKE_BINARY_DIR}/googletest-build)

# The gtest/gmock targets carry header search path
# dependencies automatically when using CMake 2.8.11 or
# later. Otherwise we have to add them here ourselves.
if (CMAKE_VERSION VERSION_LESS 2.8.11)
  include_directories("${gtest_SOURCE_DIR}/include"
                      "${gmock_SOURCE_DIR}/include")
endif()

# Now simply link your own targets against gtest, gmock,
# etc. as appropriate

No entanto, parece bastante hacky. Eu gostaria de propor uma solução alternativa - usar submódulos Git.

cd MyProject/dependencies/gtest
git submodule add https://github.com/google/googletest.git
cd googletest
git checkout release-1.8.0
cd ../../..
git add *
git commit -m "Add googletest"

Então MyProject/dependencies/gtest/CMakeList.txtvocê pode fazer algo como:

cmake_minimum_required(VERSION 3.3)

if(TARGET gtest) # To avoid diamond dependencies; may not be necessary depending on you project.
    return()
endif()

add_subdirectory("googletest")

Ainda não tentei extensivamente, mas parece mais limpo.

Edit: Há uma desvantagem nesta abordagem: O subdiretório pode executar install()comandos que você não deseja. Este post tem uma abordagem para desativá-los, mas tinha bugs e não funcionou para mim.

Editar 2: se você usar add_subdirectory("googletest" EXCLUDE_FROM_ALL)parece, significa que os install()comandos no subdiretório não são usados ​​por padrão.


Provavelmente sou excessivamente cauteloso porque este é apenas um exemplo e gtest é provavelmente bastante estável, mas eu recomendo sempre usar um específico GIT_TAGdurante a clonagem, você pode perder a repetibilidade da compilação porque daqui a 2 anos alguém executando o script de compilação terá um versão diferente da que você fez. Os documentos do CMake também recomendam isso.
— jrh

5

Edit: CMake agora tem suporte embutido para isso. Veja a nova resposta .

Você também pode forçar a construção do destino dependente em um processo de criação secundário

Veja minha resposta em um tópico relacionado.


1

De ExternalProject_Addfato, o cmake pode ser usado, mas o que eu não gostei nele - é que ele executa algo durante a construção, pesquisa contínua, etc ... Eu preferiria construir o projeto durante a fase de construção, nada mais. Tentei substituir ExternalProject_Addem várias tentativas, infelizmente sem sucesso.

Em seguida, tentei também adicionar o submódulo git, mas isso arrasta todo o repositório git, enquanto em certos casos eu preciso apenas de um subconjunto de todo o repositório git. O que eu verifiquei - é realmente possível realizar uma verificação git esparsa, mas isso requer uma função separada, que escrevi abaixo.

#-----------------------------------------------------------------------------
#
# Performs sparse (partial) git checkout
#
#   into ${checkoutDir} from ${url} of ${branch}
#
# List of folders and files to pull can be specified after that.
#-----------------------------------------------------------------------------
function (SparseGitCheckout checkoutDir url branch)
    if(EXISTS ${checkoutDir})
        return()
    endif()

    message("-------------------------------------------------------------------")
    message("sparse git checkout to ${checkoutDir}...")
    message("-------------------------------------------------------------------")

    file(MAKE_DIRECTORY ${checkoutDir})

    set(cmds "git init")
    set(cmds ${cmds} "git remote add -f origin --no-tags -t master ${url}")
    set(cmds ${cmds} "git config core.sparseCheckout true")

    # This command is executed via file WRITE
    # echo <file or folder> >> .git/info/sparse-checkout")

    set(cmds ${cmds} "git pull --depth=1 origin ${branch}")

    # message("In directory: ${checkoutDir}")

    foreach( cmd ${cmds})
        message("- ${cmd}")
        string(REPLACE " " ";" cmdList ${cmd})

        #message("Outfile: ${outFile}")
        #message("Final command: ${cmdList}")

        if(pull IN_LIST cmdList)
            string (REPLACE ";" "\n" FILES "${ARGN}")
            file(WRITE ${checkoutDir}/.git/info/sparse-checkout ${FILES} )
        endif()

        execute_process(
            COMMAND ${cmdList}
            WORKING_DIRECTORY ${checkoutDir}
            RESULT_VARIABLE ret
        )

        if(NOT ret EQUAL "0")
            message("error: previous command failed, see explanation above")
            file(REMOVE_RECURSE ${checkoutDir})
            break()
        endif()
    endforeach()

endfunction()


SparseGitCheckout(${CMAKE_BINARY_DIR}/catch_197 https://github.com/catchorg/Catch2.git v1.9.7 single_include)
SparseGitCheckout(${CMAKE_BINARY_DIR}/catch_master https://github.com/catchorg/Catch2.git master single_include)

Eu adicionei duas chamadas de função abaixo apenas para ilustrar como usar a função.

Alguém pode não gostar de checkout master / trunk, pois aquele pode estar quebrado - então é sempre possível especificar uma tag específica.

A verificação será realizada apenas uma vez, até que você limpe a pasta de cache.


1

Eu estava procurando uma solução semelhante. As respostas aqui e o tutorial no topo são informativos. Estudei posts / blogs aqui referidos para construir o meu com sucesso. Estou postando CMakeLists.txt completo que funcionou para mim. Eu acho que isso seria útil como um modelo básico para iniciantes.

"CMakeLists.txt"

cmake_minimum_required(VERSION 3.10.2)

# Target Project
project (ClientProgram)

# Begin: Including Sources and Headers
include_directories(include)
file (GLOB SOURCES "src/*.c")
# End: Including Sources and Headers


# Begin: Generate executables
add_executable (ClientProgram ${SOURCES})
# End: Generate executables


# This Project Depends on External Project(s) 
include (ExternalProject)

# Begin: External Third Party Library
set (libTLS ThirdPartyTlsLibrary)
ExternalProject_Add (${libTLS}
    PREFIX          ${CMAKE_CURRENT_BINARY_DIR}/${libTLS}
# Begin: Download Archive from Web Server
    URL             http://myproject.com/MyLibrary.tgz
    URL_HASH        SHA1=<expected_sha1sum_of_above_tgz_file>
    DOWNLOAD_NO_PROGRESS ON
# End: Download Archive from Web Server

# Begin: Download Source from GIT Repository
#    GIT_REPOSITORY  https://github.com/<project>.git
#    GIT_TAG         <Refer github.com releases -> Tags>
#    GIT_SHALLOW     ON
# End: Download Source from GIT Repository

# Begin: CMAKE Comamnd Argiments
    CMAKE_ARGS      -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/${libTLS}
    CMAKE_ARGS      -DUSE_SHARED_LIBRARY:BOOL=ON
# End: CMAKE Comamnd Argiments    
)

# The above ExternalProject_Add(...) construct wil take care of \
# 1. Downloading sources
# 2. Building Object files
# 3. Install under DCMAKE_INSTALL_PREFIX Directory

# Acquire Installation Directory of 
ExternalProject_Get_Property (${libTLS} install_dir)

# Begin: Importing Headers & Library of Third Party built using ExternalProject_Add(...)
# Include PATH that has headers required by Target Project
include_directories (${install_dir}/include)

# Import librarues from External Project required by Target Project
add_library (lmytls SHARED IMPORTED)
set_target_properties (lmytls PROPERTIES IMPORTED_LOCATION ${install_dir}/lib/libmytls.so)
add_library (lmyxdot509 SHARED IMPORTED)
set_target_properties(lmyxdot509 PROPERTIES IMPORTED_LOCATION ${install_dir}/lib/libmyxdot509.so)

# End: Importing Headers & Library of Third Party built using ExternalProject_Add(...)
# End: External Third Party Library

# Begin: Target Project depends on Third Party Component
add_dependencies(ClientProgram ${libTLS})
# End: Target Project depends on Third Party Component

# Refer libraries added above used by Target Project
target_link_libraries (ClientProgram lmytls lmyxdot509)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.