CMake để làm gì?
Theo Wikipedia:
CMake là [...] phần mềm quản lý quá trình xây dựng phần mềm bằng phương pháp độc lập với trình biên dịch. Nó được thiết kế để hỗ trợ phân cấp thư mục và các ứng dụng phụ thuộc vào nhiều thư viện. Nó được sử dụng kết hợp với các môi trường xây dựng gốc như make, Xcode của Apple và Microsoft Visual Studio.
Với CMake, bạn không cần duy trì các cài đặt riêng biệt dành riêng cho môi trường biên dịch / xây dựng của mình. Bạn có một cấu hình và cấu hình đó hoạt động cho nhiều môi trường.
CMake có thể tạo giải pháp Microsoft Visual Studio, dự án Eclipse hoặc mê cung Makefile từ cùng các tệp mà không thay đổi bất kỳ điều gì trong chúng.
Cung cấp một loạt các thư mục với mã trong đó, CMake quản lý tất cả các phụ thuộc, xây dựng đơn đặt hàng và các tác vụ khác mà dự án của bạn cần thực hiện trước khi nó có thể được biên dịch. Nó KHÔNG thực sự biên dịch bất cứ thứ gì. Để sử dụng CMake, bạn phải cho nó biết (bằng cách sử dụng tệp cấu hình có tên là CMakeLists.txt) những tệp thực thi nào bạn cần được biên dịch, những thư viện nào chúng liên kết đến, những thư mục nào có trong dự án của bạn và những gì bên trong chúng, cũng như bất kỳ chi tiết nào như cờ hoặc bất cứ thứ gì khác mà bạn cần (CMake khá mạnh).
Nếu điều này được thiết lập chính xác, thì bạn sử dụng CMake để tạo tất cả các tệp mà "môi trường xây dựng gốc" mà bạn lựa chọn cần để thực hiện công việc của nó. Trong Linux, theo mặc định, điều này có nghĩa là Makefiles. Vì vậy, một khi bạn chạy CMake, nó sẽ tạo ra một loạt các tệp để sử dụng riêng cùng với một số tệp Makefile
. Tất cả những gì bạn cần làm sau đó là nhập "make" trong bảng điều khiển từ thư mục gốc mỗi khi bạn chỉnh sửa xong mã của mình và bam, tệp thực thi được biên dịch và liên kết sẽ được tạo.
CMake hoạt động như thế nào? Nó làm gì?
Đây là một ví dụ về thiết lập dự án mà tôi sẽ sử dụng trong suốt:
simple/
CMakeLists.txt
src/
tutorial.cxx
CMakeLists.txt
lib/
TestLib.cxx
TestLib.h
CMakeLists.txt
build/
Nội dung của mỗi tệp sẽ được hiển thị và thảo luận ở phần sau.
CMake thiết lập dự án của bạn theo thư mục gốc CMakeLists.txt
của dự án và làm như vậy trong bất kỳ thư mục nào bạn đã thực thi cmake
từ bảng điều khiển. Thực hiện việc này từ một thư mục không phải là thư mục gốc của dự án của bạn sẽ tạo ra thứ được gọi là bản dựng ngoài nguồn , có nghĩa là các tệp được tạo trong quá trình biên dịch (tệp obj, tệp lib, tệp thực thi, bạn biết đấy) sẽ được đặt trong thư mục đã nói , được giữ tách biệt với mã thực tế. Nó giúp giảm bớt sự lộn xộn và cũng được ưa thích vì những lý do khác mà tôi sẽ không thảo luận.
Tôi không biết điều gì sẽ xảy ra nếu bạn thực thi cmake
trên bất kỳ phần mềm nào khác ngoài thư mục gốc CMakeLists.txt
.
Trong ví dụ này, vì tôi muốn tất cả được đặt bên trong build/
thư mục, đầu tiên tôi phải điều hướng đến đó, sau đó chuyển CMake vào thư mục mà thư mục gốc CMakeLists.txt
nằm trong đó .
cd build
cmake ..
Theo mặc định, điều này thiết lập mọi thứ bằng cách sử dụng Makefiles như tôi đã nói. Đây là thư mục xây dựng sẽ trông như thế nào bây giờ:
simple/build/
CMakeCache.txt
cmake_install.cmake
Makefile
CMakeFiles/
(...)
src/
CMakeFiles/
(...)
cmake_install.cmake
Makefile
lib/
CMakeFiles/
(...)
cmake_install.cmake
Makefile
Tất cả các tệp này là gì? Điều duy nhất bạn phải lo lắng là Makefile và các thư mục dự án .
Lưu ý src/
và lib/
các thư mục. Chúng đã được tạo bởi vì simple/CMakeLists.txt
trỏ đến chúng bằng lệnh add_subdirectory(<folder>)
. Lệnh này yêu cầu CMake tìm CMakeLists.txt
tệp khác trong thư mục đã nói và thực thi tập lệnh đó , vì vậy mọi thư mục con được thêm theo cách này phải có CMakeLists.txt
tệp bên trong. Trong dự án này, simple/src/CMakeLists.txt
mô tả cách xây dựng tệp thực thi thực tế và simple/lib/CMakeLists.txt
mô tả cách xây dựng thư viện. Mọi mục tiêu mà một CMakeLists.txt
mô tả sẽ được đặt theo mặc định trong thư mục con của nó trong cây xây dựng. Vì vậy, sau một thời gian
make
trong bảng điều khiển được thực hiện từ build/
, một số tệp được thêm vào:
simple/build/
(...)
lib/
libTestLib.a
(...)
src/
Tutorial
(...)
Dự án được xây dựng và tệp thực thi đã sẵn sàng được thực thi. Bạn phải làm gì nếu bạn muốn các tệp thi hành được đặt trong một thư mục cụ thể? Đặt biến CMake thích hợp hoặc thay đổi thuộc tính của một mục tiêu cụ thể . Tìm hiểu thêm về các biến CMake sau.
Làm cách nào để cho CMake biết cách xây dựng dự án của tôi?
Dưới đây là nội dung, giải thích, của mỗi tệp trong thư mục nguồn:
simple/CMakeLists.txt
:
cmake_minimum_required(VERSION 2.6)
project(Tutorial)
# Add all subdirectories in this project
add_subdirectory(lib)
add_subdirectory(src)
Phiên bản yêu cầu tối thiểu phải luôn được đặt, theo cảnh báo CMake ném khi bạn không. Sử dụng bất kỳ phiên bản CMake nào của bạn.
Tên dự án của bạn có thể được sử dụng sau này và gợi ý rằng bạn có thể quản lý nhiều dự án từ cùng một tệp CMake. Tuy nhiên, tôi sẽ không đi sâu vào điều đó.
Như đã đề cập trước đây, add_subdirectory()
thêm một thư mục vào dự án, có nghĩa là CMake mong đợi nó có một thư mục CMakeLists.txt
bên trong, sau đó nó sẽ chạy trước khi tiếp tục. Nhân tiện, nếu bạn tình cờ có một hàm CMake được định nghĩa, bạn có thể sử dụng nó từ các CMakeLists.txt
s khác trong các thư mục con, nhưng bạn phải xác định nó trước khi sử dụng add_subdirectory()
nếu không sẽ không tìm thấy nó. Tuy nhiên, CMake thông minh hơn về thư viện, vì vậy đây có thể là lần duy nhất bạn gặp phải vấn đề này.
simple/lib/CMakeLists.txt
:
add_library(TestLib TestLib.cxx)
Để tạo thư viện của riêng bạn, bạn đặt tên cho nó và sau đó liệt kê tất cả các tệp được tạo từ đó. Thẳng thắn. Nếu nó cần một tệp khác foo.cxx
, để được biên dịch, thay vào đó bạn sẽ viết add_library(TestLib TestLib.cxx foo.cxx)
. Ví dụ, điều này cũng hoạt động đối với các tệp trong các thư mục khác add_library(TestLib TestLib.cxx ${CMAKE_SOURCE_DIR}/foo.cxx)
. Thêm thông tin về biến CMAKE_SOURCE_DIR sau.
Một điều khác bạn có thể làm với điều này là chỉ định rằng bạn muốn một thư viện được chia sẻ. Ví dụ: add_library(TestLib SHARED TestLib.cxx)
. Đừng sợ, đây là nơi CMake bắt đầu làm cho cuộc sống của bạn dễ dàng hơn. Cho dù nó được chia sẻ hay không, bây giờ tất cả những gì bạn cần xử lý để sử dụng một thư viện được tạo theo cách này là tên bạn đã đặt cho nó ở đây. Tên của thư viện này bây giờ là TestLib và bạn có thể tham khảo nó từ bất kỳ đâu trong dự án. CMake sẽ tìm thấy nó.
Có cách nào tốt hơn để liệt kê các phụ thuộc không? Chắc chắn là có . Kiểm tra bên dưới để biết thêm về điều này.
simple/lib/TestLib.cxx
:
#include <stdio.h>
void test() {
printf("testing...\n");
}
simple/lib/TestLib.h
:
#ifndef TestLib
#define TestLib
void test();
#endif
simple/src/CMakeLists.txt
:
# Name the executable and all resources it depends on directly
add_executable(Tutorial tutorial.cxx)
# Link to needed libraries
target_link_libraries(Tutorial TestLib)
# Tell CMake where to look for the .h files
target_include_directories(Tutorial PUBLIC ${CMAKE_SOURCE_DIR}/lib)
Lệnh add_executable()
hoạt động giống hệt như add_library()
, tất nhiên, ngoại trừ nó sẽ tạo ra một tệp thực thi thay thế. Tập tin thực thi này bây giờ có thể được tham chiếu như một mục tiêu cho những thứ như target_link_libraries()
. Vì tutorial.cxx sử dụng mã được tìm thấy trong thư viện TestLib, bạn chỉ ra điều này cho CMake như được hiển thị.
Tương tự, bất kỳ tệp .h nào # bao gồm bởi bất kỳ nguồn nào trong add_executable()
đó không nằm trong cùng thư mục với nguồn phải được thêm bằng cách nào đó. Nếu không phải target_include_directories()
lệnh, lib/TestLib.h
sẽ không được tìm thấy khi biên dịch Hướng dẫn, vì vậy toàn bộ lib/
thư mục được thêm vào thư mục bao gồm để tìm kiếm #includes. Bạn cũng có thể thấy lệnh include_directories()
hoạt động theo kiểu tương tự, ngoại trừ việc nó không cần bạn chỉ định mục tiêu vì nó hoàn toàn đặt nó trên toàn cầu, cho tất cả các tệp thực thi. Một lần nữa, tôi sẽ giải thích về CMAKE_SOURCE_DIR sau.
simple/src/tutorial.cxx
:
#include <stdio.h>
#include "TestLib.h"
int main (int argc, char *argv[])
{
test();
fprintf(stdout, "Main\n");
return 0;
}
Lưu ý cách bao gồm tệp "TestLib.h". Không cần phải bao gồm đường dẫn đầy đủ: CMake đảm nhận tất cả những gì đằng sau hậu trường nhờ target_include_directories()
.
Về mặt kỹ thuật, trong một cây mã nguồn đơn giản như thế này bạn có thể làm mà không có CMakeLists.txt
s dưới lib/
và src/
và chỉ cần thêm một cái gì đó như add_executable(Tutorial src/tutorial.cxx)
để simple/CMakeLists.txt
. Nó phụ thuộc vào bạn và nhu cầu của dự án của bạn.
Tôi nên biết gì khác để sử dụng CMake đúng cách?
(Các chủ đề AKA liên quan đến sự hiểu biết của bạn)
Tìm và sử dụng gói : Câu trả lời cho câu hỏi này giải thích nó tốt hơn tôi từng có thể.
Khai báo các biến và hàm, sử dụng luồng điều khiển, v.v .: hãy xem hướng dẫn này giải thích những điều cơ bản về những gì CMake cung cấp, cũng như là một phần giới thiệu tốt nói chung.
Các biến CMake : có rất nhiều, vì vậy những gì sau đây là một khóa học cơ bản để giúp bạn đi đúng hướng. CMake wiki là một nơi tốt để có thêm thông tin chuyên sâu về các biến và bề ngoài là những thứ khác.
Bạn có thể muốn chỉnh sửa một số biến mà không cần xây dựng lại cây xây dựng. Sử dụng ccmake cho việc này (nó chỉnh sửa CMakeCache.txt
tệp). Hãy nhớ c
bật cấu hình khi thực hiện xong các thay đổi và sau đó điều chỉnh các g
tệp trang bằng cấu hình đã cập nhật.
Đọc hướng dẫn đã tham khảo trước đây để tìm hiểu về cách sử dụng các biến, nhưng chỉ là một câu chuyện ngắn:
set(<variable name> value)
để thay đổi hoặc tạo một biến.
${<variable name>}
để dùng nó.
CMAKE_SOURCE_DIR
: Thư mục gốc của nguồn. Trong ví dụ trước, giá trị này luôn bằng/simple
CMAKE_BINARY_DIR
: Thư mục gốc của bản dựng. Trong ví dụ trước, giá trị này bằng với simple/build/
, nhưng nếu bạn chạy cmake simple/
từ một thư mục chẳng hạn foo/bar/etc/
, thì tất cả các tham chiếu đến CMAKE_BINARY_DIR
trong cây xây dựng đó sẽ trở thành /foo/bar/etc
.
CMAKE_CURRENT_SOURCE_DIR
: Thư mục chứa dòng điện CMakeLists.txt
. Điều này có nghĩa là nó thay đổi xuyên suốt: in cái này từ các kết simple/CMakeLists.txt
quả /simple
và in nó từ các kết simple/src/CMakeLists.txt
quả /simple/src
.
CMAKE_CURRENT_BINARY_DIR
: Bạn có được ý tưởng. Đường dẫn này sẽ không chỉ phụ thuộc vào thư mục chứa bản dựng mà còn phụ thuộc vào CMakeLists.txt
vị trí của tập lệnh hiện tại .
Tại sao những điều này lại quan trọng? Các tệp nguồn rõ ràng sẽ không nằm trong cây xây dựng. Nếu bạn thử một cái gì đó giống như target_include_directories(Tutorial PUBLIC ../lib)
trong ví dụ trước, đường dẫn đó sẽ tương đối với cây xây dựng, có nghĩa là nó sẽ giống như viết ${CMAKE_BINARY_DIR}/lib
, sẽ nhìn vào bên trong simple/build/lib/
. Không có tệp .h nào trong đó; nhiều nhất bạn sẽ tìm thấy libTestLib.a
. Bạn muốn ${CMAKE_SOURCE_DIR}/lib
thay thế.
CMAKE_CXX_FLAGS
: Cờ để chuyển cho trình biên dịch, trong trường hợp này là trình biên dịch C ++. Cũng cần lưu ý là CMAKE_CXX_FLAGS_DEBUG
nó sẽ được sử dụng thay thế nếu CMAKE_BUILD_TYPE
được đặt thành GỬI. Có nhiều thứ như thế này; kiểm tra wiki CMake .
CMAKE_RUNTIME_OUTPUT_DIRECTORY
: Cho CMake biết nơi đặt tất cả các tệp thực thi khi được xây dựng. Đây là một thiết lập toàn cầu. Ví dụ, bạn có thể đặt nó thành bin/
và đặt mọi thứ gọn gàng ở đó. EXECUTABLE_OUTPUT_PATH
tương tự, nhưng không được dùng nữa, trong trường hợp bạn tình cờ gặp nó.
CMAKE_LIBRARY_OUTPUT_DIRECTORY
: Tương tự như vậy, một cài đặt chung để cho CMake biết nơi đặt tất cả các tệp thư viện.
Thuộc tính mục tiêu : bạn có thể đặt các thuộc tính chỉ ảnh hưởng đến một mục tiêu, có thể là tệp thực thi hoặc thư viện (hoặc kho lưu trữ ... bạn có ý tưởng). Đây là một ví dụ điển hình về cách sử dụng nó (với set_target_properties()
.
Có cách nào dễ dàng để tự động thêm nguồn vào mục tiêu không? Sử dụng GLOB để liệt kê mọi thứ trong một thư mục nhất định dưới cùng một biến. Cú pháp ví dụ là FILE(GLOB <variable name> <directory>/*.cxx)
.
Bạn có thể chỉ định các kiểu xây dựng khác nhau không? Có, mặc dù tôi không chắc về cách thức hoạt động hoặc những hạn chế của điều này. Nó có thể yêu cầu một số if / then'ning, nhưng CMake không cung cấp một số hỗ trợ cơ bản mà không cần định cấu hình bất kỳ thứ gì, chẳng hạn như mặc định cho CMAKE_CXX_FLAGS_DEBUG
. Ví dụ, bạn có thể đặt loại bản dựng của mình từ trong CMakeLists.txt
tệp qua set(CMAKE_BUILD_TYPE <type>)
hoặc bằng cách gọi CMake từ bảng điều khiển với các cờ thích hợp cmake -DCMAKE_BUILD_TYPE=Debug
.
Có ví dụ điển hình nào về các dự án sử dụng CMake không? Wikipedia có một danh sách các dự án mã nguồn mở sử dụng CMake, nếu bạn muốn xem xét điều đó. Các hướng dẫn trực tuyến không có gì khác ngoài một điều khiến tôi thất vọng về vấn đề này, tuy nhiên câu hỏi về Stack Overflow này có cách thiết lập CMake khá thú vị và dễ hiểu. Nó đáng để xem.
Sử dụng các biến từ CMake trong mã của bạn : Đây là một ví dụ nhanh và dễ hiểu (phỏng theo một số hướng dẫn khác ):
simple/CMakeLists.txt
:
project (Tutorial)
# Setting variables
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 1)
# Configure_file(<input> <output>)
# Copies a file <input> to file <output> and substitutes variable values referenced in the file content.
# So you can pass some CMake variables to the source code (in this case version numbers)
configure_file (
"${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
"${PROJECT_SOURCE_DIR}/src/TutorialConfig.h"
)
simple/TutorialConfig.h.in
:
// Configured options and settings
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
Kết quả là tập tin được tạo ra bởi CMake, simple/src/TutorialConfig.h
:
// Configured options and settings
#define Tutorial_VERSION_MAJOR 1
#define Tutorial_VERSION_MINOR 1
Với cách sử dụng thông minh, bạn có thể làm những việc thú vị như tắt thư viện, v.v. Tôi khuyên bạn nên xem qua hướng dẫn đó vì có một số thứ nâng cao hơn một chút nhưng chắc chắn sẽ rất hữu ích cho các dự án lớn hơn, sớm hay muộn.
Đối với mọi thứ khác, Stack Overflow chứa đầy các câu hỏi cụ thể và câu trả lời ngắn gọn, điều này rất tốt cho tất cả mọi người trừ những người chưa quen.