Thực hiện và tìm kiếm các dự án khác và sự phụ thuộc của chúng


76

Hãy tưởng tượng tình huống sau: Dự án A là một thư viện dùng chung có một số phụ thuộc (LibA, LibB và LibC). Dự án B là một tệp thực thi có phụ thuộc vào dự án A, và do đó yêu cầu tất cả các phụ thuộc của Dự án A cũng để xây dựng.

Ngoài ra, cả hai dự án đều được xây dựng bằng CMake và Dự án A không cần phải cài đặt (thông qua mục tiêu 'cài đặt') để Dự án B sử dụng nó, vì điều này có thể gây phiền toái cho các nhà phát triển.

Cách tốt nhất để giải quyết những sự phụ thuộc này bằng CMake là gì? Giải pháp lý tưởng sẽ càng đơn giản càng tốt (mặc dù không đơn giản hơn) và yêu cầu bảo trì tối thiểu.


2
Vì sự thịnh vượng trong tương lai: cmake.org/Wiki/CMake/Tutorials/…
blockchaindev

1
Hướng dẫn đó dường như không giải thích cách xử lý xuất các phụ thuộc thư viện bên ngoài. Thư viện duy nhất được liên kết với là một thư viện được xây dựng bởi dự án. Tôi cần phải biết làm thế nào để nói với Dự án B mà dự án A đòi hỏi thư viện bên ngoài khác nhau và do đó, những cần phải add sang bước liên kết của dự án B.
Ben Farmer

Trên thực tế, bạn nên thử Linux hoặc hệ thống con Linux nếu bạn là một người yêu thích PC. Điều tốt nhất với nền tảng này là Linux sẽ cài đặt tất cả các phụ thuộc cho bạn. Hoặc tốt hơn, nó gợi ý bạn đang thiếu những phụ thuộc nào và cung cấp cho Sudo apt-get install mydependencies, cách cài đặt. Thật sự dễ dàng.
Juniar

@Juniar, điều đó đơn giản hóa và tối ưu hóa mọi thứ rất nhiều, tôi đồng ý. Nhưng làm cho việc triển khai phần mềm trở thành cơn ác mộng. Tôi muốn có tất cả trong một gói cho phần mềm của mình và triển khai tất cả cùng nhau (thậm chí sao chép một phần một số thư viện). Chưa kể các vấn đề bảo trì. Mỗi hộp sẽ có một bộ lib riêng (ở một mức độ nào đó).
OpalApps

@OpalApps, Các phần phụ thuộc có thể được cài đặt trên các đường dẫn và thư mục khác nhau, tuy nhiên bạn vẫn có thể thêm các phần phụ thuộc này vào lúc biên dịch hoặc định cấu hình / bao gồm các đường dẫn bên ngoài. Tất cả chúng sẽ không được cài đặt trên một đường dẫn Đúng, tuy nhiên "sudo apt-get install" không cài đặt trên các thư mục cụ thể, chỉ cần chuyển chúng xung quanh.
Juniar

Câu trả lời:


147

Dễ dàng. Đây là ví dụ từ đỉnh đầu của tôi:

Cấp cao nhất CMakeLists.txt:

cmake_minimum_required(VERSION 2.8.10)

# You can tweak some common (for all subprojects) stuff here. For example:

set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)
set(CMAKE_DISABLE_SOURCE_CHANGES  ON)

if ("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
  message(SEND_ERROR "In-source builds are not allowed.")
endif ()

set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_COLOR_MAKEFILE   ON)

# Remove 'lib' prefix for shared libraries on Windows
if (WIN32)
  set(CMAKE_SHARED_LIBRARY_PREFIX "")
endif ()

# When done tweaking common stuff, configure the components (subprojects).
# NOTE: The order matters! The most independent ones should go first.
add_subdirectory(components/B) # B is a static library (depends on Boost)
add_subdirectory(components/C) # C is a shared library (depends on B and external XXX)
add_subdirectory(components/A) # A is a shared library (depends on C and B)

add_subdirectory(components/Executable) # Executable (depends on A and C)

CMakeLists.txttrong components/B:

cmake_minimum_required(VERSION 2.8.10)

project(B C CXX)

find_package(Boost
             1.50.0
             REQUIRED)

