CMake: วิธีสร้างโครงการภายนอกและรวมเป้าหมายไว้ด้วย


114

ฉันมีโครงการ A ที่ส่งออกไลบรารีแบบคงที่เป็นเป้าหมาย:

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

ตอนนี้ฉันต้องการใช้โครงการ A เป็นโครงการภายนอกจากโครงการ B และรวมเป้าหมายที่สร้างไว้:

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)

ปัญหาคือยังไม่มีไฟล์รวมเมื่อเรียกใช้ CMakeLists ของโครงการ B

มีวิธีทำให้การรวมขึ้นอยู่กับโครงการภายนอกที่สร้างขึ้นหรือไม่?

อัปเดต : ฉันเขียนบทแนะนำ CMakeสั้น ๆตามตัวอย่างตามนี้และปัญหาทั่วไปอื่น ๆ ที่ฉันพบ

คำตอบ:


67

ฉันคิดว่าคุณกำลังผสมผสานสองกระบวนทัศน์ที่แตกต่างกันที่นี่

ตามที่คุณสังเกตเห็นว่าExternalProjectโมดูลที่มีความยืดหยุ่นสูงจะเรียกใช้คำสั่งในเวลาสร้างดังนั้นคุณจึงไม่สามารถใช้ประโยชน์จากไฟล์นำเข้าของโครงการ A ได้โดยตรงเนื่องจากสร้างขึ้นเมื่อติดตั้งโครงการ A แล้วเท่านั้น

หากคุณต้องการที่จะincludeนำเข้าไฟล์โครงการของ A คุณจะมีการติดตั้งโครงการด้วยตนเองก่อนที่จะอัญเชิญโครงการบี CMakeLists.txt - เช่นเดียวกับการพึ่งพาบุคคลที่สามอื่น ๆ เพิ่มด้วยวิธีนี้หรือผ่านทางfind_file/ /find_libraryfind_package

หากคุณต้องการใช้ประโยชน์ExternalProject_Addคุณจะต้องเพิ่มสิ่งต่อไปนี้ใน 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
ขอบคุณสำหรับคำตอบ. สิ่งที่คุณแนะนำคล้ายกับที่ฉันเคยมีมาก่อน ฉันหวังว่าจะพบวิธีใช้ประโยชน์จากเป้าหมายที่ส่งออกเนื่องจากดูเหมือนว่าอินเทอร์เฟซที่ดีกว่าการระบุเส้นทาง lib ด้วยตนเอง ...
mirkokiefer

7
ฉันต้องการหลีกเลี่ยงการรวมแหล่งที่มาของโครงการภายนอกในแผนผังแหล่งที่มาของฉัน มันจะดีมากถ้าExternalProject_Addทำตัวเหมือนadd_subdirectoryและเปิดเผยเป้าหมายทั้งหมด วิธีแก้ปัญหาที่คุณอธิบายไว้ข้างต้นน่าจะยังคงสะอาดที่สุด
mirkokiefer

2
พิจารณาสร้างทั้ง ExternalProject builds จากนั้นให้ B ขึ้นอยู่กับ A จากนั้นไฟล์ CMakeLists สำหรับโปรเจ็กต์ B จะรวมไฟล์เป้าหมายจากโปรเจ็กต์ A แต่ CMakeLists "Super Build" ของคุณจะสร้าง A และ B ทั้งสองเป็นโปรเจ็กต์ภายนอก ...
DLRdave

3
@DLRdave - ฉันเคยเห็นโซลูชัน Super Build ที่แนะนำสองสามครั้ง แต่ฉันเดาว่าฉันไม่แน่ใจว่าจะให้ประโยชน์อะไรมากกว่ารวมถึงโครงการภายนอกบางอย่างผ่านทางExternalProject. มีความสอดคล้องหรือเป็นที่ยอมรับมากขึ้นหรืออย่างอื่น? ฉันแน่ใจว่าฉันขาดอะไรพื้นฐานที่นี่
Fraser

6
ปัญหาอย่างหนึ่งในการแก้ปัญหานี้คือเราเพิ่งเข้ารหัสชื่อไลบรารี (alib.lib) ซึ่งทำให้ระบบบิวด์ไม่ข้ามแพลตฟอร์มเนื่องจากระบบปฏิบัติการอื่นใช้รูปแบบการตั้งชื่อที่แตกต่างกันสำหรับไลบรารีที่ใช้ร่วมกันและปรับให้เข้ากับการตั้งชื่อที่แตกต่างกันเหล่านี้ โครงร่างเป็นหนึ่งในคุณสมบัติของ CMake
nsg

