CMake: Bagaimana membangun proyek eksternal dan memasukkan target mereka

114

Saya memiliki Proyek A yang mengekspor pustaka statis sebagai target:

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

Sekarang saya ingin menggunakan Proyek A sebagai proyek eksternal dari Proyek B dan menyertakan target yang dibangun:

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)

Masalahnya adalah bahwa file penyertaan belum ada saat CMakeLists dari Proyek B dijalankan.

Apakah ada cara untuk membuat penyertaan bergantung pada proyek eksternal yang sedang dibangun?

Pembaruan : Saya menulis tutorial singkat CMake by Example berdasarkan ini dan masalah umum lainnya yang saya temui.

mirkokiefer
sumber

Jawaban:

67

Saya pikir Anda mencampurkan dua paradigma berbeda di sini.

Seperti yang Anda catat, ExternalProjectmodul yang sangat fleksibel menjalankan perintahnya pada waktu pembuatan, jadi Anda tidak dapat menggunakan file impor Project A secara langsung karena hanya dibuat setelah Project A diinstal.

Jika Anda ingin includefile impor Project A, Anda harus menginstal Project A secara manual sebelum menjalankan CMakeLists.txt Project B - sama seperti dependensi pihak ketiga lainnya yang ditambahkan dengan cara ini atau melalui find_file/ find_library/ find_package.

Jika ingin memanfaatkannya ExternalProject_Add, Anda perlu menambahkan sesuatu seperti berikut ini ke 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
sumber
2
Terima kasih atas jawaban anda. Apa yang Anda sarankan serupa dengan yang saya miliki sebelumnya. Saya berharap menemukan cara untuk menggunakan target yang diekspor karena tampaknya antarmuka yang lebih baik daripada menentukan jalur lib secara manual ...
mirkokiefer
7
Saya ingin menghindari keharusan menyertakan sumber proyek eksternal di pohon sumber saya. Akan lebih bagus jika ExternalProject_Addhanya bersikap seperti add_subdirectorydan mengekspos semua target. Solusi yang Anda jelaskan di atas mungkin masih yang terbersih.
mirkokiefer
2
Pertimbangkan untuk menjadikan keduanya sebagai build ExternalProject, lalu memiliki B bergantung pada A, lalu file CMakeLists untuk project B akan menyertakan file target dari project A, tetapi CMakeList "Super Build" Anda hanya akan membuat A dan kemudian B, keduanya sebagai ExternalProjects ...
DLRdave
3
@DLRdave - Saya telah melihat solusi Super Build yang direkomendasikan beberapa kali, tetapi saya rasa saya tidak yakin manfaat apa yang diberikannya hanya dengan menyertakan beberapa proyek eksternal melalui ExternalProject. Apakah itu konsistensi, atau lebih kanonik, atau sesuatu yang lain? Saya yakin saya kehilangan sesuatu yang mendasar di sini.
Fraser
6
Salah satu masalah dengan solusi ini adalah kami baru saja melakukan hardcode nama pustaka (alib.lib), yang membuat sistem build tidak lintas platform, karena OS yang berbeda menggunakan skema penamaan yang berbeda untuk pustaka bersama, dan menyesuaikan dengan penamaan yang berbeda ini. skema adalah salah satu fitur CMake.
nsg
22

Posting ini memiliki jawaban yang masuk akal:

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

Namun tampaknya cukup hacky. Saya ingin mengusulkan solusi alternatif - gunakan submodul 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"

Kemudian MyProject/dependencies/gtest/CMakeList.txtAnda dapat melakukan sesuatu seperti:

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

Saya belum mencoba ini secara ekstensif tetapi tampaknya lebih bersih.

Sunting: Ada kerugian untuk pendekatan ini: Subdirektori mungkin menjalankan install()perintah yang tidak Anda inginkan. Posting ini memiliki pendekatan untuk menonaktifkannya tetapi itu buggy dan tidak berfungsi untuk saya.

Edit 2: Jika Anda menggunakannya add_subdirectory("googletest" EXCLUDE_FROM_ALL), sepertinya install()perintah di subdirektori tidak digunakan secara default.

Timmmm
sumber
Ini mungkin hanya saya yang terlalu berhati-hati karena ini hanya contoh dan gtest mungkin cukup stabil, tetapi saya sangat menyarankan untuk selalu menggunakan yang spesifik GIT_TAGselama klon, Anda dapat kehilangan pengulangan build karena 2 tahun dari sekarang seseorang yang menjalankan skrip build akan mendapatkan versi yang berbeda dari yang Anda lakukan. Dokumen CMake merekomendasikan ini juga.
jrh
5

Sunting: CMake sekarang memiliki dukungan bawaan untuk ini. Lihat jawaban baru .

Anda juga bisa memaksakan pembangunan target dependen dalam proses pembuatan sekunder

Lihat jawaban saya tentang topik terkait.

David
sumber
1

cmake ExternalProject_Addmemang dapat digunakan, tetapi yang tidak saya sukai - adalah ia melakukan sesuatu selama pembuatan, polling berkelanjutan, dll. Saya lebih suka membangun proyek selama fase build, tidak ada yang lain. Saya telah mencoba menimpa ExternalProject_Adddalam beberapa upaya, sayangnya tidak berhasil.

Kemudian saya juga mencoba menambahkan git submodule, tetapi itu menyeret seluruh repositori git, sementara dalam kasus tertentu saya hanya memerlukan subset dari seluruh repositori git. Apa yang telah saya periksa - memang mungkin untuk melakukan pemeriksaan git yang jarang, tetapi itu memerlukan fungsi terpisah, yang saya tulis di bawah ini.

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

Saya telah menambahkan dua pemanggilan fungsi di bawah ini hanya untuk mengilustrasikan bagaimana menggunakan fungsi tersebut.

Seseorang mungkin tidak suka checkout master / trunk, karena itu mungkin rusak - maka selalu mungkin untuk menentukan tag tertentu.

Checkout akan dilakukan hanya sekali, sampai Anda menghapus folder cache.

TarmoPikaro
sumber
1

Saya sedang mencari solusi serupa. Balasan di sini dan Tutorial di atas bersifat informatif. Saya mempelajari posting / blog yang dirujuk di sini untuk membangun kesuksesan saya. Saya memposting CMakeLists.txt lengkap bekerja untuk saya. Saya rasa, ini akan berguna sebagai template dasar untuk pemula.

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