Ví dụ CMake đơn giản nhưng đầy đủ nhất


117

Bằng cách nào đó tôi hoàn toàn bối rối bởi cách hoạt động của CMake. Mỗi khi tôi nghĩ rằng tôi đang tiến gần hơn để hiểu CMake được viết như thế nào, nó sẽ biến mất trong ví dụ tiếp theo tôi đọc. Tất cả những gì tôi muốn biết là, tôi nên cấu trúc dự án của mình như thế nào để CMake của tôi yêu cầu ít bảo trì nhất trong tương lai. Ví dụ: tôi không muốn cập nhật CMakeList.txt của mình khi tôi thêm một thư mục mới trong cây src của mình, thư mục này hoạt động chính xác như tất cả các thư mục src khác.

Đây là cách tôi hình dung cấu trúc dự án của mình, nhưng xin vui lòng đây chỉ là một ví dụ. Nếu cách được đề xuất khác, vui lòng cho tôi biết và cho tôi biết cách thực hiện.

myProject
    src/
        module1/
            module1.h
            module1.cpp
        module2/
            [...]
        main.cpp
    test/
        test1.cpp
    resources/
        file.png
    bin
        [execute cmake ..]

Nhân tiện, điều quan trọng là chương trình của tôi phải biết nguồn tài nguyên ở đâu. Tôi muốn biết cách quản lý tài nguyên được khuyến nghị. Tôi không muốn truy cập tài nguyên của mình bằng "../resources/file.png"


1
For example I don't want to update my CMakeList.txt when I am adding a new folder in my src treebạn có thể cho một ví dụ về IDE thu thập các nguồn tự động không?

7
không có lý tưởng nào thường không tự động thu thập các nguồn, bởi vì chúng không cần thiết. Khi tôi thêm một tệp hoặc thư mục mới, tôi thực hiện việc đó trong Ide và dự án được cập nhật. Một hệ thống xây dựng ở phía bên kia không thực hiện thông báo khi tôi thay đổi một số tác phẩm, vì vậy nó là một hành vi mong muốn mà nó thu thập tất cả các file nguồn tự động
Arne

4
Nếu tôi nhìn thấy liên kết đó, tôi có ấn tượng rằng CMake đã thất bại ở nhiệm vụ quan trọng nhất mà nó muốn giải quyết: Tạo một hệ thống xây dựng đa nền tảng dễ dàng.
Arne

Câu trả lời:


94

sau một số nghiên cứu, bây giờ tôi đã có phiên bản của riêng mình về ví dụ cmake đơn giản nhưng đầy đủ nhất. Đây là nó, và nó cố gắng bao gồm hầu hết những điều cơ bản, bao gồm cả tài nguyên và đóng gói.

một điều nó không theo tiêu chuẩn là xử lý tài nguyên. Theo mặc định, cmake muốn đưa chúng vào / usr / share /, / usr / local / share / và một cái gì đó tương đương trên windows. Tôi muốn có một tệp zip / tar.gz đơn giản mà bạn có thể giải nén ở bất cứ đâu và chạy. Do đó, tài nguyên được tải liên quan đến tệp thực thi.

Quy tắc cơ bản để hiểu các lệnh cmake là cú pháp sau: <function-name>(<arg1> [<arg2> ...])không có dấu phẩy hoặc dấu chấm phẩy. Mỗi đối số là một chuỗi. foobar(3.0)foobar("3.0")giống nhau. bạn có thể đặt danh sách / biến với set(args arg1 arg2). Với bộ biến này foobar(${args})foobar(arg1 arg2)hiệu quả là như nhau. Một biến không tồn tại tương đương với một danh sách trống. Bên trong danh sách chỉ là một chuỗi có dấu chấm phẩy để phân tách các phần tử. Do đó, một danh sách chỉ có một phần tử thì theo định nghĩa chỉ là phần tử đó, không có quyền anh diễn ra. Các biến có tính toàn cục. Các hàm Builtin cung cấp một số dạng đối số được đặt tên bởi thực tế là chúng mong đợi một số id như PUBLIChoặcDESTINATIONtrong danh sách đối số của họ, để nhóm các đối số. Nhưng đó không phải là một tính năng ngôn ngữ, những id đó cũng chỉ là các chuỗi và được phân tích cú pháp bởi việc triển khai hàm.

bạn có thể sao chép mọi thứ từ github

cmake_minimum_required(VERSION 3.0)
project(example_project)

###############################################################################
## file globbing ##############################################################
###############################################################################

