CMake: Cómo construir proyectos externos e incluir sus objetivos

114

Tengo un Proyecto A que exporta una biblioteca estática como objetivo:

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

Ahora quiero usar el Proyecto A como un proyecto externo del Proyecto B e incluir sus objetivos construidos:

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)

El problema es que el archivo de inclusión aún no existe cuando se ejecuta CMakeLists del Proyecto B.

¿Hay alguna forma de hacer que la inclusión dependa del proyecto externo que se está construyendo?

Actualización : escribí un breve tutorial de CMake por ejemplo basado en este y otros problemas comunes que encontré.

mirkokiefer
fuente

Respuestas:

67

Creo que estás mezclando dos paradigmas diferentes aquí.

Como notó, el ExternalProjectmódulo altamente flexible ejecuta sus comandos en el momento de la compilación, por lo que no puede hacer uso directo del archivo de importación del Proyecto A, ya que solo se crea una vez que se ha instalado el Proyecto A.

Si desea includearchivo de importación de Proyecto A, usted tiene que instalar manualmente un proyecto antes de invocar del Proyecto B CMakeLists.txt - al igual que cualquier otra dependencia de terceros añadió esta manera oa través de find_file/ find_library/ find_package.

Si desea utilizarlo ExternalProject_Add, deberá agregar algo como lo siguiente a su 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
fuente
2
Gracias por tu respuesta. Lo que sugieres es similar a lo que tenía antes. Esperaba encontrar una manera de hacer uso de los destinos exportados, ya que parecía una interfaz mejor que especificar las rutas de la
biblioteca
7
Quería evitar tener que incluir la fuente de proyectos externos en mi árbol de fuentes. Sería genial si ExternalProject_Addsimplemente se comportara add_subdirectoryy expusiera todos los objetivos. La solución que describió anteriormente es probablemente la más limpia.
mirkokiefer
2
Considere hacer ambas compilaciones de ExternalProject, y luego hacer que B dependa de A, y luego el archivo CMakeLists para el proyecto B incluiría el archivo de objetivos del proyecto A, pero su CMakeLists "Super Build" solo compilaría A y luego B, ambas como ExternalProjects ...
DLRdave
3
@DLRdave: he visto la solución Super Build recomendada varias veces, pero supongo que no estoy seguro de qué beneficios proporciona solo con algunos proyectos externos a través de ExternalProject. ¿Es coherente, más canónico o algo más? Estoy seguro de que me estoy perdiendo algo fundamental aquí.
Fraser
6
Uno de los problemas con esta solución es que acabamos de codificar el nombre de la biblioteca (alib.lib), lo que hace que el sistema de compilación no sea multiplataforma, ya que diferentes sistemas operativos usan diferentes esquemas de nombres para bibliotecas compartidas y se adaptan a estos diferentes nombres. esquemas es una de las características de CMake.
nsg
22

Esta publicación tiene una respuesta razonable:

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

Sin embargo, parece bastante hacky. Me gustaría proponer una solución alternativa: use submódulos de 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"

Luego MyProject/dependencies/gtest/CMakeList.txt, puedes hacer 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")

Todavía no lo he probado mucho, pero parece más limpio.

Editar: hay una desventaja en este enfoque: el subdirectorio puede ejecutar install()comandos que no desea. Esta publicación tiene un enfoque para deshabilitarlos, pero tenía errores y no funcionó para mí.

Edición 2: si lo usas add_subdirectory("googletest" EXCLUDE_FROM_ALL), parece que los install()comandos del subdirectorio no se usan por defecto.

Timmmm
fuente
Probablemente sea solo por mí siendo demasiado cauteloso porque esto es solo un ejemplo y gtest es probablemente bastante estable, pero recomiendo encarecidamente usar siempre un específico GIT_TAGdurante la clonación, podría perder la repetibilidad de compilación porque dentro de 2 años alguien que ejecute el script de compilación obtendrá un versión diferente a la que hiciste. Los documentos de CMake también recomiendan esto.
jrh
5

Editar: CMake ahora tiene soporte incorporado para esto. Ver nueva respuesta .

También puede forzar la construcción del objetivo dependiente en un proceso de creación secundario

Vea mi respuesta sobre un tema relacionado.

David
fuente
1

De ExternalProject_Addhecho , cmake se puede usar, pero lo que no me gustó de él es que realiza algo durante la compilación, la encuesta continua, etc. Preferiría compilar el proyecto durante la fase de compilación, nada más. He intentado anular ExternalProject_Adden varios intentos, desafortunadamente sin éxito.

Luego también intenté agregar el submódulo git, pero eso arrastra todo el repositorio de git, mientras que en ciertos casos solo necesito un subconjunto de todo el repositorio de git. Lo que he comprobado: de hecho, es posible realizar un pago de git escaso, pero eso requiere una función separada, que escribí a continuación.

#-----------------------------------------------------------------------------
#
# 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)

He agregado dos llamadas de función a continuación solo para ilustrar cómo usar la función.

Es posible que a alguien no le guste pagar el master / trunk, ya que ese podría estar roto, entonces siempre es posible especificar una etiqueta específica.

El pago se realizará solo una vez, hasta que borre la carpeta de caché.

TarmoPikaro
fuente
1

Estaba buscando una solución similar. Las respuestas aquí y el tutorial en la parte superior son informativos. Estudié publicaciones / blogs referidos aquí para construir el mío con éxito. Estoy publicando CMakeLists.txt completo que funcionó para mí. Supongo que esto sería útil como plantilla básica para principiantes.

"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
fuente