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.

Mirkokiefer
fonte

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)
Fraser
fonte
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.

Timmmm
fonte
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.

David
fonte
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.

TarmoPikaro
fonte
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)
Gopi
fonte