2016-06-30 19 views
6

Các dự án C++ của tôi bao gồm mã nguồn của thư viện của bên thứ ba (hiện tại là một mô-đun con git).Chỉ xây dựng thư viện bên ngoài một lần với CMake

Thư viện này được thêm vào dự án bởi các nhà lập pháp chính của chúng tôi thông qua việc sử dụng add_subdirectory và sau đó thư viện được liên kết với mục tiêu chính.

Đây là một phiên bản thu gọn của tập tin cmake hiện tại của tôi:

add_subdirectory(foo) 
set(FOO_LIBRARY ${CMAKE_CURRENT_SOURCE_DIR}/libfoo/libfoo.so) 

add_executable(target main.cpp) 
add_dependencies(target foo) 
target_link_libraries(target ${FOO_LIBRARY}) 

Thư viện này mất nhiều thời gian để xây dựng và, vì tôi không thay đổi mã của nó tôi cần nó được xây dựng chỉ một lần (mỗi build cấu hình). Nhưng khi tôi làm sạch và xây dựng lại mã của tôi, nó cũng làm sạch các tệp thư viện và biên dịch lại chúng.

Tôi đã cố gắng đặt thuộc tính CLEAN_NO_CUSTOM trong thư mục của thư viện, nhưng theo tài liệu chỉ hoạt động cho mục tiêu lệnh tùy chỉnh.

Có cơ chế nào trong CMake thông qua đó có thể chỉ định rằng mục tiêu thư viện này chỉ cần được tạo một lần hoặc không được làm sạch bởi make clean?

+2

Vì bạn không sử dụng mục tiêu * nội bộ * của thư viện của bên thứ ba, có vẻ như cách tiếp cận với 'ExternalProject_Add' sẽ tốt hơn' add_subdirectory'. Vì 'ExternalProject_Add' không chỉ định các quy tắc rõ ràng, CMake sẽ không cố gắng dọn dẹp thư viện. – Tsyvarev

Trả lời

4

Như @Tsyvarev đã nói, trong trường hợp của bạn ExternalProject_Add là tốt hơn add_subdirectory. add_subdirectory là tốt khi bạn muốn dự án là một phần thiết yếu trong hệ thống xây dựng của bạn vì mục tiêu mà nó tạo ra có thể được sử dụng ở phía bên phải của lệnh target_link_libraries() trong khi mục tiêu được tạo bởi ExternalProject_Add không thể.

Đây là phương pháp tôi đã sử dụng trong một trong các dự án của mình. Bạn cố gắng tìm thư viện cần thiết và xây dựng nó chỉ khi nó không được tìm thấy. Tôi sử dụng thư viện INTERFACE để biến FOO_EXTERNAL thành mục tiêu có thể chấp nhận được bởi target_link_libraries().

add_library(foo INTERFACE) 
find_package(foo ${FOO_VER}) 
if(NOT foo_FOUND) 
    include(ExternalProject) 
    include(GNUInstallDirs) 
    ExternalProject_Add(FOO_EXTERNAL 
        SOURCE_DIR "${FOO_SOURCE_DIR}" 
        BINARY_DIR "${FOO_BINARY_DIR}" 
        INSTALL_DIR "${FOO_INSTALL_DIR}" 
        CMAKE_ARGS "-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}" 
           "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}" 
           "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}" 

           "-DCMAKE_INSTALL_PREFIX=${FOO_INSTALL_DIR}" 
        ) 

    add_dependencies(foo FOO_EXTERNAL) 
    set(foo_LIBRARY 
      "${FOO_INSTALL_DIR}/${CMAKE_INSTALL_LIBDIR}/${CMAKE_STATIC_LIBRARY_PREFIX}foo${CMAKE_STATIC_LIBRARY_SUFFIX}") 
    set(foo_INCLUDE_DIR "${FOO_INSTALL_DIR}/include") 
endif() 

target_link_libraries(foo INTERFACE ${foo_LIBRARY}) 
target_include_directories(foo INTERFACE ${foo_INCLUDE_DIR}) 
3

