Là tốt hơn để chỉ định các tệp nguồn với GLOB hoặc từng tệp riêng lẻ trong CMake?


157

CMake cung cấp một số cách để chỉ định các tệp nguồn cho mục tiêu. Một là sử dụng globalbing ( tài liệu ), ví dụ:

FILE(GLOB MY_SRCS dir/*)

Một phương pháp khác là chỉ định từng tệp riêng lẻ.

Cách nào được ưa thích? Globbing có vẻ dễ dàng, nhưng tôi nghe nói nó có một số nhược điểm.

Câu trả lời:


185

Tiết lộ đầy đủ: Ban đầu tôi thích cách tiếp cận toàn cầu vì tính đơn giản của nó, nhưng qua nhiều năm tôi đã nhận ra rằng việc liệt kê rõ ràng các tệp ít bị lỗi đối với các dự án lớn, nhiều nhà phát triển.

Câu trả lời gốc:


Những lợi thế của Globing là:

  • Thật dễ dàng để thêm các tệp mới vì chúng chỉ được liệt kê ở một nơi: trên đĩa. Không Globing tạo ra sự trùng lặp.

  • Tệp CMakeLists.txt của bạn sẽ ngắn hơn. Đây là một điểm cộng lớn nếu bạn có nhiều tệp. Không toàn cầu khiến bạn mất logic CMake giữa các danh sách tệp khổng lồ.

Ưu điểm của việc sử dụng danh sách tệp được mã hóa cứng là:

  • CMake sẽ theo dõi chính xác các phụ thuộc của một tệp mới trên đĩa - nếu chúng ta sử dụng toàn cầu thì các tệp không được đặt vòng đầu tiên khi bạn chạy CMake sẽ không được chọn

  • Bạn đảm bảo rằng chỉ các tệp bạn muốn được thêm vào. Globbing có thể nhận các tập tin đi lạc mà bạn không muốn.

Để khắc phục sự cố đầu tiên, bạn chỉ cần "chạm" vào CMakeLists.txt có toàn cầu, bằng cách sử dụng lệnh cảm ứng hoặc bằng cách viết tệp mà không thay đổi. Điều này sẽ buộc CMake chạy lại và chọn tệp mới.

Để khắc phục vấn đề thứ hai, bạn có thể sắp xếp mã của mình một cách cẩn thận vào các thư mục, đây là điều bạn có thể làm dù sao đi nữa. Trong trường hợp xấu nhất, bạn có thể sử dụng list(REMOVE_ITEM)lệnh để dọn sạch danh sách các tập tin toàn cầu:

file(GLOB to_remove file_to_remove.cpp)
list(REMOVE_ITEM list ${to_remove})

Tình huống thực tế duy nhất mà điều này có thể cắn bạn là nếu bạn đang sử dụng một cái gì đó như git-bisect để thử các phiên bản cũ hơn của mã của bạn trong cùng thư mục bản dựng. Trong trường hợp đó, bạn có thể phải dọn dẹp và biên dịch nhiều hơn mức cần thiết để đảm bảo bạn có được các tệp đúng trong danh sách. Đây là một trường hợp góc như vậy, và một trong những nơi bạn đã ở trên ngón chân của mình, rằng nó không thực sự là một vấn đề.


1
Cũng rất tệ với globalbing: các tệp differftool của git được lưu dưới dạng $ basename. $ Ext. $ Type. $ Pid. $ Ext có thể gây ra các lỗi thú vị khi cố gắng biên dịch sau một độ phân giải hợp nhất.
mathstuf

9
Tôi nghĩ rằng câu trả lời này nhũ qua những hạn chế của cmake thiếu tập tin mới, Simply "touch" the CMakeLists.txtlà OK nếu bạn là nhà phát triển, nhưng đối với những người khác xây dựng phần mềm của bạn nó thực sự có thể là một nỗi đau-điểm đó xây dựng của bạn thất bại sau khi cập nhật và gánh nặng trên chúng để điều tra tại sao.
ideaman42

36
Bạn biết gì? Kể từ khi viết câu trả lời này 6 năm trước , tôi đã thay đổi suy nghĩ của mình một chút và bây giờ thích liệt kê rõ ràng các tệp. Điều bất lợi duy nhất là "thêm một chút công việc để thêm một tập tin", nhưng nó giúp bạn tiết kiệm tất cả các loại đau đầu. Và trong rất nhiều cách rõ ràng là tốt hơn ngầm.
richq

1
@richq Cái móc git này có khiến bạn xem xét lại vị trí hiện tại của mình không? :)
Antonio

8
Cũng như Antonio nói, các phiếu đã được đưa ra để ủng hộ cách tiếp cận "toàn cầu hóa". Thay đổi bản chất của câu trả lời là một điều cần thiết để làm cho những cử tri đó. Như một sự thỏa hiệp tôi đã thêm một chỉnh sửa để phản ánh quan điểm đã thay đổi của tôi. Tôi xin lỗi trên mạng vì đã gây ra một cơn bão như vậy trong một tách trà :-P
richq

113

Cách tốt nhất để xác định các tệp nguồn trong CMake là liệt kê chúng một cách rõ ràng .

Bản thân những người tạo ra CMake khuyên không nên sử dụng Globing.

Xem: https://cmake.org/cmake/help/v3.15/command/file.html?highlight=glob#file

(Chúng tôi không khuyên bạn nên sử dụng GLOB để thu thập danh sách các tệp nguồn từ cây nguồn của bạn. Nếu không có tệp CMakeLists.txt thay đổi khi một nguồn được thêm hoặc xóa thì hệ thống xây dựng được tạo không thể biết khi nào nên yêu cầu CMake tạo lại.)

Tất nhiên, bạn có thể muốn biết những nhược điểm là gì - đọc tiếp!


Khi Globbing thất bại:

Nhược điểm lớn đối với toàn cầu là việc tạo / xóa các tệp sẽ không tự động cập nhật hệ thống xây dựng.

Nếu bạn là người thêm các tệp, điều này có vẻ như là một sự đánh đổi chấp nhận được, tuy nhiên điều này gây ra vấn đề cho những người khác xây dựng mã của bạn, họ cập nhật dự án từ kiểm soát phiên bản, chạy bản dựng, sau đó liên hệ với bạn, phàn nàn rằng
"bản dựng bị hỏng".

Để làm cho vấn đề tồi tệ hơn, lỗi thường đưa ra một số lỗi liên kết không đưa ra bất kỳ gợi ý nào cho nguyên nhân của vấn đề và thời gian bị mất để khắc phục sự cố.

Trong một dự án tôi đã làm việc, chúng tôi đã bắt đầu phát triển toàn cầu nhưng đã nhận được rất nhiều khiếu nại khi các tệp mới được thêm vào, đó là lý do đủ để liệt kê rõ ràng các tệp thay vì toàn cầu hóa.

Điều này cũng phá vỡ các luồng công việc git phổ biến
( git bisectvà chuyển đổi giữa các nhánh tính năng).

Vì vậy, tôi không thể khuyến nghị điều này, những vấn đề mà nó gây ra vượt xa sự tiện lợi, khi ai đó không thể xây dựng phần mềm của bạn vì điều này, họ có thể mất nhiều thời gian để theo dõi vấn đề hoặc chỉ từ bỏ.

Và một lưu ý khác, Chỉ cần nhớ chạm vào CMakeLists.txtkhông phải lúc nào cũng đủ, với các bản dựng tự động sử dụng toàn cầu, tôi phải chạy cmaketrước mỗi bản dựng vì các tệp có thể đã được thêm / xóa kể từ bản dựng trước *.

Các ngoại lệ cho quy tắc:

Có những thời điểm mà Globing thích hợp hơn:

  • Để thiết lập một CMakeLists.txttệp cho các dự án hiện tại không sử dụng CMake.
    Đây là một cách nhanh chóng để có được tất cả các nguồn được tham chiếu (một khi hệ thống xây dựng đang chạy - thay thế toàn cầu bằng danh sách tệp rõ ràng).
  • Khi CMake không được sử dụng làm hệ thống xây dựng chính , ví dụ: nếu bạn đang sử dụng một dự án không sử dụng CMake và bạn muốn duy trì hệ thống xây dựng của riêng mình cho hệ thống đó.
  • Đối với bất kỳ tình huống trong đó danh sách tập tin thay đổi thường xuyên đến mức nó trở nên không thực tế để duy trì. Trong trường hợp này thể hữu ích, nhưng sau đó bạn phải chấp nhận chạy cmakeđể tạo tệp xây dựng mỗi lần để có bản dựng đáng tin cậy / chính xác (đi ngược lại ý định của CMake - khả năng phân tách cấu hình khỏi tòa nhà) .

* Có, tôi có thể đã viết một mã để so sánh cây tập tin trên đĩa trước và sau khi cập nhật, nhưng đây không phải là một cách giải quyết tốt và một cái gì đó tốt hơn để lại cho hệ thống xây dựng.


9
"Bất lợi lớn cho toàn cầu là việc tạo các tệp mới sẽ không tự động cập nhật hệ thống xây dựng." Nhưng không phải sự thật là nếu bạn không toàn cầu, bạn vẫn phải cập nhật thủ công CMakeLists.txt, nghĩa là cmake vẫn không tự động cập nhật hệ thống xây dựng? Có vẻ như một trong hai cách bạn phải nhớ tự làm một cái gì đó để tạo các tệp mới. Chạm vào CMakeLists.txt có vẻ dễ hơn là mở nó lên và chỉnh sửa nó để thêm tệp mới.
Dan

17
@Dan, đối với hệ thống của bạn - chắc chắn, nếu bạn chỉ phát triển một mình thì điều này tốt, nhưng còn những người khác xây dựng dự án của bạn thì sao? bạn sẽ gửi email cho họ để đi và chạm vào tệp CMake bằng tay? mỗi khi một tập tin được thêm hoặc xóa? - Lưu danh sách tệp trong CMake để đảm bảo bản dựng luôn sử dụng cùng các tệp mà vcs biết. Hãy tin tôi - đây không chỉ là một số chi tiết tinh tế - Khi bản dựng của bạn thất bại đối với nhiều nhà phát triển - họ gửi danh sách và hỏi trên IRC rằng mã bị hỏng. Lưu ý: (Ngay cả trên hệ thống của riêng bạn, bạn có thể quay lại lịch sử git chẳng hạn, và không nghĩ sẽ truy cập và chạm vào các tệp CMake)
ideaman42

2
Ah tôi đã không nghĩ về trường hợp đó. Đó là lý do tốt nhất mà tôi đã nghe chống lại Globing. Tôi muốn các tài liệu cmake mở rộng về lý do tại sao họ khuyên mọi người nên tránh việc quảng cáo.
Dan

1
Tôi đã suy nghĩ về giải pháp viết dấu thời gian của lần thực hiện cmake cuối cùng vào tệp. Vấn đề duy nhất là: 1) có lẽ phải thực hiện bằng cmake để trở thành nền tảng chéo và vì vậy chúng ta cần tránh cmake tự chạy lần thứ hai bằng cách nào đó. 2) Có thể có nhiều xung đột hợp nhất (vẫn xảy ra với danh sách tệp btw) Chúng thực sự có thể được giải quyết một cách tầm thường trong trường hợp này bằng cách lấy dấu thời gian sau đó.
Predelnik

2
@ tim-mb, "Nhưng thật tuyệt nếu CMake tạo một tệp filetree_updated mà bạn có thể đăng ký, nó sẽ tự động thay đổi mỗi khi toàn cầu tập tin được cập nhật." - bạn đã mô tả chính xác những gì câu trả lời của tôi làm.
Glen Knowles

21

Trong CMake 3.12, các lệnh file(GLOB ...)file(GLOB_RECURSE ...) lệnh đã đạt được một CONFIGURE_DEPENDStùy chọn chạy lại cmake nếu giá trị của toàn cầu thay đổi. Vì đó là nhược điểm chính của việc tạo khối cho các tệp nguồn, giờ đây bạn có thể làm như vậy:

# Whenever this glob's value changes, cmake will rerun and update the build with the
# new/removed files.
file(GLOB_RECURSE sources CONFIGURE_DEPENDS "*.cpp")

add_executable(my_target ${sources})

Tuy nhiên, một số người vẫn khuyên bạn nên tránh Globing cho các nguồn. Thật vậy, tài liệu nêu rõ:

Chúng tôi không khuyên bạn nên sử dụng GLOB để thu thập danh sách các tệp nguồn từ cây nguồn của bạn. ... CONFIGURE_DEPENDSCờ có thể không hoạt động đáng tin cậy trên tất cả các máy phát hoặc nếu một máy phát mới được thêm vào trong tương lai không thể hỗ trợ nó, các dự án sử dụng nó sẽ bị kẹt. Ngay cả khi CONFIGURE_DEPENDShoạt động đáng tin cậy, vẫn có một chi phí để thực hiện kiểm tra trên mỗi lần xây dựng lại.

Cá nhân, tôi xem xét các lợi ích của việc không phải quản lý thủ công danh sách tệp nguồn để vượt trội hơn các nhược điểm có thể có. Nếu bạn phải chuyển trở lại các tệp được liệt kê thủ công, điều này có thể dễ dàng đạt được bằng cách chỉ cần in danh sách nguồn toàn cầu và dán lại.


Nếu hệ thống xây dựng của bạn thực hiện chu trình xây dựng và xây dựng hoàn chỉnh (xóa thư mục xây dựng, chạy cmake từ đó và sau đó gọi tệp tạo tệp), miễn là chúng không kéo vào các tệp không mong muốn, chắc chắn không có nhược điểm nào khi sử dụng nguồn GLOBbed? Theo kinh nghiệm của tôi, phần cmake chạy nhanh hơn nhiều so với bản dựng, vì vậy dù sao nó cũng không quá chi phí
Den-Jason

9

Bạn có thể an toàn toàn cầu (và có lẽ nên) với chi phí của một tệp bổ sung để giữ các phụ thuộc.

Thêm các chức năng như thế này ở đâu đó:

# Compare the new contents with the existing file, if it exists and is the 
# same we don't want to trigger a make by changing its timestamp.
function(update_file path content)
    set(old_content "")
    if(EXISTS "${path}")
        file(READ "${path}" old_content)
    endif()
    if(NOT old_content STREQUAL content)
        file(WRITE "${path}" "${content}")
    endif()
endfunction(update_file)

# Creates a file called CMakeDeps.cmake next to your CMakeLists.txt with
# the list of dependencies in it - this file should be treated as part of 
# CMakeLists.txt (source controlled, etc.).
function(update_deps_file deps)
    set(deps_file "CMakeDeps.cmake")
    # Normalize the list so it's the same on every machine
    list(REMOVE_DUPLICATES deps)
    foreach(dep IN LISTS deps)
        file(RELATIVE_PATH rel_dep ${CMAKE_CURRENT_SOURCE_DIR} ${dep})
        list(APPEND rel_deps ${rel_dep})
    endforeach(dep)
    list(SORT rel_deps)
    # Update the deps file
    set(content "# generated by make process\nset(sources ${rel_deps})\n")
    update_file(${deps_file} "${content}")
    # Include the file so it's tracked as a generation dependency we don't
    # need the content.
    include(${deps_file})
endfunction(update_deps_file)

Và sau đó đi toàn cầu:

file(GLOB_RECURSE sources LIST_DIRECTORIES false *.h *.cpp)
update_deps_file("${sources}")
add_executable(test ${sources})

Bạn vẫn đang xoay quanh các phụ thuộc rõ ràng (và kích hoạt tất cả các bản dựng tự động!) Như trước đây, chỉ có trong hai tệp thay vì một.

Thay đổi duy nhất trong thủ tục là sau khi bạn đã tạo một tệp mới. Nếu bạn không toàn cầu, quy trình làm việc là sửa đổi CMakeLists.txt từ bên trong Visual Studio và xây dựng lại, nếu bạn làm toàn cầu, bạn sẽ chạy cmake một cách rõ ràng - hoặc chỉ cần chạm vào CMakeLists.txt.


Lúc đầu, tôi nghĩ rằng đây là một công cụ sẽ tự động cập nhật Makefiles khi tệp nguồn được thêm vào, nhưng bây giờ tôi thấy giá trị của nó là gì. Đẹp! Điều này giải quyết mối quan tâm của ai đó cập nhật từ kho lưu trữ và có makelỗi liên kết lạ.
Cris Luengo

1
Tôi tin rằng đây có thể là một phương pháp tốt. Tất nhiên người ta vẫn phải nhớ kích hoạt cmake sau khi thêm hoặc xóa một tệp, và nó cũng yêu cầu cam kết tệp phụ thuộc này, vì vậy một số giáo dục về phía người dùng là cần thiết. Hạn chế lớn có thể là tệp phụ thuộc này có thể tạo ra xung đột hợp nhất khó chịu, có thể khó giải quyết mà không yêu cầu nhà phát triển phải có một số hiểu biết về cơ chế.
Antonio

1
Điều này sẽ không hoạt động nếu dự án của bạn có các tệp bao gồm có điều kiện (ví dụ: một số tệp chỉ được sử dụng khi bật tính năng hoặc chỉ được sử dụng cho một hệ điều hành cụ thể). Nó đủ phổ biến cho phần mềm di động mà một số tệp chỉ được sử dụng cho các nền tảng đặc biệt.
ideaman42

0

Chỉ định từng tệp riêng lẻ!

Tôi sử dụng CMakeLists.txt thông thường và tập lệnh python để cập nhật nó. Tôi chạy tập lệnh python bằng tay sau khi thêm tập tin.

Xem câu trả lời của tôi ở đây: https://stackoverflow.com/a/48318388/3929196

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.