file(GLOB CPP_FILES source/*.cpp)

include_directories(${Boost_INCLUDE_DIRS})

add_library(${PROJECT_NAME} STATIC ${CPP_FILES})

# Required on Unix OS family to be able to be linked into shared libraries.
set_target_properties(${PROJECT_NAME}
                      PROPERTIES POSITION_INDEPENDENT_CODE ON)

target_link_libraries(${PROJECT_NAME})

# Expose B's public includes (including Boost transitively) to other
# subprojects through cache variable.
set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include
                                 ${Boost_INCLUDE_DIRS}
    CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE)

CMakeLists.txttrong components/C:

cmake_minimum_required(VERSION 2.8.10)

project(C C CXX)

find_package(XXX REQUIRED)

file(GLOB CPP_FILES source/*.cpp)

add_definitions(${XXX_DEFINITIONS})

# NOTE: Boost's includes are transitively added through B_INCLUDE_DIRS.
include_directories(${B_INCLUDE_DIRS}
                    ${XXX_INCLUDE_DIRS})

add_library(${PROJECT_NAME} SHARED ${CPP_FILES})

target_link_libraries(${PROJECT_NAME} B
                                      ${XXX_LIBRARIES})

# Expose C's definitions (in this case only the ones of XXX transitively)
# to other subprojects through cache variable.
set(${PROJECT_NAME}_DEFINITIONS ${XXX_DEFINITIONS}
    CACHE INTERNAL "${PROJECT_NAME}: Definitions" FORCE)

# Expose C's public includes (including the ones of C's dependencies transitively)
# to other subprojects through cache variable.
set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include
                                 ${B_INCLUDE_DIRS}
                                 ${XXX_INCLUDE_DIRS}
    CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE)

CMakeLists.txttrong components/A:

cmake_minimum_required(VERSION 2.8.10)

project(A C CXX)

file(GLOB CPP_FILES source/*.cpp)

# XXX's definitions are transitively added through C_DEFINITIONS.
add_definitions(${C_DEFINITIONS})

# NOTE: B's and Boost's includes are transitively added through C_INCLUDE_DIRS.
include_directories(${C_INCLUDE_DIRS})

add_library(${PROJECT_NAME} SHARED ${CPP_FILES})

# You could need `${XXX_LIBRARIES}` here too, in case if the dependency 
# of A on C is not purely transitive in terms of XXX, but A explicitly requires
# some additional symbols from XXX. However, in this example, I assumed that 
# this is not the case, therefore A is only linked against B and C.
target_link_libraries(${PROJECT_NAME} B
                                      C)

# Expose A's definitions (in this case only the ones of C transitively)
# to other subprojects through cache variable.
set(${PROJECT_NAME}_DEFINITIONS ${C_DEFINITIONS}
    CACHE INTERNAL "${PROJECT_NAME}: Definitions" FORCE)

# Expose A's public includes (including the ones of A's dependencies
# transitively) to other subprojects through cache variable.
set(${PROJECT_NAME}_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include
                                 ${C_INCLUDE_DIRS}
    CACHE INTERNAL "${PROJECT_NAME}: Include Directories" FORCE)

CMakeLists.txttrong components/Executable:

cmake_minimum_required(VERSION 2.8.10)

project(Executable C CXX)

file(GLOB CPP_FILES source/*.cpp)

add_definitions(${A_DEFINITIONS})

include_directories(${A_INCLUDE_DIRS})

add_executable(${PROJECT_NAME} ${CPP_FILES})

target_link_libraries(${PROJECT_NAME} A C)

Để làm rõ hơn, đây là cấu trúc cây nguồn tương ứng:

Root of the project
├───components
│   ├───Executable
│   │   ├───resource
│   │   │   └───icons
│   │   ├───source
|   |   └───CMakeLists.txt
│   ├───A
│   │   ├───include
│   │   │   └───A
│   │   ├───source
|   |   └───CMakeLists.txt
│   ├───B
│   │   ├───include
│   │   │   └───B
│   │   ├───source
|   |   └───CMakeLists.txt
│   └───C
│       ├───include
│       │   └───C
│       ├───source
|       └───CMakeLists.txt
└───CMakeLists.txt

Có nhiều điểm mà điều này có thể được điều chỉnh / tùy chỉnh hoặc thay đổi để đáp ứng các nhu cầu nhất định, nhưng điều này ít nhất sẽ giúp bạn bắt đầu.

LƯU Ý: Tôi đã sử dụng thành công cấu trúc này trong một số dự án quy mô vừa và lớn.


15
Bạn là một NGÔI SAO F ****! bạn thực sự giúp tôi thoát khỏi cơn đau đầu dữ dội kéo dài gần một ngày. Cảm ơn rất nhiều 1
rsacchettini

2
Tôi tò mò, liệu nó có biên dịch không nếu tôi gọi Cmake từ thư mục "Executable"; hay tôi nên biên dịch từ thư mục gốc của dự án mọi lúc?
Ahmet Ipkin

1
Tôi nghĩ rằng nó có một nhược điểm nhỏ mà bạn xác định trong mỗi dự án là các thư mục bao gồm hai lần (một lần cho set(...INCLUDE_DIRSvà một lần cho include_directories()), điều này tôi thấy khó duy trì (luôn nhớ thêm phụ thuộc bao gồm mới ở hai nơi). Bạn có thể khai thác chúng với get_property(...PROPERTY INCLUDE_DIRECTORIES).
pseyfert

2
Điều này thực sự tuyệt vời, nhưng làm sao có thể đạt được điều tương tự khi A, B và C là những dự án hoàn toàn riêng biệt? Tức là tôi muốn xây dựng A và xuất nó để có tệp ProjectConfig.cmake của riêng nó, sau đó trong B sử dụng find_package để tìm A trên hệ thống, bằng cách nào đó thu được danh sách tất cả các thư viện mà A phụ thuộc vào để chúng có thể được liên kết khi tòa nhà B.
Ben Farmer

2
@Ben Farmer, đây thực sự là một chủ đề phức tạp hơn và xứng đáng có một bài báo lớn để giải thích đúng. Tôi chưa bao giờ có đủ kiên nhẫn để phác thảo nó ở đây. Trên thực tế, đó là những gì tôi làm để quản lý các dự án của mình vì đây thực sự là cách sử dụng và ý định cuối cùng (chuyên nghiệp) của CMake. Để quản lý tất cả những điều đó, tôi có một khuôn khổ CMake của riêng mình để xử lý rất nhiều thứ phía sau hậu trường. Ví dụ: bạn có thể thử xây dựng các dự án đồ chơi C ++ Hacks hoặc C ++ Firewall của tôi .
Alexander Shukaev

15

Alexander Shukaev đã có một khởi đầu tuyệt vời, nhưng có một số điều có thể làm tốt hơn:

  1. Không sử dụng include_directories. Ít nhất, hãy sử dụng target_include_directories. Tuy nhiên, bạn có thể thậm chí không cần làm điều đó nếu bạn sử dụng các mục tiêu đã nhập.
  2. Sử dụng các mục tiêu đã nhập. Ví dụ cho Boost:

    find_package(Boost 1.56 REQUIRED COMPONENTS
                 date_time filesystem iostreams)
    add_executable(foo foo.cc)
    target_link_libraries(foo
      PRIVATE
        Boost::date_time
        Boost::filesystem
        Boost::iostreams
    )
    

    Điều này sẽ xử lý các thư mục bao gồm, thư viện, v.v. Nếu bạn đã sử dụng Boost trong tiêu đề của mình bằng B, thì thay vì RIÊNG, hãy sử dụng CÔNG KHAI và các phần phụ thuộc này sẽ được thêm vào bất kỳ thứ gì phụ thuộc vào B.

  3. Không sử dụng tập tin Globing (trừ khi bạn sử dụng 3.12). Cho đến rất gần đây, tệp cầu vồng chỉ hoạt động trong thời gian cấu hình, vì vậy nếu bạn thêm tệp và xây dựng, nó không thể phát hiện các thay đổi cho đến khi bạn tạo lại dự án một cách rõ ràng. Tuy nhiên, nếu bạn liệt kê trực tiếp các tệp và cố gắng xây dựng, nó sẽ nhận ra cấu hình đã lỗi thời và tự động tạo lại trong bước xây dựng.

Có cuộc nói chuyện hay ở đây (YouTube): C ++ Now 2017: Daniel Pfeifer “CMake hiệu quả”

Trong đó đề cập đến ý tưởng trình quản lý gói cho phép CMake cấp gốc của bạn hoạt động với find_packageOR subdirectory, tuy nhiên, tôi đã cố gắng áp dụng tư tưởng của điều này và đang gặp vấn đề lớn với việc sử dụng find_packagecho mọi thứ và có cấu trúc thư mục giống như của bạn.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.