Gần đây tôi có dịp đụng vào CMake, nên có tìm hiểu một chút về nó. Hy vọng có ích cho anh em.
Nó cung cấp tính năng sinh ra Makefile một cách hiệu quả. Nhất là đối với các dự án phức tạp. Nó cũng cung cấp thêm các bộ sinh khác để sinh cấu trúc quản lý source cho các IDE khác nhau như Project cho Visual Studio, etc.
Trong giới hạn, tôi sẽ nói về việc sử dụng CMake để build một simple project trên Linux.
Ta vẫn xoay quanh ví dụ kinh điển là tạo chương trình Hello World, nhưng yêu cầu hiển thị câu đó trên ít nhất 3 ngôn ngữ. ja,en,vi. Chương trình sẽ viết bằng ngôn ngữ C.
(Link tham khảo)
Ví dụ được thực hiện trong thư mục /home/oedev/Code/CMake
Nội dung chính gồm các phần sau,
- Chương trình Hello World cho En.
- Thêm ngôn ngữ Ja, Vi vào chương trình trên.
- Thêm ngôn ngữ Spain(Es) trong một tình huống khác.
============================================================
Ta sẽ có file source như sau:
mainapp.c
#include<stdio.h> // // Greeting message in En // void greeting_en() { printf("Hello world n"); } // // Main functions // int main(int argc, char* argv[]) { greeting_en(); return 0; }
File sử dụng cho CMake là 1 file CMakeLists.txt.
Nó sẽ có nội dung như sau:
# # Điều kiện về version tối thiểu để đọc được file CMakeList.txt này. # cmake_minimum_required(VERSION 2.8.12) # # Tên project thường sẽ là tên file chạy # project (helloworld) # # Định nghĩa mối liên quan giữa file chạy và file nguồn # add_executable(hellworld mainapp.c)
Đặt cả 2 file trên vào cùng 1 thư mục:
Nội dung được kiểm tra bằng lệnh tree
như sau
CMake ├── CMakeLists.txt └── mainapp.c
File CMakeList.txt chính là file định nghĩa source sử dụng ngôn ngữ mà CMake hiểu được.
Đến đây, ta thực hiện công việc chính của CMake. Đó là sinh ra Makefiles cho project đơn giản này. Ta phải chuyển vào bên trong thư mục CMake trước khi chạy lệnh dưới đây:
$ cmake .
Dấu . phía sau rất quan trọng. Đó là đường dân đến thư mục chưa file CMakeList.txt hay có thể thay nó bằng $(PWD)
trong trường hợp này.
Nội dung chạy xong nên như thế này:
manhongit.dhp@gmail.com:~/Code/CMake$ cmake . -- The C compiler identification is GNU 4.8.2 -- The CXX compiler identification is GNU 4.8.2 -- Check for working C compiler: /usr/bin/cc -- Check for working C compiler: /usr/bin/cc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Configuring done -- Generating done -- Build files have been written to: /home/oedev/Code/CMake
Nội dung thư mục sau khi chạy lệnh trên:
manhongit.dhp@gmail.com:~/Code/CMake$ tree -L 2 . ├── CMakeCache.txt ├── CMakeFiles │ ├── 2.8.12.2 │ ├── cmake.check_cache │ ├── CMakeDirectoryInformation.cmake │ ├── CMakeOutput.log │ ├── CMakeTmp │ ├── hellworld.dir │ ├── Makefile2 │ ├── Makefile.cmake │ ├── progress.marks │ └── TargetDirectories.txt ├── cmake_install.cmake ├── CMakeLists.txt ├── mainapp.c └── Makefile
Như ta thấy, có rất nhiều file và thư mục được tạo ra khi ta chạy CMake. Nhưng trong giới hạn bài này ta chỉ quan tâm đến Makefile thôi.
Ta cũng thấy rằng chương trình chưa được build, hay nói cách khác CMake chỉ làm nhiệm vụ của nó đến đây thôi. Tức là sinh ra Makefile , rồi nghỉ.
Giờ muốn build project ta vừa tạo lúc đầu (chỉ có 1 file .c), ta sẽ chạy Makefiles thôi.
manhongit.dhp@gmail.com:~/Code/CMake$ make Scanning dependencies of target hellworld [100%] Building C object CMakeFiles/hellworld.dir/mainapp.c.o Linking C executable hellworld [100%] Built target hellworld
Kết quả thu được như sau
manhongit.dhp@gmail.com:~/Code/CMake$ ls CMakeCache.txt CMakeFiles cmake_install.cmake CMakeLists.txt hellworld mainapp.c Makefile
Ta đã thây file helloword được sinh ra, đây chính là file chạy:
manhongit.dhp@gmail.com:~/Code/CMake$ ./hellworld Hello world
Giờ ta sẽ thêm lời chào bằng ngôn ngữ Ja, Vi vào chương trình trên.
Ta sẽ tổ chức lại source như sau:
manhongit.dhp@gmail.com:~/Code/CMake$ tree . ├── CMakeLists.txt ├── include │ ├── greetings_en.h │ ├── greetings_ja.h │ └── greetings_vi.h └── src ├── greetings_en.c ├── greetings_ja.c ├── greetings_vi.c └── mainapp.c
Do cấu trúc thư mục đã thay đổi, vị trí các file nguồn cũng thế.
Ta cần thay đổi file CMakeList.txt để phù hợp với thay đổi này.
Nội dung file này sẽ thành như sau:
*CMakeList.txt* # # Điều kiện về version # cmake_minimum_required(VERSION 2.8.12) # # Tên project # project (helloworld) # # Khai bảo thư mục chứa file header (.h) # include_directories(include) # # Thêm từ file nguồn bằng lệnh *set* # set(SOURCES src/mainapp.c src/greetings_en.c src/greetings_ja.c src/greetings_vi.c) # # Thêm một tập các file bằng một bộ lọc trong thư mục chưa source # Đây là cách nhanh và phổ biến hơn. # #file(GLOB SOURCES "src/*.c") # # Định nghĩa sự liên quan giữa file chạy và các file nguồn. # add_executable(hellworld ${SOURCES})
Ta sẽ chạy cmake để sinh Makefiles và build chương trình với Makefiles xem kết quả thế nào:
Với các sử dụng hàm set:
Chạy cmake
manhongit.dhp@gmail.com:~/Code/CMake$ cmake . -- The C compiler identification is GNU 4.8.2 -- The CXX compiler identification is GNU 4.8.2 -- Check for working C compiler: /usr/bin/cc -- Check for working C compiler: /usr/bin/cc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Configuring done -- Generating done -- Build files have been written to: /home/oedev/Code/CMake
Chạy Make
manhongit.dhp@gmail.com:~/Code/CMake$ make Scanning dependencies of target hellworld [ 25%] Building C object CMakeFiles/hellworld.dir/src/mainapp.c.o [ 50%] Building C object CMakeFiles/hellworld.dir/src/greetings_en.c.o [ 75%] Building C object CMakeFiles/hellworld.dir/src/greetings_ja.c.o [100%] Building C object CMakeFiles/hellworld.dir/src/greetings_vi.c.o Linking C executable hellworld [100%] Built target hellworld
Kiểm tra thư mục
manhongit.dhp@gmail.com:~/Code/CMake$ tree -L 2 . ├── CMakeCache.txt ├── CMakeFiles │ ├── 2.8.12.2 │ ├── cmake.check_cache │ ├── CMakeDirectoryInformation.cmake │ ├── CMakeOutput.log │ ├── CMakeTmp │ ├── hellworld.dir │ ├── Makefile2 │ ├── Makefile.cmake │ ├── progress.marks │ └── TargetDirectories.txt ├── cmake_install.cmake ├── CMakeLists.txt ├── hellworld ├── include │ ├── greetings_en.h │ ├── greetings_ja.h │ └── greetings_vi.h ├── Makefile └── src ├── greetings_en.c ├── greetings_ja.c ├── greetings_vi.c └── mainapp.c
Kết quả chạy chương trình
manhongit.dhp@gmail.com:~/Code/CMake$ ./hellworld Hello world Konichiwa!!! Xin chao !!!!
Khi thay set command bằng command file (GLOB) thì cho kết quả gần như giống hệt nhau.
2.1 Tạo thư mục để build riêng
Xem thêm: Các loại ma túy phổ biến bạn cần biết (phần 2)
Xem thêm: Patchouli Là Gì – Tinh Dầu Hoắc Hương Patchouli
À, có một điểm cần nói ở đây, vừa ta có thấy rất nhiều file (ngoài Makefile) được sinh ra trong quá trình cmake chạy.
Nếu để các file đó chung với thư mục các file source, header ta vừa tạo sẽ gây ra rắc rối, khó quản lý.
Ta sẽ tống các file đó vào 1 thư mục, gọi là thư mục build.
Cấu trúc thư mục sẽ thay đổi như sau:
manhongit.dhp@gmail.com:~/Code/CMake$ tree . ├── build ├── CMakeLists.txt ├── include │ ├── greetings_en.h │ ├── greetings_ja.h │ └── greetings_vi.h └── src ├── greetings_en.c ├── greetings_ja.c ├── greetings_vi.c └── mainapp.c
Giờ ta sẽ thực hiện toàn bộ quá trình cmake và make bên trong thư mục build
manhongit.dhp@gmail.com:~/Code/CMake/build$ cmake .. -- The C compiler identification is GNU 4.8.2 -- The CXX compiler identification is GNU 4.8.2 -- Check for working C compiler: /usr/bin/cc -- Check for working C compiler: /usr/bin/cc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Configuring done -- Generating done -- Build files have been written to: /home/oedev/Code/CMake/build
Ta sử dụng .. thay vì . như ở các câu lệnh trước, vì ta đã chuyển vào thư mục ./build nên file CMakeList.txt ở thư mục cha của thư mục này. Ta phải nói cho cmake biết điều đó.
Tiếp tục chạy lệnh make, ta sẽ có nội dung thư mục CMake sẽ như sau:
manhongit.dhp@gmail.com:~/Code/CMake$ tree -L 3 . ├── build │ ├── CMakeCache.txt │ ├── CMakeFiles │ │ ├── 2.8.12.2 │ │ ├── cmake.check_cache │ │ ├── CMakeDirectoryInformation.cmake │ │ ├── CMakeOutput.log │ │ ├── CMakeTmp │ │ ├── hellworld.dir │ │ ├── Makefile2 │ │ ├── Makefile.cmake │ │ ├── progress.marks │ │ └── TargetDirectories.txt │ ├── cmake_install.cmake │ ├── hellworld │ └── Makefile ├── CMakeLists.txt ├── include │ ├── greetings_en.h │ ├── greetings_ja.h │ └── greetings_vi.h └── src ├── greetings_en.c ├── greetings_ja.c ├── greetings_vi.c └── mainapp.c
Như ta thấy, toàn bộ nội dung của quá trình chạy cmake và make được giữ trong thư mục ./build, phần đinh nghĩa source code bên ngoài không bị ảnh hưởng gì hết.
Để chạy chương trình, ta phải vào thư mục ./build
Kết quả vẫn là :
manhongit.dhp@gmail.com:~/Code/CMake/build$ ./hellworld Hello world Konichiwa!!! Xin chao !!!!
Tình huống khác ở đây là gì? Tôi muốn nói đến tình huống như sau:
Giả sử ta có một thư viện chứa lời chào cho 3 ngôn ngữ En, Ja, Vi.
Ta cần thêm ngôn ngữ Es để có một chương trình với loài chào của 4 ngôn ngữ En, Ja, Vi, Es.
Trước hết, ta nên hiểu thư viện như thế nào? Trong C/C++ để sử dụng lại được mã nguồn hoặc để bảo vệ mã nguồn. Người ta thường build những mã nguồn muốn sử dụng lại hoặc đưa cho người khác thành các thư viện.
Về mặt file ta thấy được, thư viện sẽ có 2 loại: thư viện tĩnh(.a) và thư viện động(.so).
Các hàm ta đã viết trong file .c sẽ nằm trong các file thư viện này.
Nếu thư viện chỉ có thế thôi thì chưa đủ, ta cần phải biết thư viện đó cung cấp gì cho bên ngoài có thể sử dụng được. Có thể là biến toàn cục, có thể là hàm.
Các hàm và biến này phải đặt đâu đó trong các file header . Rồi khi build với source mà ta viết, trình biên dịch sẽ biết cách compile hoặc link cho thích hợp.
File header thường được cung cấp kèm theo các file .a(.so). Nếu không có, thì phải có tài liệu mô tả.
Có cả tài liệu và file header là điều lý tưởng nhất.
Giả định rằng, ta có thư viện chưa xử lý hiển thị 3 loài chào En, Ja, Vi. Nhưng ta chỉ có file header mô tả prototype của các hàm mà không thấy được source của nó.
3.1 Build thư viện tĩnh
Kết quả của đoạn này sẽ là file thư viện greetings_en_ja_vi.a và 3 file header đi kèm.
Sau đó ta có thể xóa 3 file source(.c) đi được.
Về cấu trúc file, do thư viện không chứa hàm main() được, ta phải bỏ file mainapp.c đi.
Nhớ nhé, thư viện thì chỉ cần file header thôi là dùng được rồi.
Thư viện mẫu mực sẽ không cần đến xem code, ta chỉ cần xem mô tả API hoặc file header là có thể dùng được rồi.
Cấu trúc file mới sẽ như thế này:
manhongit.dhp@gmail.com:~/Code/CMake$ tree -L 2 . ├── CMakeLists.txt ├── include │ ├── greetings_en.h │ ├── greetings_ja.h │ └── greetings_vi.h └── src ├── greetings_en.c ├── greetings_ja.c └── greetings_vi.c
Giờ, ta phải sửa lại file CMakeList.txt, nó sẽ thành như sau:
CMakeList.txt
# # Điều kiện phiên bản nhỏ nhất của CMake # cmake_minimum_required(VERSION 2.8.12) # # Tên project # project (multilang_greetings) # # Thêm thư mục chứa các file header (.h) # include_directories(include) # # Thêm một tập các file source # file(GLOB SOURCES "src/*.c") # # Thư viện bao gồm những source nào. # Loại thư viện là STATIC (tĩnh) # Tên thư viện có thể khác tên *project* # add_library(greetings_enjavi STATIC ${SOURCES})
Chạy tiếp cmake và make trong thư mục build:
Ta sẽ được kết quả như sau:
Chạy Cmake, kết quả gần như không thay đổi so với trước:
manhongit.dhp@gmail.com:~/Code/CMake/build$ cmake .. -- The C compiler identification is GNU 4.8.2 -- The CXX compiler identification is GNU 4.8.2 -- Check for working C compiler: /usr/bin/cc -- Check for working C compiler: /usr/bin/cc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Configuring done -- Generating done -- Build files have been written to: /home/oedev/Code/CMake/buil
Chạy make:
manhongit.dhp@gmail.com:~/Code/CMake/build$ make Scanning dependencies of target greetings_enjavi [ 33%] Building C object CMakeFiles/greetings_enjavi.dir/src/greetings_vi.c.o [ 66%] Building C object CMakeFiles/greetings_enjavi.dir/src/greetings_ja.c.o [100%] Building C object CMakeFiles/greetings_enjavi.dir/src/greetings_en.c.o Linking C static library libgreetings_enjavi.a [100%] Built target greetings_enjavi
Ta thấy rằng tên thư viện sinh ra là libgreetings_enjavi.a, như vậy tiền tố lib được thêm vào tên thư viện ta đã khai báo với add_library trong file CMakeList.txt.
Ta có thể kiểm tra xem thư viện này chứa cái gì, bằng câu lệnh ar
manhongit.dhp@gmail.com:~/Code/CMake/build$ ar -t libgreetings_enjavi.a greetings_vi.c.o greetings_ja.c.o greetings_en.c.o
Ta sẽ thấy không có file chạy nào được sinh ra trong thư mục build. Đúng như mong muốn, ta cần build thư viện.
Ta cũng kiểm tra kích thước file này:
manhongit.dhp@gmail.com:~/Code/CMake/build$ ls -lh libgreetings_enjavi.a -rw-rw-r-- 1 oedev oedev *4.9K* 8月 17 23:13 libgreetings_enjavi.a
Đó là 4.9K (dung lượng NET), còn trên đĩa chắc phải hơn.
3.2 Build thư viện động
Ta đã build thư viện tĩnh, được file .a (một file nén chưa 3 file .o). Kích thước 4.9K(NET)
Giờ ra build thư viện động.
Vậy động với tĩnh khác nhau ở chỗ nào, tại sao phải build cả động làm gì.
Chuyện khá dài, tôi sẽ trình bày ngắn gọn. Thư viện tĩnh, tức là tất cả xử lý của mọi hàm đều nằm trong thư viện đó rồi. Ví dụ, nó gọi hàm printf trong source .c, thì khi tạo thư viện tĩnh, nó ôm toàn bộ xử lý của printf vào trong file .a mà ta vừa tạo.
Còn thư viện động, thì không làm thế, nó lưu lại thông tin báo là, chỗ này gọi hàm printf với các tham số x,y,z gì đó. Khi nào chạy thì hỏi tiếp xem hàm đó ở đâu, xử lý thế nào rồi mới truyền các tham số để xử lý.
Cấu trúc các file source, header không thay đổi gì so với khi build thư viện tĩnh.
Ta cần thay đổi file CMakeList.txt một chút:
Nó sẽ có nội dung như thế này:
# # Điều kiện phiên bản nhỏ nhất của CMake # cmake_minimum_required(VERSION 2.8.12) # # Tên project # project (multilang_greetings) # # Thêm thư mục chứa các file header (.h) # include_directories(include) # # Thêm một tập các file source # file(GLOB SOURCES "src/*.c") # # Thư viện bao gồm những source nào. # Loại thư viện là SHARED(động, thực ra không sát nghĩa cho lắm) # Tên thư viện có thể khác tên *project* # add_library(greetings_enjavi SHARED ${SOURCES})
Kết quả của khi chạy CMake là không thay đổi gì, còn khi chạy make, nó sẽ như thế này:
manhongit.dhp@gmail.com:~/Code/CMake/build$ make Scanning dependencies of target greetings_enjavi [ 33%] Building C object CMakeFiles/greetings_enjavi.dir/src/greetings_vi.c.o [ 66%] Building C object CMakeFiles/greetings_enjavi.dir/src/greetings_ja.c.o [100%] Building C object CMakeFiles/greetings_enjavi.dir/src/greetings_en.c.o Linking C shared library libgreetings_enjavi.so [100%] Built target greetings_enjavi
Thư viện được sinh ra là libgreetings_enjavi.so, cũng bắt đầu với tiền tố lib, nhưng phần mở rộng là .so.
Với thư viện động, ta sẽ kiểm tra nội dung như sau:
manhongit.dhp@gmail.com:~/Code/CMake/build$ ldd libgreetings_enjavi.so linux-vdso.so.1 => (0x00007fff3e7fc000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6cf04e7000) /lib64/ld-linux-x86-64.so.2 (0x00007f6cf0acd000)
Thực ra mình cũng không hiểu kĩ mấy dòng này, tốt nhất không giải thích gì. ;))
3.3 Thêm ngôn ngữ Es
Giờ ta đã có trong tay siêu thư viện với 2 phiên bản, tĩnh và động.
Ta sẽ viết code sử dụng chúng, đồng thời thêm chức năng lời chào bằng tiếng Spain nữa.
-
Với thư viện tĩnh
Gần như rất giống thư viện động, chỉ khác tên file thư viện thôi.
Ta chỉ cần thay libgreetings_enjavi.so trong file CMakeList.txt ở phần dưới bằng libgreetings_enjavi.a, ta đã build ở phần trên thôi. -
Với thư viện động
Cấu trúc thư mục sẽ như sau:
manhongit.dhp@gmail.com:~/Code/CMake$ tree -L 2 . ├── build ├── CMakeLists.txt ├── include │ ├── greetings_en.h │ ├── greetings_es.h │ ├── greetings_ja.h │ └── greetings_vi.h ├── lib │ └── libgreetings_enjavi.so └── src ├── greetings_es.c └── mainapp.c
Ta cũng cần sửa lại file CMakeList.txt nữa
Nó sẽ thành như thế này:
CMakeList.txt
# # Ràng buộc về version # cmake_minimum_required(VERSION 2.8.12) # # Tên project # project (multilang_greetings) # # Khai báo nơi chưa thư viện (.so) # set (PROJECT_LINK_LIBS libgreetings_enjavi.so) link_directories(lib) # # Nơi chưa các file header(.h) # include_directories(include) # # Thêm source .c bằng lệnh set # #set(SOURCES src/greetings_es.c) # # Thêm source .c bằng file GLOB # file(GLOB SOURCES "src/*.c") # # File chạy sẽ được tạo như thế nào # Sử dụng thư viện đã khai báo qua PROJECT_LINK_LIBS # add_executable(helloworld ${SOURCES}) target_link_libraries(helloworld ${PROJECT_LINK_LIBS})
Sau đó, ta cũng chạy CMake và Make để tạo ra file chạy:
Kết quả của việc chạy CMake hầu như vẫn vậy.
Còn kết quả của Make sẽ như thế này:
manhongit.dhp@gmail.com:~/Code/CMake/build$ make Scanning dependencies of target helloworld [ 50%] Building C object CMakeFiles/helloworld.dir/src/mainapp.c.o [100%] Building C object CMakeFiles/helloworld.dir/src/greetings_es.c.o Linking C executable helloworld [100%] Built target helloworld
File chạy là helloworld
Kết quả chạy của file này sẽ như thế này:
manhongit.dhp@gmail.com:~/Code/CMake/build$ ./helloworld Hello world Konichiwa!!! Xin chao !!!! Hola!!!
Trên đây chỉ mô tả cách dùng CMake với một ví đơn giản trên Linux( tôi dung Ubuntu). Do bài viết đã quá dài, tôi không mô tả cách sử dụng trên Window (cho Visual Studio) của CMake. Về cơ bản, chúng ta sẽ có một chương trình CMake (có GUI) để trợ giúp việc export một source chứa file CMakeList.txt ra một thư mục chứa project Visual Studio. Có mấy điểm chú ý ở đây;
Thứ tự thực hiện sẽ là:
- [Browse Source] là thư mục đến source code, sẽ chứa file CMakeList.txt [Browse Build] là thư mục chứa cả project Visual Studio.
- [Configure] để chọn loại Project sẽ export và các tham số kèm theo.
- [Generate] để thấy kết quả.
Chú ý ở đây là dù ta có export ra project rồi nhưng source mà project đó chưa đều ở dạng tham chiếu đến các file source chứa trong thư mục chọn bằng [Browse Source]. Điều này tạo ra cho source ban đầu độc lập với project, dễ cập nhật khi phải build trên nhiều hệ điều hành. Nhưng cần chú ý khi di chuyển thư mục source đó.
Thanks and best regards. Rất mong đựoc thảo luận nhiều về tool này.
Website: https://www.batchuontyren.com