Ist es besser, Quelldateien mit GLOB oder jede Datei einzeln in CMake anzugeben?

157

CMake bietet verschiedene Möglichkeiten, um die Quelldateien für ein Ziel anzugeben. Eine ist die Verwendung von Globbing ( Dokumentation ), zum Beispiel:

FILE(GLOB MY_SRCS dir/*)

Eine andere Methode besteht darin, jede Datei einzeln anzugeben.

Welcher Weg wird bevorzugt? Globbing scheint einfach zu sein, aber ich habe gehört, dass es einige Nachteile hat.

Marenz
quelle

Antworten:

185

Vollständige Offenlegung: Ich habe den Globbing-Ansatz ursprünglich wegen seiner Einfachheit bevorzugt, aber im Laufe der Jahre habe ich erkannt, dass das explizite Auflisten der Dateien für große Projekte mit mehreren Entwicklern weniger fehleranfällig ist.

Ursprüngliche Antwort:


Die Vorteile von Globbing sind:

  • Es ist einfach, neue Dateien hinzuzufügen, da diese nur an einer Stelle aufgelistet sind: auf der Festplatte. Nicht globbing führt zu Duplikaten.

  • Ihre CMakeLists.txt-Datei wird kürzer. Dies ist ein großes Plus, wenn Sie viele Dateien haben. Wenn Sie nicht globalisieren, verlieren Sie die CMake-Logik in großen Dateilisten.

Die Verwendung von fest codierten Dateilisten bietet folgende Vorteile:

  • CMake verfolgt die Abhängigkeiten einer neuen Datei auf der Festplatte korrekt. Wenn wir glob verwenden, werden Dateien, die beim ersten Ausführen von CMake nicht globalisiert wurden, nicht erfasst

  • Sie stellen sicher, dass nur die gewünschten Dateien hinzugefügt werden. Globbing kann Streudateien aufnehmen, die Sie nicht möchten.

Um das erste Problem zu umgehen, können Sie einfach die CMakeLists.txt "berühren", die den Glob ausführt, entweder mit dem Befehl touch oder indem Sie die Datei ohne Änderungen schreiben. Dadurch wird CMake gezwungen, die neue Datei erneut auszuführen und aufzunehmen.

Um das zweite Problem zu beheben, können Sie Ihren Code sorgfältig in Verzeichnissen organisieren, was Sie wahrscheinlich sowieso tun. Im schlimmsten Fall können Sie den list(REMOVE_ITEM)Befehl verwenden, um die globale Liste der Dateien zu bereinigen:

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

Die einzige reale Situation, in der Sie dies beißen kann, ist, wenn Sie so etwas wie git-bisect verwenden , um ältere Versionen Ihres Codes im selben Build-Verzeichnis zu testen . In diesem Fall müssen Sie möglicherweise mehr als erforderlich bereinigen und kompilieren, um sicherzustellen, dass Sie die richtigen Dateien in der Liste erhalten. Dies ist ein solcher Eckfall, bei dem Sie bereits auf den Beinen sind, dass es nicht wirklich ein Problem ist.

richq
quelle
1
Ebenfalls schlecht beim Globbing: Die Difftool-Dateien von git werden als $ basename. $ Ext. $ Type. $ Pid. $ Ext gespeichert, was beim Kompilieren nach einer einzigen Zusammenführungsauflösung zu lustigen Fehlern führen kann.
Mathstuf
9
Ich denke, diese Antwort beschönigt die Nachteile des Fehlens neuer Dateien durch cmake. Simply "touch" the CMakeLists.txtWenn Sie Entwickler sind , ist dies in Ordnung. Für andere, die Ihre Software erstellen, kann es jedoch ein Problem sein, dass Ihr Build nach der Aktualisierung fehlschlägt und sie zu untersuchen sind Warum.
ideasman42
36
Weißt du was? Seit ich diese Antwort vor 6 Jahren geschrieben habe , habe ich meine Meinung ein wenig geändert und ziehe es jetzt vor, Dateien explizit aufzulisten. Es ist nur ein wirklicher Nachteil, "es ist ein bisschen mehr Arbeit, eine Datei hinzuzufügen", aber es erspart Ihnen alle möglichen Kopfschmerzen. Und in vielerlei Hinsicht ist explizit besser als implizit.
Richq
1
@richq Würde dieser Git-Hook Sie dazu bringen, Ihre aktuelle Position zu überdenken? :)
Antonio
8
Wie Antonio sagt, wurden die Stimmen für die Befürwortung des "Globbing" -Ansatzes abgegeben. Das Ändern der Art der Antwort ist für diese Wähler eine Köder-und-Schalter-Sache. Als Kompromiss habe ich eine Bearbeitung hinzugefügt, um meine geänderte Meinung widerzuspiegeln. Ich entschuldige mich im Internet für einen solchen Sturm in einer Teetasse :-P
richq
113

Der beste Weg, Quelldateien in CMake anzugeben, besteht darin, sie explizit aufzulisten .

Die Entwickler von CMake selbst raten davon ab , Globbing zu verwenden.

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

(Wir empfehlen, GLOB nicht zum Sammeln einer Liste von Quelldateien aus Ihrem Quellbaum zu verwenden. Wenn sich beim Hinzufügen oder Entfernen einer Quelle keine CMakeLists.txt-Datei ändert, kann das generierte Build-System nicht wissen, wann CMake zur Neuerstellung aufgefordert werden soll.)

Natürlich möchten Sie vielleicht wissen, was die Nachteile sind - lesen Sie weiter!


Wenn Globbing fehlschlägt:

Der große Nachteil von Globbing ist, dass das Erstellen / Löschen von Dateien das Build-System nicht automatisch aktualisiert.

Wenn Sie die Person sind, die die Dateien hinzufügt, scheint dies ein akzeptabler Kompromiss zu sein. Dies führt jedoch zu Problemen für andere Personen, die Ihren Code erstellen. Sie aktualisieren das Projekt über die Versionskontrolle, führen den Build aus und setzen sich dann mit Ihnen in Verbindung. Sie beschweren sich, dass
"der Build" ist gebrochen".

Um die Sache noch schlimmer zu machen, führt der Fehler normalerweise zu einem Verbindungsfehler, der keine Hinweise auf die Ursache des Problems gibt, und es geht Zeit verloren, das Problem zu beheben.

In einem Projekt, an dem ich gearbeitet habe, haben wir mit dem Globbing begonnen, aber beim Hinzufügen neuer Dateien gab es so viele Beschwerden, dass es Grund genug war, Dateien explizit aufzulisten, anstatt Globbing.

Dies unterbricht auch gängige Git-Workflows
( git bisectund das Umschalten zwischen Feature-Zweigen).

Daher kann ich dies nicht empfehlen. Die Probleme, die dadurch verursacht werden, überwiegen bei weitem die Bequemlichkeit. Wenn jemand Ihre Software aus diesem Grund nicht erstellen kann, verliert er möglicherweise viel Zeit, um das Problem aufzuspüren oder einfach aufzugeben.

Und noch ein Hinweis: Nur daran zu denken, etwas zu berühren, reicht CMakeLists.txtnicht immer aus. Bei automatisierten Builds, die Globbing verwenden, musste ich cmakevor jedem Build ausgeführt werden, da möglicherweise Dateien seit dem letzten Build hinzugefügt / entfernt wurden *.

Ausnahmen von der Regel:

Es gibt Zeiten, in denen Globbing vorzuziehen ist:

  • Zum Einrichten von CMakeLists.txtDateien für vorhandene Projekte, die CMake nicht verwenden.
    Dies ist ein schneller Weg, um alle Quellen zu referenzieren (sobald das Build-System ausgeführt wird - ersetzen Sie Globbing durch explizite Dateilisten).
  • Wenn CMake nicht als primäres Build-System verwendet wird, wenn Sie beispielsweise ein Projekt verwenden, das CMake nicht verwendet, und Sie möchten Ihr eigenes Build-System dafür verwalten.
  • Für jede Situation, in der sich die Dateiliste so oft ändert, dass die Pflege unpraktisch wird. In diesem Fall könnte es nützlich sein, aber dann müssen Sie das Ausführen akzeptieren cmake, um jedes Mal Build-Dateien zu generieren, um einen zuverlässigen / korrekten Build zu erhalten (was gegen die Absicht von CMake verstößt - die Möglichkeit, die Konfiguration vom Erstellen zu trennen) .

* Ja, ich hätte einen Code schreiben können, um den Baum der Dateien auf der Festplatte vor und nach einem Update zu vergleichen, aber dies ist keine so gute Problemumgehung und etwas Besseres, das dem Build-System überlassen bleibt.

ideasman42
quelle
9
"Der große Nachteil von Globbing ist, dass das Erstellen neuer Dateien das Build-System nicht automatisch aktualisiert." Aber stimmt es nicht, dass Sie CMakeLists.txt immer noch manuell aktualisieren müssen, wenn Sie nicht global sind, was bedeutet, dass cmake das Build-System immer noch nicht automatisch aktualisiert? In beiden Fällen müssen Sie daran denken, manuell etwas zu tun, damit die neuen Dateien erstellt werden können. Das Berühren von CMakeLists.txt scheint einfacher zu sein, als es zu öffnen und zu bearbeiten, um die neue Datei hinzuzufügen.
Dan
17
@Dan, für Ihr System - sicher, wenn Sie nur alleine entwickeln, ist das in Ordnung, aber was ist mit allen anderen, die Ihr Projekt erstellen ? Wirst du ihnen eine E-Mail senden, um die CMake-Datei manuell zu berühren? jedes Mal, wenn eine Datei hinzugefügt oder entfernt wird? - Durch das Speichern der Dateiliste in CMake wird sichergestellt, dass der Build immer dieselben Dateien verwendet, die vcs kennt. Glauben Sie mir - dies ist nicht nur ein subtiles Detail. Wenn Ihr Build für viele Entwickler fehlschlägt, senden sie Listen per E-Mail und fragen im IRC, ob der Code fehlerhaft ist. Hinweis: (Selbst auf Ihrem eigenen System können Sie beispielsweise in den Git-Verlauf zurückkehren und nicht daran denken, CMake-Dateien zu berühren.)
ideasman42
2
Ah, ich hatte nicht an diesen Fall gedacht. Das ist der beste Grund, den ich gegen Globbing gehört habe. Ich wünschte, die cmake-Dokumente würden erweitert, warum sie Menschen empfehlen, das Globbing zu vermeiden.
Dan
1
Ich habe über eine Lösung nachgedacht, um den Zeitstempel der letzten cmake-Ausführung in eine Datei zu schreiben. Die einzigen Probleme sind: 1) Es muss wahrscheinlich von cmake gemacht werden, um plattformübergreifend zu sein, und so müssen wir vermeiden, dass cmake sich irgendwie selbst zum zweiten Mal ausführt. 2) Möglicherweise mehr Zusammenführungskonflikte (die übrigens immer noch mit der Dateiliste auftreten). Sie könnten in diesem Fall tatsächlich trivial gelöst werden, indem ein späterer Zeitstempel verwendet wird.
Predelnik
2
@ tim-mb, "Aber es wäre schön, wenn CMake eine filetree_updated-Datei erstellen würde, die Sie einchecken könnten. Diese würde sich jedes Mal automatisch ändern, wenn der Globus der Dateien aktualisiert wird." - Sie haben gerade genau beschrieben, was meine Antwort bewirkt.
Glen Knowles
21

In CMake 3.12, das file(GLOB ...)undfile(GLOB_RECURSE ...) gewann Befehle eine CONFIGURE_DEPENDSOption , die Wiederholungen , wenn die glob des Wertänderungen cmake. Da dies der Hauptnachteil des Globbings für Quelldateien war, ist dies jetzt in Ordnung:

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

Einige Leute empfehlen jedoch immer noch, das Durchsuchen von Quellen zu vermeiden. In der Dokumentation heißt es in der Tat :

Wir empfehlen, GLOB nicht zum Sammeln einer Liste von Quelldateien aus Ihrem Quellbaum zu verwenden. ... Das CONFIGURE_DEPENDSFlag funktioniert möglicherweise nicht bei allen Generatoren zuverlässig. Wenn in Zukunft ein neuer Generator hinzugefügt wird, der ihn nicht unterstützt, bleiben Projekte, die ihn verwenden, hängen. Selbst wenn dies CONFIGURE_DEPENDSzuverlässig funktioniert, fallen bei jedem Umbau Kosten für die Überprüfung an.

Persönlich betrachte ich die Vorteile, die Quelldateiliste nicht manuell verwalten zu müssen, um die möglichen Nachteile aufzuwiegen. Wenn Sie zu manuell aufgelisteten Dateien zurückkehren müssen, können Sie dies einfach erreichen, indem Sie einfach die Globbed-Quellliste drucken und wieder einfügen.

Justin
quelle
Wenn Ihr Build-System einen vollständigen cmake- und Build-Zyklus ausführt (das Build-Verzeichnis löschen, cmake von dort aus ausführen und dann das Makefile aufrufen), gibt es keine Nachteile bei der Verwendung von GLOBbed-Quellen, sofern keine unerwünschten Dateien abgerufen werden. Nach meiner Erfahrung läuft der cmake-Teil viel schneller als der Build, daher ist es sowieso kein so großer Aufwand
Den-Jason
9

Sie können sicher (und sollten wahrscheinlich) auf Kosten einer zusätzlichen Datei globalisieren, um die Abhängigkeiten zu speichern.

Fügen Sie irgendwo solche Funktionen hinzu:

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

Und dann los geht's:

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

Sie karren immer noch wie zuvor um die expliziten Abhängigkeiten (und lösen alle automatisierten Builds aus!), Nur in zwei Dateien anstelle von einer.

Die einzige Änderung in der Prozedur erfolgt, nachdem Sie eine neue Datei erstellt haben. Wenn Sie nicht global arbeiten, besteht der Workflow darin, CMakeLists.txt in Visual Studio zu ändern und neu zu erstellen. Wenn Sie glob ausführen, führen Sie cmake explizit aus - oder berühren Sie einfach CMakeLists.txt.

Glen Knowles
quelle
Zuerst dachte ich, dies sei ein Tool, das die Makefiles automatisch aktualisiert, wenn eine Quelldatei hinzugefügt wird, aber jetzt sehe ich, welchen Wert sie hat. Nett! Dies löst das Problem, dass jemand aus dem Repository aktualisiert und makeseltsame Linkerfehler gemeldet hat.
Cris Luengo
1
Ich glaube, das könnte eine gute Methode sein. Man muss natürlich immer noch daran denken, cmake nach dem Hinzufügen oder Entfernen einer Datei auszulösen, und es ist auch erforderlich, diese Abhängigkeitsdatei festzuschreiben, so dass eine gewisse Schulung auf der Benutzerseite erforderlich ist. Der Hauptnachteil könnte sein, dass diese Abhängigkeitsdatei zu bösen Zusammenführungskonflikten führen kann, die möglicherweise schwer zu lösen sind, ohne dass der Entwickler erneut ein gewisses Verständnis des Mechanismus benötigt.
Antonio
1
Dies funktioniert nicht, wenn Ihr Projekt bedingt Dateien enthält (z. B. einige Dateien, die nur verwendet werden, wenn eine Funktion aktiviert ist, oder nur für ein bestimmtes Betriebssystem). Bei tragbarer Software ist es häufig genug, dass einige Dateien nur für bestimmte Plattformen verwendet werden.
ideasman42
0

Geben Sie jede Datei einzeln an!

Ich verwende eine herkömmliche CMakeLists.txt und ein Python-Skript, um es zu aktualisieren. Ich führe das Python-Skript manuell aus, nachdem ich Dateien hinzugefügt habe.

Siehe meine Antwort hier: https://stackoverflow.com/a/48318388/3929196

palfi
quelle