Dựa trên câu trả lời tuyệt vời của @Hikke, tôi đã viết hai macro để đơn giản hóa việc sử dụng các dự án bên ngoài.

include(ExternalProject) 

# 
# Add external project. 
# 
# \param name    Name of external project 
# \param path    Path to source directory 
# \param external   Name of the external target 
# 
macro(add_external_project name path) 
    # Create external project 
    set(${name}_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/${path}) 
    set(${name}_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${path}) 
    ExternalProject_Add(${name} 
     SOURCE_DIR "${${name}_SOURCE_DIR}" 
     BINARY_DIR "${${name}_BINARY_DIR}" 
     CMAKE_ARGS "-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}" 
        "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}" 
        "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}" 
        # These are only useful if you're cross-compiling. 
        # They, however, will not hurt regardless. 
        "-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}" 
        "-DCMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}" 
        "-DCMAKE_AR=${CMAKE_AR}" 
        "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}" 
        "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" 
        "-DCMAKE_RC_COMPILER=${CMAKE_RC_COMPILER}" 
        "-DCMAKE_COMPILER_PREFIX=${CMAKE_COMPILER_PREFIX}" 
        "-DCMAKE_FIND_ROOT_PATH=${CMAKE_FIND_ROOT_PATH}" 
     INSTALL_COMMAND "" 
    ) 

endmacro(add_external_project) 

# 
# Add external target to external project. 
# 
# \param name    Name of external project 
# \param includedir  Path to include directory 
# \param libdir   Path to library directory 
# \param build_type  Build type {STATIC, SHARED} 
# \param external   Name of the external target 
# 
macro(add_external_target name includedir libdir build_type external) 
    # Configurations 
    set(${name}_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${libdir}) 

    # Create external library 
    add_library(${name} ${build_type} IMPORTED) 
    set(${name}_LIBRARY "${${name}_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${CMAKE_${build_type}_LIBRARY_PREFIX}${name}${CMAKE_${build_type}_LIBRARY_SUFFIX}") 

    # Find paths and set dependencies 
    add_dependencies(${name} ${external}) 
    set(${name}_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${includedir}") 

    # Set interface properties 
    set_target_properties(${name} PROPERTIES IMPORTED_LOCATION ${${name}_LIBRARY}) 
    set_target_properties(${name} PROPERTIES INCLUDE_DIRECTORIES ${${name}_INCLUDE_DIR}) 
endmacro(add_external_target) 

Giải thích

vĩ mô đầu tiên tạo ra các dự án bên ngoài, mà không toàn bộ bên ngoài xây dựng các bước, trong khi bước thứ hai đặt phụ thuộc cần thiết và xác định giao diện. Tách hai điều này là quan trọng, bởi vì hầu hết các dự án có nhiều hơn một giao diện/thư viện.

Ví dụ

Nói rằng tôi có GoogleTest như một submodule trong dự án của tôi, nằm trong thư mục con googletest. Tôi có thể sử dụng giao diện sau để xác định các macro gtestgtest_main, rất giống với cách chính bản thân Googletest thực hiện.

add_external_project(googletest_external googletest) 
add_external_target(gtest googletest/googletest/include googletest/googlemock/gtest STATIC googletest_external) 
add_external_target(gtest_main googletest/googletest/include googletest/googlemock/gtest STATIC googletest_external) 

Sau đó tôi có thể liên kết mục tiêu của tôi để googletest nhiều như trước đây:

target_link_libraries(target_tests 
    gtest 
    gtest_main 
    # The CMAKE_THREAD_LIBS_INIT can be found from `find_package(Threads)` 
    # and is required for all but MinGW builds. 
    ${CMAKE_THREAD_LIBS_INIT} 
) 

này nên cung cấp đầy đủ soạn sẵn để đơn giản hóa thực tế quá trình xây dựng bên ngoài, ngay cả với các dự án CMake-driven.

Các vấn đề liên quan