Czy lepiej jest określić pliki źródłowe za pomocą GLOB, czy też każdy plik osobno w CMake?

157

CMake oferuje kilka sposobów określania plików źródłowych dla celu. Jednym z nich jest użycie globbingu ( dokumentacji ), na przykład:

FILE(GLOB MY_SRCS dir/*)

Inną metodą jest określenie każdego pliku osobno.

Który sposób jest preferowany? Globbing wydaje się łatwy, ale słyszałem, że ma pewne wady.

Marenz
źródło

Odpowiedzi:

185

Pełne ujawnienie: początkowo wolałem podejście globalne ze względu na jego prostotę, ale z biegiem lat zdałem sobie sprawę, że jawne umieszczanie plików na liście jest mniej podatne na błędy w przypadku dużych projektów z udziałem wielu programistów.

Oryginalna odpowiedź:


Zalety globbingu to:

  • Dodawanie nowych plików jest łatwe, ponieważ są one wymienione tylko w jednym miejscu: na dysku. Brak globowania powoduje duplikację.

  • Twój plik CMakeLists.txt będzie krótszy. To duży plus, jeśli masz dużo plików. Brak globowania powoduje utratę logiki CMake wśród ogromnych list plików.

Zalety korzystania z list plików zakodowanych na stałe to:

  • CMake będzie poprawnie śledzić zależności nowego pliku na dysku - jeśli użyjemy globu, wtedy pliki, które nie są globbowane za pierwszym razem po uruchomieniu CMake, nie zostaną pobrane

  • Upewnij się, że dodawane są tylko żądane pliki. Globbing może zbierać niepotrzebne pliki, których nie chcesz.

Aby obejść pierwszy problem, możesz po prostu „dotknąć” pliku CMakeLists.txt, który wykonuje glob, za pomocą polecenia touch lub pisząc plik bez zmian. Zmusi to CMake do ponownego uruchomienia i pobrania nowego pliku.

Aby rozwiązać drugi problem, możesz ostrożnie zorganizować swój kod w katalogach, co i tak prawdopodobnie robisz. W najgorszym przypadku możesz użyć list(REMOVE_ITEM)polecenia, aby wyczyścić globbed listę plików:

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

Jedyną realną sytuacją, w której może to cię ugryźć, jest użycie czegoś takiego jak git-bisect, aby wypróbować starsze wersje swojego kodu w tym samym katalogu kompilacji. W takim przypadku może być konieczne wyczyszczenie i skompilowanie więcej niż to konieczne, aby zapewnić uzyskanie odpowiednich plików na liście. To taka narożna sprawa i taka, w której już jesteś na palcach, że tak naprawdę nie stanowi problemu.

richq
źródło
1
Również złe z globbingiem: pliki difftool gita są przechowywane jako $ basename. $ Ext. $ Type. $ Pid. $ Ext, co może powodować zabawne błędy podczas próby kompilacji po pojedynczym rozwiązaniu scalania.
mathstuf
9
Myślę, że ta odpowiedź wyjaśnia wady braku nowych plików przez cmake, Simply "touch" the CMakeLists.txtjest OK, jeśli jesteś programistą, ale dla innych tworzących oprogramowanie może być naprawdę problem, że twoja kompilacja nie powiedzie się po aktualizacji i na nich spoczywa ciężar zbadania czemu.
ideasman42
36
Wiesz co? Od czasu napisania tej odpowiedzi 6 lat temu zmieniłem nieco zdanie i teraz wolę jawnie wymieniać pliki. Jedyną prawdziwą wadą jest to, że „dodanie pliku wymaga trochę więcej pracy”, ale oszczędza to wszelkiego rodzaju bólów głowy. I pod wieloma względami wyraźne jest lepsze niż niejawne.
Richq
1
@richq Czy ten hak git zmusi cię do ponownego rozważenia swojej obecnej pozycji? :)
Antonio
8
Jak mówi Antonio, głosy oddano za poparciem podejścia „globalnego”. Zmiana charakteru odpowiedzi jest przynętą, którą można zrobić dla tych wyborców. Jako kompromis dodałem edycję, aby odzwierciedlić moją zmienioną opinię. Przepraszam internet za spowodowanie takiej burzy w filiżance :-P
richq
113

Najlepszym sposobem określenia plików źródłowych w CMake jest jawne ich podanie .

Twórcy CMake sami doradzają nie do użycia masek.

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

(Nie zalecamy używania GLOB do zbierania listy plików źródłowych z twojego drzewa źródłowego. Jeśli żaden plik CMakeLists.txt nie zmienia się po dodaniu lub usunięciu źródła, wówczas wygenerowany system kompilacji nie może wiedzieć, kiedy poprosić CMake o ponowne wygenerowanie).

Oczywiście możesz chcieć wiedzieć, jakie są wady - czytaj dalej!


Kiedy globbing się nie powiedzie:

Dużą wadą globbingu jest to, że tworzenie / usuwanie plików nie aktualizuje automatycznie systemu kompilacji.

Jeśli jesteś osobą dodającą pliki, może się to wydawać akceptowalnym kompromisem, jednak powoduje to problemy dla innych osób budujących Twój kod, aktualizują projekt z poziomu kontroli wersji, uruchamiają kompilację, a następnie kontaktują się z tobą, narzekając, że
„kompilacja złamany".

Co gorsza, awaria zazwyczaj powoduje błąd łączenia, który nie daje żadnych wskazówek co do przyczyny problemu, a jego rozwiązanie jest tracone.

W projekcie, nad którym pracowałem, zaczęliśmy od globowania, ale otrzymywaliśmy tak wiele skarg, gdy dodawano nowe pliki, że był to wystarczający powód, aby wyraźnie wyświetlić listę plików zamiast globowania.

To również łamie typowe przepływy pracy git
(git bisect i przełączanie między gałęziami funkcji).

Więc nie mogłem tego polecić, problemy, które powoduje, znacznie przewyższają wygodę, kiedy ktoś nie może z tego powodu zbudować twojego oprogramowania, może stracić dużo czasu na wyśledzenie problemu lub po prostu się poddać.

I jeszcze jedna uwaga, samo pamiętanie o dotknięciu CMakeLists.txtnie zawsze wystarcza, przy automatycznych kompilacjach, które używają globbingu, musiałem uruchamiać cmakeprzed każdą kompilacją od plików mogły zostać dodane / usunięte od ostatniego budowania *.

Wyjątki od reguły:

Są chwile, kiedy globbing jest lepszy:

  • Do konfigurowania CMakeLists.txtplików dla istniejących projektów, które nie używają CMake.
    Jest to szybki sposób na uzyskanie wszystkich źródeł, do których się odwołujemy (po uruchomieniu systemu budowania - zamień globbing na jawne listy plików).
  • Gdy CMake nie jest używany jako podstawowy system kompilacji, jeśli na przykład używasz projektu, który nie używa CMake, i chcesz utrzymywać dla niego własny system kompilacji.
  • W każdej sytuacji, w której lista plików zmienia się tak często, że jej utrzymanie staje się niepraktyczne. W tym przypadku może to być przydatne, ale wtedy musisz zaakceptować uruchamianie w cmakecelu generowania plików kompilacji za każdym razem, aby uzyskać niezawodną / poprawną kompilację (co jest sprzeczne z intencją CMake - możliwością oddzielenia konfiguracji od kompilacji ) .

* Tak, mogłem napisać kod porównujący drzewo plików na dysku przed i po aktualizacji, ale nie jest to takie przyjemne obejście i coś lepiej pozostawić systemowi kompilacji.

ideasman42
źródło
9
„Dużą wadą globbingu jest to, że tworzenie nowych plików nie powoduje automatycznej aktualizacji systemu kompilacji”. Ale czy nie jest prawdą, że jeśli tego nie zrobisz, nadal musisz ręcznie zaktualizować CMakeLists.txt, co oznacza, że ​​cmake nadal nie aktualizuje automatycznie systemu kompilacji? Wygląda na to, że tak czy inaczej musisz pamiętać, aby ręcznie zrobić coś, aby nowe pliki zostały zbudowane. Dotknięcie CMakeLists.txt wydaje się łatwiejsze niż otwieranie go i edytowanie w celu dodania nowego pliku.
Dan,
17
@Dan, dla twojego systemu - jasne, jeśli rozwijasz się sam, to jest w porządku, ale co z wszystkimi innymi, którzy budują twój projekt? czy zamierzasz wysłać je e-mailem, aby przejść i ręcznie dotknąć pliku CMake? za każdym razem, gdy plik jest dodawany lub usuwany? - Przechowywanie listy plików w CMake zapewnia, że ​​kompilacja zawsze używa tych samych plików, o których wie vcs. Uwierz mi - to nie są tylko subtelne szczegóły - Kiedy twoja kompilacja się nie powiedzie dla wielu programistów - wysyłają listy mailingowe i pytają na IRC, że kod jest uszkodzony. Uwaga: (nawet w swoim własnym systemie możesz cofnąć się do historii git, np. I nie myśleć o wchodzeniu i dotykaniu plików CMake)
ideasman42
2
Ach, nie myślałem o tym przypadku. To najlepszy powód, dla którego słyszałem przeciwko globbingowi. Chciałbym, żeby doktorzy cmake rozwinęli, dlaczego zalecają ludziom unikanie globbingu.
Dan,
1
Myślałem o rozwiązaniu polegającym na zapisaniu do pliku znacznika czasu wykonania ostatniego cmake'a. Jedynymi problemami są: 1) prawdopodobnie musi to być zrobione przez cmake, aby było wieloplatformowe, więc musimy jakoś uniknąć tego, że cmake uruchamia się samoczynnie po raz drugi. 2) Prawdopodobnie więcej konfliktów przy scalaniu (które nadal występują z listą plików btw). W tym przypadku można by je rozwiązać trywialnie, biorąc późniejszy znacznik czasu.
Predelnik
2
@ tim-mb, "Ale byłoby miło, gdyby CMake utworzył plik filetree_updated, który można by zaewidencjonować, który byłby automatycznie zmieniany przy każdej aktualizacji globu plików." - dokładnie opisałeś, co robi moja odpowiedź.
Glen Knowles
21

W CMake 3.12 polecenia file(GLOB ...)ifile(GLOB_RECURSE ...) zyskały CONFIGURE_DEPENDSopcję, która ponownie uruchamia cmake, jeśli zmieni się wartość globu. Ponieważ była to główna wada globbingu dla plików źródłowych, teraz można to zrobić:

# 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})

Jednak niektórzy ludzie nadal zalecają unikanie globbingu dla źródeł. Rzeczywiście, dokumentacja stwierdza:

Nie zalecamy używania GLOB do zbierania listy plików źródłowych z twojego drzewa źródłowego. ... CONFIGURE_DEPENDSFlaga może nie działać niezawodnie na wszystkich generatorach lub jeśli w przyszłości zostanie dodany nowy generator, który nie może go obsługiwać, projekty, które go używają, utkną. Nawet jeśli CONFIGURE_DEPENDSdziała niezawodnie, nadal istnieje koszt przeprowadzenia kontroli przy każdej przebudowie.

Osobiście uważam, że korzyści wynikające z braku konieczności ręcznego zarządzania listą plików źródłowych przeważają nad możliwymi wadami. Jeśli musisz przełączyć się z powrotem na ręcznie wymienione pliki, można to łatwo osiągnąć, po prostu drukując listę źródeł globbed i wklejając ją z powrotem.

Justin
źródło
Jeśli twój system kompilacji wykonuje pełny cykl cmake i kompilacji (usuń katalog kompilacji, uruchom go stamtąd, a następnie wywołaj plik makefile), pod warunkiem, że nie ściągają niechcianych plików, z pewnością nie ma żadnych wad korzystania ze źródeł GLOBbed? Z mojego doświadczenia wynika, że ​​część cmake działa znacznie szybciej niż kompilacja, więc i tak nie jest to duży narzut
Den-Jason
9

Możesz bezpiecznie globować (i prawdopodobnie powinieneś) kosztem dodatkowego pliku do przechowywania zależności.

Dodaj gdzieś takie funkcje:

# 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)

A potem przejdź do globu:

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

Nadal poruszasz się po jawnych zależnościach (i uruchamiasz wszystkie automatyczne kompilacje!), Jak poprzednio, tylko w dwóch plikach zamiast w jednym.

Jedyna zmiana w procedurze następuje po utworzeniu nowego pliku. Jeśli tego nie zrobisz, przepływ pracy polega na zmodyfikowaniu CMakeLists.txt z poziomu programu Visual Studio i ponownym skompilowaniu, jeśli wykonujesz glob, uruchamiasz cmake jawnie - lub po prostu dotykasz CMakeLists.txt.

Glen Knowles
źródło
Na początku myślałem, że jest to narzędzie, które automatycznie aktualizuje pliki Makefiles po dodaniu pliku źródłowego, ale teraz widzę, jaka jest jego wartość. Miły! To rozwiązuje problem, że ktoś aktualizuje z repozytorium i makepodaje dziwne błędy linkera.
Cris Luengo,
1
Uważam, że to może być dobra metoda. Oczywiście trzeba jeszcze pamiętać, aby wywołać cmake po dodaniu lub usunięciu pliku, a także wymaga to zatwierdzenia tego pliku zależności, więc po stronie użytkownika konieczna jest pewna edukacja. Główną wadą może być to, że ten plik zależności może powodować nieprzyjemne konflikty scalania, które mogą być trudne do rozwiązania bez ponownego wymagania od dewelopera zrozumienia mechanizmu.
Antonio
1
To nie zadziała, jeśli projekt zawiera warunkowo dołączone pliki (np. Niektóre pliki, które są używane tylko wtedy, gdy funkcja jest włączona, lub używane tylko dla określonego systemu operacyjnego). W przypadku oprogramowania przenośnego jest to dość powszechne, że niektóre pliki są używane tylko na specjalnych platformach.
ideasman42
0

Określ każdy plik indywidualnie!

Używam konwencjonalnego CMakeLists.txt i skryptu w Pythonie, aby go zaktualizować. Po dodaniu plików uruchamiam skrypt Pythona ręcznie.

Zobacz moją odpowiedź tutaj: https://stackoverflow.com/a/48318388/3929196

palfi
źródło