22

โพสต์นี้มีคำตอบที่สมเหตุสมผล:

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

อย่างไรก็ตามดูเหมือนว่าค่อนข้างแฮ็ค ฉันต้องการเสนอทางเลือกอื่น - ใช้ Git submodules

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"

จากนั้นMyProject/dependencies/gtest/CMakeList.txtคุณสามารถทำสิ่งต่างๆเช่น:

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

ฉันยังไม่ได้ลองสิ่งนี้มากนัก แต่ดูเหมือนว่าจะสะอาดกว่า

แก้ไข: แนวทางนี้มีข้อเสียคือไดเร็กทอรีย่อยอาจเรียกใช้install()คำสั่งที่คุณไม่ต้องการ โพสต์นี้มีวิธีปิดใช้งานแต่มันเป็นปัญหาและไม่ได้ผลสำหรับฉัน

แก้ไข 2: หากคุณใช้add_subdirectory("googletest" EXCLUDE_FROM_ALL)ดูเหมือนว่าinstall()คำสั่งในไดเรกทอรีย่อยจะไม่ถูกใช้โดยค่าเริ่มต้น


นี่อาจเป็นเพียงฉันที่ระมัดระวังมากเกินไปเพราะนี่เป็นเพียงตัวอย่างและ gtest อาจค่อนข้างเสถียร แต่ฉันขอแนะนำอย่างยิ่งให้ใช้เฉพาะGIT_TAGในระหว่างการโคลนคุณอาจสูญเสียความสามารถในการสร้างซ้ำได้เนื่องจาก 2 ปีนับจากนี้คนที่รันสคริปต์บิลด์จะได้รับ เวอร์ชันที่แตกต่างจากที่คุณทำ เอกสารของ CMake ก็แนะนำสิ่งนี้เช่นกัน
jrh

5

แก้ไข: ตอนนี้ CMake มีการรองรับสิ่งนี้ในตัวแล้ว ดูคำตอบใหม่

คุณยังสามารถบังคับสร้างเป้าหมายที่อ้างอิงในกระบวนการสร้างรอง

ดูคำตอบของฉันในหัวข้อที่เกี่ยวข้อง


1

cmake ExternalProject_Addสามารถใช้งานได้จริง แต่สิ่งที่ฉันไม่ชอบเกี่ยวกับมัน - คือมันดำเนินการบางอย่างในระหว่างการสร้างการสำรวจความคิดเห็นต่อเนื่อง ฯลฯ ... ฉันต้องการสร้างโครงการในระหว่างขั้นตอนการสร้างไม่ใช่อย่างอื่น ฉันพยายามลบล้างExternalProject_Addอยู่หลายครั้ง แต่น่าเสียดายที่ไม่ประสบความสำเร็จ

จากนั้นฉันได้ลองเพิ่มโมดูลย่อย git ด้วย แต่นั่นลากที่เก็บ git ทั้งหมดในขณะที่ในบางกรณีฉันต้องการเพียงส่วนย่อยของที่เก็บ git ทั้งหมด สิ่งที่ฉันตรวจสอบ - เป็นไปได้ที่จะทำการชำระเงิน git แบบกระจัดกระจาย แต่ต้องใช้ฟังก์ชันแยกต่างหากซึ่งฉันเขียนไว้ด้านล่าง

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

ฉันได้เพิ่มการเรียกใช้ฟังก์ชันสองสายด้านล่างเพื่อแสดงวิธีใช้ฟังก์ชันนี้

อาจมีคนไม่ชอบเช็คเอาต์ master / trunk เนื่องจากอันนั้นอาจเสีย - จึงสามารถระบุแท็กเฉพาะได้เสมอ

การชำระเงินจะดำเนินการเพียงครั้งเดียวจนกว่าคุณจะล้างโฟลเดอร์แคช


1

ฉันกำลังค้นหาวิธีแก้ปัญหาที่คล้ายกัน คำตอบที่นี่และบทช่วยสอนด้านบนเป็นข้อมูล ฉันศึกษาโพสต์ / บล็อกที่อ้างถึงที่นี่เพื่อสร้างความสำเร็จของฉัน ฉันกำลังโพสต์ CMakeLists.txt ที่สมบูรณ์ได้ผลสำหรับฉัน ฉันเดาว่านี่จะเป็นประโยชน์ในฐานะแม่แบบพื้นฐานสำหรับผู้เริ่มต้น

"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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.