# these instructions search the directory tree when cmake is
# invoked and put all files that match the pattern in the variables 
# `sources` and `data`
file(GLOB_RECURSE sources      src/main/*.cpp src/main/*.h)
file(GLOB_RECURSE sources_test src/test/*.cpp)
file(GLOB_RECURSE data resources/*)
# you can use set(sources src/main.cpp) etc if you don't want to
# use globing to find files automatically

###############################################################################
## target definitions #########################################################
###############################################################################

# add the data to the target, so it becomes visible in some IDE
add_executable(example ${sources} ${data})

# just for example add some compiler flags
target_compile_options(example PUBLIC -std=c++1y -Wall -Wfloat-conversion)

# this lets me include files relative to the root src dir with a <> pair
target_include_directories(example PUBLIC src/main)

# this copies all resource files in the build directory
# we need this, because we want to work with paths relative to the executable
file(COPY ${data} DESTINATION resources)

###############################################################################
## dependencies ###############################################################
###############################################################################

# this defines the variables Boost_LIBRARIES that contain all library names
# that we need to link to
find_package(Boost 1.36.0 COMPONENTS filesystem system REQUIRED)

target_link_libraries(example PUBLIC
  ${Boost_LIBRARIES}
  # here you can add any library dependencies
)

###############################################################################
## testing ####################################################################
###############################################################################

# this is for our testing framework
# we don't add REQUIRED because it's just for testing
find_package(GTest)

if(GTEST_FOUND)
  add_executable(unit_tests ${sources_test} ${sources})

  # we add this define to prevent collision with the main
  # this might be better solved by not adding the source with the main to the
  # testing target
  target_compile_definitions(unit_tests PUBLIC UNIT_TESTS)

  # this allows us to use our executable as a link library
  # therefore we can inherit all compiler options and library dependencies
  set_target_properties(example PROPERTIES ENABLE_EXPORTS on)

  target_link_libraries(unit_tests PUBLIC
    ${GTEST_BOTH_LIBRARIES}
    example
  )

  target_include_directories(unit_tests PUBLIC
    ${GTEST_INCLUDE_DIRS} # doesn't do anything on Linux
  )
endif()

###############################################################################
## packaging ##################################################################
###############################################################################

# all install commands get the same destination. this allows us to use paths
# relative to the executable.
install(TARGETS example DESTINATION example_destination)
# this is basically a repeat of the file copy instruction that copies the
# resources in the build directory, but here we tell cmake that we want it
# in the package
install(DIRECTORY resources DESTINATION example_destination)

# now comes everything we need, to create a package
# there are a lot more variables you can set, and some
# you need to set for some package types, but we want to
# be minimal here
set(CPACK_PACKAGE_NAME "MyExample")
set(CPACK_PACKAGE_VERSION "1.0.0")

# we don't want to split our program up into several things
set(CPACK_MONOLITHIC_INSTALL 1)

# This must be last
include(CPack)

8
@SteveLorimer Tôi chỉ không đồng ý, tập tin đó là một phong cách xấu, tôi nghĩ rằng việc sao chép thủ công cây tập tin vào CMakeLists.txt là một phong cách xấu vì nó là thừa. Nhưng tôi biết rằng mọi người không đồng ý về chủ đề này, do đó tôi đã để lại một bình luận trong mã, nơi bạn có thể thay thế hình cầu bằng một danh sách chứa tất cả các tệp nguồn một cách rõ ràng. Tìm kiếm set(sources src/main.cpp).
Arne

3
@SteveLorimer vâng, tôi thường phải gọi lại cmake. Mỗi khi tôi thêm một cái gì đó vào cây thư mục, tôi cần phải gọi lại cmake theo cách thủ công, để Globe được đánh giá lại. Nếu bạn đặt các tệp vào CMakeLists.txt, thì một make (hoặc ninja) bình thường sẽ kích hoạt việc tái định vị cmake, vì vậy bạn không thể quên nó. Nó cũng thân thiện hơn với đội một chút, bởi vì sau đó các thành viên trong nhóm cũng không thể quên thực hiện cmake. Nhưng tôi nghĩ rằng một makefile không nên được chạm vào, chỉ vì ai đó đã thêm một tệp. Viết nó một lần và không ai cần phải nghĩ về nó nữa.
Arne

3
@SteveLorimer Tôi cũng không đồng ý với mô hình đặt một CMakeLists.txt trong mọi thư mục của các dự án, nó chỉ phân tán cấu hình của dự án ở khắp mọi nơi, tôi nghĩ một tệp để làm tất cả là đủ, nếu không bạn sẽ mất tổng quan, về cái gì. thực sự được thực hiện trong quá trình xây dựng. Điều đó không có nghĩa là không thể có thư mục con với CMakeLists.txt của riêng chúng, tôi chỉ nghĩ rằng nó nên là một ngoại lệ.
Arne

2
Giả sử "VCS" là viết tắt của "hệ thống kiểm soát phiên bản" , thì điều đó không liên quan. Vấn đề không phải là, các tạo tác sẽ không được thêm vào kiểm soát nguồn. Vấn đề là CMake sẽ không đánh giá lại các tệp nguồn đã thêm. Nó sẽ không tạo lại các tệp đầu vào hệ thống xây dựng. Hệ thống xây dựng sẽ vui vẻ gắn bó với các tệp đầu vào lỗi thời, hoặc dẫn đến lỗi (nếu bạn may mắn) hoặc sẽ không được chú ý, nếu bạn không may mắn. GLOBbing tạo ra một khoảng trống trong chuỗi tính toán phụ thuộc. Đây một vấn đề quan trọng và một bình luận không thừa nhận điều này một cách thích hợp.
IInspectable

2
CMake và VCS hoạt động hoàn toàn biệt lập. VCS không biết về CMake và CMake không biết về bất kỳ VCS nào. Không có liên kết giữa chúng. Trừ khi bạn gợi ý rằng các nhà phát triển nên thực hiện các bước thủ công, lấy thông tin ra khỏi VCS và dựa trên một số CMake sạch và chạy lại. Rõ ràng là điều đó không mở rộng quy mô và dễ mắc phải sự nguỵ biện đặc biệt đối với con người. Không, xin lỗi, cho đến nay bạn vẫn chưa đưa ra được điểm thuyết phục cho tệp GLOBbing.
IInspectable

39

Ví dụ cơ bản nhưng đầy đủ nhất có thể được tìm thấy trong hướng dẫn CMake :

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable(Tutorial tutorial.cxx)

Đối với ví dụ dự án của bạn, bạn có thể có:

cmake_minimum_required (VERSION 2.6)
project (MyProject)
add_executable(myexec src/module1/module1.cpp src/module2/module2.cpp src/main.cpp)
add_executable(mytest test1.cpp)

Đối với câu hỏi bổ sung của bạn, một cách để tiếp tục là trong hướng dẫn: tạo tệp tiêu đề có thể định cấu hình mà bạn đưa vào mã của mình. Đối với điều này, hãy tạo một tệp configuration.h.invới nội dung sau:

#define RESOURCES_PATH "@RESOURCES_PATH@"

Sau đó, trong CMakeLists.txtphần bổ sung của bạn :

set(RESOURCES_PATH "${PROJECT_SOURCE_DIR}/resources/"
# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
  "${PROJECT_SOURCE_DIR}/configuration.h.in"
  "${PROJECT_BINARY_DIR}/configuration.h"
)

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")

Cuối cùng, khi bạn cần đường dẫn trong mã của mình, bạn có thể làm:

#include "configuration.h"

...

string resourcePath = string(RESOURCE_PATH) + "file.png";

cảm ơn bạn rất nhiều, đặc biệt là đối với RESOURCE_PATH, bằng cách nào đó tôi không hiểu rằng config_file là những gì tôi đang tìm kiếm. Nhưng bạn đã thêm tất cả các tệp từ dự án theo cách thủ công, có cách nào tốt hơn để chỉ cần xác định một mẫu trong đó tất cả các tệp được thêm từ cây src không?
Arne

Xem câu trả lời của Dieter, nhưng cũng có thể nhận xét của tôi về lý do tại sao bạn không nên sử dụng nó. Nếu bạn thực sự muốn tự động hóa nó, cách tiếp cận tốt hơn có thể là viết một tập lệnh mà bạn có thể chạy để tạo lại danh sách các tệp nguồn (hoặc sử dụng một IDE nhận biết cmake để thực hiện việc này cho bạn; tôi không quen thuộc với bất kỳ tệp nào).
sgvd

3
@sgvd string resourcePath = string(RESOURCE_PATH) + "file.png"IMHO, bạn nên viết mã cứng đường dẫn tuyệt đối đến thư mục nguồn. Nếu bạn cần cài đặt dự án của mình thì sao?

2
Tôi biết tự động thu thập các nguồn nghe có vẻ tốt, nhưng nó có thể dẫn đến tất cả các loại phức tạp. Xem câu hỏi này từ một lúc trước để thảo luận ngắn: stackoverflow.com/q/10914607/1401351 .
Peter

2
Bạn gặp chính xác lỗi tương tự nếu bạn không chạy cmake; thêm tệp bằng tay mất một giây một lần, chạy cmake ở mỗi lần biên dịch mất một giây mỗi lần; bạn thực sự phá vỡ một tính năng của cmake; ai đó làm việc trên cùng một dự án và thực hiện các thay đổi của bạn sẽ làm: chạy make -> nhận tham chiếu không xác định -> hy vọng nhớ chạy lại cmake, hoặc lỗi tệp với bạn -> chạy cmake -> chạy make thành công, trong khi nếu bạn thêm tệp by hand he does: chạy làm cho thành công -> dành thời gian cho gia đình. Tóm lại, đừng lười biếng, kẻo bạn và những người khác phải đau đầu trong tương lai.
sgvd

2

Ở đây tôi viết một mẫu tệp CMakeLists.txt đơn giản nhưng đầy đủ nhất.

Mã nguồn

  1. Hướng dẫn từ hello world sang nhiều nền tảng Android / iOS / Web / Desktop.
  2. Mỗi nền tảng tôi phát hành một ứng dụng mẫu.
  3. Cấu trúc tệp 08-cross_platform được xác minh bởi công việc của tôi
  4. Nó có thể không hoàn hảo nhưng hữu ích và là phương pháp hay nhất cho nhóm của riêng tôi

Sau đó, tôi cung cấp tài liệu để biết chi tiết.

Nếu bạn có bất kỳ câu hỏi nào, bạn có thể liên hệ với tôi và tôi muốn giải thích.

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.