Là một người mới bắt đầu, có một số tên thông thường cho các thư mục mà bạn không thể bỏ qua, chúng dựa trên truyền thống lâu đời với hệ thống tệp Unix. Đó là:
trunk
├── bin : for all executables (applications)
├── lib : for all other binaries (static and shared libraries (.so or .dll))
├── include : for all header files
├── src : for source files
└── doc : for documentation
Có lẽ là một ý tưởng hay nếu bạn tuân theo bố cục cơ bản này, ít nhất là ở cấp cao nhất.
Về việc tách tệp tiêu đề và tệp nguồn (cpp), cả hai lược đồ đều khá phổ biến. Tuy nhiên, tôi có xu hướng muốn giữ chúng lại với nhau, nó chỉ thực tế hơn trong các công việc hàng ngày để có các tệp cùng nhau. Ngoài ra, khi tất cả mã nằm trong một thư mục cấp cao nhất, tức là trunk/src/
thư mục, bạn có thể nhận thấy rằng tất cả các thư mục khác (bin, lib, include, doc và có thể một số thư mục thử nghiệm) ở cấp cao nhất, ngoài thư mục "xây dựng" cho một bản dựng ngoài nguồn, là tất cả các thư mục không chứa gì khác ngoài các tệp được tạo trong quá trình xây dựng. Và do đó, chỉ thư mục src cần được sao lưu, hoặc tốt hơn nhiều, được giữ dưới hệ thống / máy chủ kiểm soát phiên bản (như Git hoặc SVN).
Và khi nói đến việc cài đặt các tệp tiêu đề của bạn trên hệ thống đích (nếu bạn muốn cuối cùng phân phối thư viện của mình), thì CMake có một lệnh để cài đặt tệp (ngầm tạo một mục tiêu "cài đặt", để thực hiện "thực hiện cài đặt"). bạn có thể sử dụng để đặt tất cả các tiêu đề vào /usr/include/
thư mục. Tôi chỉ sử dụng macro cmake sau cho mục đích này:
# custom macro to register some headers as target for installation:
# setup_headers("/path/to/header/something.h" "/relative/install/path")
macro(setup_headers HEADER_FILES HEADER_PATH)
foreach(CURRENT_HEADER_FILE ${HEADER_FILES})
install(FILES "${SRCROOT}${CURRENT_HEADER_FILE}" DESTINATION "${INCLUDEROOT}${HEADER_PATH}")
endforeach(CURRENT_HEADER_FILE)
endmacro(setup_headers)
Đâu SRCROOT
là một biến cmake mà tôi đặt vào thư mục src và INCLUDEROOT
là biến cmake mà tôi định cấu hình ở bất cứ đâu mà tiêu đề cần đến. Tất nhiên, có nhiều cách khác để làm điều này, và tôi chắc chắn rằng cách của tôi không phải là tốt nhất. Vấn đề là, không có lý do gì để tách tiêu đề và nguồn chỉ vì chỉ có tiêu đề cần được cài đặt trên hệ thống đích, vì rất dễ dàng, đặc biệt là với CMake (hoặc CPack), để chọn và định cấu hình tiêu đề để được cài đặt mà không cần phải có chúng trong một thư mục riêng. Và đây là những gì tôi đã thấy ở hầu hết các thư viện.
Trích dẫn: Thứ hai, tôi muốn sử dụng Khung kiểm tra C ++ của Google để kiểm tra đơn vị mã của tôi vì nó có vẻ khá dễ sử dụng. Bạn có đề xuất gói điều này với mã của tôi, ví dụ: trong thư mục "inc / gtest" hoặc "Contrib / gtest" không? Nếu được đóng gói, bạn có đề xuất sử dụng tập lệnh fuse_gtest_files.py để giảm số lượng tệp hoặc giữ nguyên? Nếu không được đóng gói thì phần phụ thuộc này được xử lý như thế nào?
Không gói các phụ thuộc với thư viện của bạn. Nói chung, đây là một ý tưởng khá kinh khủng, và tôi luôn ghét nó khi mắc kẹt khi cố gắng xây dựng một thư viện đã làm được điều đó. Đó nên là phương sách cuối cùng của bạn, và hãy cẩn thận với những cạm bẫy. Thông thường, mọi người kết hợp các phần phụ thuộc với thư viện của họ bởi vì họ nhắm mục tiêu đến một môi trường phát triển tồi tệ (ví dụ: Windows) hoặc vì họ chỉ hỗ trợ phiên bản cũ (không được dùng nữa) của thư viện (phụ thuộc) được đề cập. Cạm bẫy chính là phần phụ thuộc đi kèm của bạn có thể xung đột với các phiên bản đã được cài đặt của cùng một thư viện / ứng dụng (ví dụ: bạn đã gói gtest, nhưng người đang cố gắng xây dựng thư viện của bạn đã cài đặt phiên bản gtest mới hơn (hoặc cũ hơn), thì cả hai có thể xung đột và khiến người đó đau đầu rất khó chịu). Vì vậy, như tôi đã nói, hãy tự chịu rủi ro, và tôi chỉ nói như một phương sách cuối cùng. Yêu cầu mọi người cài đặt một số phụ thuộc trước khi có thể biên dịch thư viện của bạn là một việc ít ác hơn nhiều so với việc cố gắng giải quyết các xung đột giữa các phụ thuộc đi kèm và các cài đặt hiện có.
Trích dẫn: Khi nói đến các bài kiểm tra viết, chúng thường được tổ chức như thế nào? Tôi đã nghĩ đến việc có một tệp cpp cho mỗi lớp (ví dụ: test_vector3.cpp) nhưng tất cả được biên dịch thành một tệp nhị phân để tất cả chúng có thể được chạy cùng nhau một cách dễ dàng?
Theo ý kiến của tôi, một tệp cpp cho mỗi lớp (hoặc một nhóm nhỏ gắn kết các lớp và chức năng) là thông thường và thiết thực hơn. Tuy nhiên, chắc chắn, đừng biên dịch tất cả chúng thành một tệp nhị phân chỉ để "tất cả chúng có thể chạy cùng nhau". Đó là một ý tưởng thực sự tồi. Nói chung, khi nói đến mã hóa, bạn muốn chia nhỏ mọi thứ ra sao cho hợp lý. Trong trường hợp kiểm tra đơn vị, bạn không muốn một tệp nhị phân chạy tất cả kiểm tra, vì điều đó có nghĩa là bất kỳ thay đổi nhỏ nào bạn thực hiện đối với bất kỳ thứ gì trong thư viện của mình đều có khả năng gây ra sự biên dịch lại gần như hoàn toàn của chương trình kiểm tra đơn vị đó và đó chỉ là phút / giờ bị mất khi chờ biên dịch lại. Chỉ cần tuân theo một sơ đồ đơn giản: 1 đơn vị = 1 chương trình kiểm tra đơn vị. Sau đó,
Trích dẫn: Vì thư viện gtest thường được xây dựng bằng cmake và make, tôi đã nghĩ rằng dự án của tôi cũng được xây dựng như thế này có hợp lý không? Nếu tôi quyết định sử dụng bố cục dự án sau:
Tôi muốn đề xuất bố cục này:
trunk
├── bin
├── lib
│ └── project
│ └── libvector3.so
│ └── libvector3.a products of installation / building
├── docs
│ └── Doxyfile
├── include
│ └── project
│ └── vector3.hpp
│_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
│
├── src
│ └── CMakeLists.txt
│ └── Doxyfile.in
│ └── project part of version-control / source-distribution
│ └── CMakeLists.txt
│ └── vector3.hpp
│ └── vector3.cpp
│ └── test
│ └── test_vector3.cpp
│_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
│
├── build
└── test working directories for building / testing
└── test_vector3
Một số điều cần lưu ý ở đây. Đầu tiên, các thư mục con của thư mục src của bạn phải phản chiếu các thư mục con của thư mục include của bạn, điều này chỉ để giữ cho mọi thứ trực quan (ngoài ra, hãy cố gắng giữ cho cấu trúc thư mục con của bạn phẳng hợp lý (nông), vì lồng sâu các thư mục thường phức tạp hơn bất cứ điều gì khác). Thứ hai, thư mục "include" chỉ là một thư mục cài đặt, nội dung của nó chỉ là bất kỳ tiêu đề nào được chọn ra từ thư mục src.
Thứ ba, hệ thống CMake được thiết kế để được phân phối trên các thư mục con nguồn, không phải dưới dạng một tệp CMakeLists.txt ở cấp cao nhất. Điều này giữ cho mọi thứ cục bộ và điều đó tốt (trên tinh thần chia nhỏ mọi thứ thành các phần độc lập). Nếu bạn thêm nguồn mới, tiêu đề mới hoặc chương trình thử nghiệm mới, tất cả những gì bạn cần là chỉnh sửa một tệp CMakeLists.txt nhỏ và đơn giản trong thư mục con được đề cập mà không ảnh hưởng đến bất kỳ thứ gì khác. Điều này cũng cho phép bạn cấu trúc lại các thư mục một cách dễ dàng (CMakeLists là cục bộ và chứa trong các thư mục con đang được di chuyển). CMakeLists cấp cao nhất nên chứa hầu hết các cấu hình cấp cao nhất, chẳng hạn như thiết lập thư mục đích, lệnh tùy chỉnh (hoặc macro) và tìm các gói được cài đặt trên hệ thống. CMakeLists cấp thấp hơn chỉ nên chứa các danh sách đơn giản gồm các tiêu đề, nguồn,
Trích dẫn: CMakeLists.txt sẽ phải trông như thế nào để nó có thể chỉ xây dựng thư viện hoặc thư viện và các bài kiểm tra?
Câu trả lời cơ bản là CMake cho phép bạn loại trừ cụ thể các mục tiêu nhất định khỏi "tất cả" (là những gì được tạo khi bạn nhập "make") và bạn cũng có thể tạo các gói mục tiêu cụ thể. Tôi không thể thực hiện hướng dẫn CMake ở đây, nhưng bạn có thể tự tìm hiểu khá dễ dàng. Tuy nhiên, trong trường hợp cụ thể này, giải pháp được đề xuất tất nhiên là sử dụng CTest, chỉ là một tập hợp lệnh bổ sung mà bạn có thể sử dụng trong tệp CMakeLists để đăng ký một số mục tiêu (chương trình) được đánh dấu là đơn vị- các bài kiểm tra. Vì vậy, CMake sẽ đặt tất cả các bài kiểm tra trong một danh mục bản dựng đặc biệt và đó chính xác là những gì bạn yêu cầu, do đó, vấn đề đã được giải quyết.
Trích dẫn: Ngoài ra, tôi đã thấy khá nhiều dự án có quảng cáo xây dựng một thư mục bin. Quá trình xây dựng có xảy ra trong thư mục xây dựng và sau đó các tệp nhị phân được chuyển đến thư mục bin không? Các mã nhị phân cho các bài kiểm tra và thư viện có ở cùng một nơi không? Hoặc sẽ có ý nghĩa hơn nếu cấu trúc nó như sau:
Có một thư mục xây dựng bên ngoài nguồn (xây dựng "ngoài nguồn") thực sự là điều cần làm duy nhất, đó là tiêu chuẩn thực tế ngày nay. Vì vậy, chắc chắn, có một thư mục "xây dựng" riêng, bên ngoài thư mục nguồn, giống như những người CMake đề xuất, và như mọi lập trình viên tôi từng gặp. Đối với thư mục bin, đó là một quy ước, và có lẽ là một ý tưởng tốt để tuân theo nó, như tôi đã nói ở phần đầu của bài viết này.
Trích dẫn: Tôi cũng muốn sử dụng doxygen để ghi lại mã của mình. Có thể làm cho điều này tự động chạy với cmake và make không?
Đúng. Nó là nhiều hơn có thể, nó là tuyệt vời. Tùy thuộc vào mức độ ưa thích mà bạn muốn có, có một số khả năng. CMake có một mô-đun cho Doxygen (tức là, find_package(Doxygen)
) cho phép bạn đăng ký các mục tiêu sẽ chạy Doxygen trên một số tệp. Nếu bạn muốn làm những điều thú vị hơn, như cập nhật số phiên bản trong Doxyfile hoặc tự động nhập ngày tháng / tem tác giả cho các tệp nguồn, v.v., tất cả đều có thể thực hiện được với một chút CMake kung-fu. Nói chung, làm điều này sẽ liên quan đến việc bạn giữ một Doxyfile nguồn (ví dụ: "Doxyfile.in" mà tôi đặt trong bố cục thư mục ở trên) có mã thông báo được tìm thấy và thay thế bằng các lệnh phân tích cú pháp của CMake. Trong tệp CMakeLists cấp cao nhất của tôi , bạn sẽ tìm thấy một đoạn CMake kung-fu thực hiện một số điều thú vị với cmake-doxygen cùng nhau.