Frage zu makefiles



  • Hi,

    ich beschäftige mich gerade mit makefiles aber habe dabei leider ein paar Verständisprobleme. Wenn ich mir bspw. dieses makefile ansehe:

    c_files = $(wildcard ./src/*.c)
    o_files = $(OBJ:%.c=%.o)
    
    all: test1
    
    clean:
    	rm test1
    	rm *.o
    
    test1: $(o_files)
    gcc -o test1 $(o_files) $(c_files)
    

    würde das auch schon ausreichen für ein makefile?

    Da werden ja oben erst einmal alle c Dateien mit der Wildcard Funktion aus dem src Ordner geholt aber die zweite Zeile verstehe ich auch ehrlich gesagt nicht. Was sagt die zweite Zeile und wofür stehen der : und das % ?

    Habe ich das auch richtig verstanden das mit dem clean oben alle .o Dateien neu erzeugt werden? Bei dem makefile wundert mich ehrlich gesagt auch, dass dort keine h Dateien dabei sind oder reicht es auch, wenn man die .o Dateien hat, die ja dann schließlich nachher mit dem linker zu einem ausführbarem Programm zusammengefügt werden?

    Auch noch einmal zur Sicherheit das ich es richtig verstanden habe:

    test1: $(o_files)
    gcc -o test1 $(o_files) $(c_files)
    

    Damit sagt man ja, das die o_files von den c_files abhängen und das gcc ein .o file, also eine ausführbare Datei erzeugen soll mit dem Namen test1, richtig?



  • Dieser Thread wurde von Moderator/in SeppJ aus dem Forum C (alle ISO-Standards) in das Forum Rund um die Programmierung verschoben.

    Im Zweifelsfall bitte auch folgende Hinweise beachten:
    C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?

    Dieses Posting wurde automatisch erzeugt.


  • Mod

    Hast du mal die Doku zu make bemüht¹?

    Die zweite Zeile nimmt den Inhalt der Variablen OBJ und ersetzt alle .c durch .o. Dokumentiert hier: http://www.gnu.org/software/make/manual/make.html#Substitution-Refs
    Es dürfte darauf hinaus laufen, das diese Liste leer ist.

    Das clean löscht alle .o-Dateien. rm steht für remove.

    .h-Dateien werden tatsächlich nicht beachtet. Dir ist schon klar, dass .h-Dateien nicht compiliert werden? Nichtsdestotrotz wird nicht beachtet, wenn eine .h-Datei geändert wurde.

    Die Klausel für test1 sagt, dass test1 von den Klauseln $(o_files) abhängt. Sofern diese Abhängigkeiten aktuell sind, wird dann gcc mit den gezeigten Parametern aufgerufen. Die Zeile besagt, dass ein ausführbares Programm (das ist etwas anderes als eine reine Objektdatei!) erzeugt werden soll aus den Dateien, die in $(o_files) und $(c_files) stehen. Das Programm soll test1 genannt werden.

    würde das auch schon ausreichen für ein makefile?

    Es ist ein syntaktisch korrektes Makefile. Ich bezweifle aber, dass es tut, was beabsichtigt war. Fehler/Schwächen:
    -Der ganze Sinn von Makefiles ist dahin, da sowieso alles in einer einzigen Klausel compiliert wird, anstatt einzeln die Abhängigkeiten zu prüfen
    -Die Abhängigkeiten sind höchstwahrscheinlich falsch. Es sieht aus wie eine leere Liste. Wahrscheinlich hätten dort die Objektdateien stehen sollen.
    -Die Objektdateien würden aber wiederum gar nicht erst erzeugt, da es keine entsprechenden Klauseln gibt.
    -Vieles mehr.

    Entweder hat der Autor sehr spezielle Ansprüche gehabt oder keine Ahnung.

    ¹: Hier verlinkt auf GNU make, welches nicht das make ist, aber so weit verbreitet, dass es für den Zweck dieses Threads als das make gelten kann.



  • Nein, die DOKU zu make hatte ich noch nicht bemnüht. Ehrlich gesagt habe ich aber auch nicht danach gesucht, sondern wie man makefiles erstellt.

    Dann lag ich ja mit dem clean und den Header Dateien richtig? Man kann dann ja make clean eingeben und alle .o Dateien werden gelöscht. Das Header Dateien nicht kompilier werden können ist mir schon klar aber das habe ich ja uach nie behauptet?

    Ich habe dann jetzt auch mal versucht ein kleines makefile selber zu erzuegen. Wäre das denn so okay oder fehlt da noch was wichtiges?

    c_files = $(wildcard ./src/*.c)
    h_files = $(wildcard ./src/*.h)
    
    all: test1
    
    clean:
        rm test1
        rm *.o
    
    test1: $(h_files) $(c_files)
    gcc -o test1 $(c_files)
    

    Sollte man solche Makefiles denn auch nicht eigentlich kurz halten? Man will ja eigentlich schließlich nicht jede Abhängikeit einzeln manuell in das Makefile einpflegen, gerade bei größere Projekten, oder nicht?

    Zu dem Autor kann ich ehrlich gesagt nicht viel sagen aber das Makefile war schon noch ein wenig länger, als das was ich oben geschrieben habe aber von den targets her stand da sonst auch nur noch ein fine und noch etwas für Compiler Warnungen, wenn ich mich recht entsinne.


  • Mod

    Das neue Makefile compiliert nach wie vor alles auf einmal, ohne irgendwelche Objektdateien, wodurch es relativ sinnfrei ist, überhaupt make zu beutzen. Da kann man ja auch gleich gcc *.c -o test machen und hätte so ziemlich den gleichen Effekt.

    Außerdem sollte clean ein phony-Target sein.

    Liq schrieb:

    Sollte man solche Makefiles denn auch nicht eigentlich kurz halten? Man will ja eigentlich schließlich nicht jede Abhängikeit einzeln manuell in das Makefile einpflegen, gerade bei größere Projekten, oder nicht?

    Doch, das ist doch gerade der Sinn, dass alle Abhängigkeiten korrekt angegeben werden. Und nein, das muss man nicht alles manuell machen, es gibt auch Makefilegeneratoren, die das für einen machen. Aber wenigstens könnte man auch beim Selberschreiben dafür sorgen, dass man erst aus jeder Sorucedatei eine Objektdatei macht und dann aus diesen das vollständige Programm. Das ist weder schwer noch lang. Der ursprüngliche Autor hatte dies wohl vor, bevor du sein Werk kastriert hast.

    Sieh dir das hier mal an:
    https://www.c-plusplus.net/forum/88418



  • SeppJ schrieb:

    Der ursprüngliche Autor hatte dies wohl vor, bevor du sein Werk kastriert hast.

    Alle haben das vor. Alles andere wäre doch Irrsinn, oder?



  • Können GCC/Clang bzw. andere übliche C bzw. C++ Compiler das echt nicht selbst?
    Also checken was neu gebaut werden muss und was nicht meine ich.



  • hustbaer schrieb:

    Können GCC/Clang bzw. andere übliche C bzw. C++ Compiler das echt nicht selbst?
    Also checken was neu gebaut werden muss und was nicht meine ich.

    Ist ja nicht ihre Aufgabe. Dafuer hast du dein Buildsystem.

    Es geht dabei ja auch nicht nur um die Abhaengigkeiten sondern um den ganzen Buildprozess - da sind die Abhaengigkeiten doch sowieso das geringste.

    Deshalb schreibt man ja auch keine makefiles von Hand. Man sagt seiner IDE einfach "Build" und die macht das, baut die Tests auch dazu, inkrementiert die Buildnumber, linked die passenden Bibliotheken, haengt automatisch den Debugger an den Prozess, etc.

    Wozu sowas in den Compiler packen? Dafuer hat man das Buildsystem.



  • Wieso sollte das nicht Aufgabe des Compilers sein? Natürlich ist es Aufgabe des Compilers! Bzw. sollte es eben sein.
    Der Compiler weiss ab besten welche Files er inkludieren wird und welche nicht. Muss er, sonst könnte er nicht compilieren. Er weiss welche vordefinierten Makros es gibt und welche Werte sie haben und all das. Er kennt die Regeln des Präprozessors. Die Regeln und Einstellungen wo und wie Include-Files gesucht werden.
    Das alles einem anderen Tool beizubringen ist doch bloss unnötig komplex und schafft zusätzliche Fehlerquellen.

    Und es ist ineffizient. Wieso zuerst alle .c und .h Files scannen und dann gucken was man später wird machen müssen, nur um beim Machen dann wieder alle .c und .h Files zu scannen?

    Weiters kommen noch Dinge hinzu wie dass es nett wäre wenn das System automatisch erkennen kann wenn ein Rebuild nötig wird, weil sich z.B. Compiler-Switches verändert haben. Oder gar ein neuerer Compiler verwendet wird. Klar, mögliche Fehler dadurch kann man abfedern indem man dem Compiler und Linker wenigstens beibringt mit nem Fehler abzubrechen wenn etwas nicht zusammenpasst. Wenn man es dagegen dem Compiler überlässt sich darum zu kümmern, dann kann er das. Er braucht ja nur ins .o File irgendwie die Compilerversion und verwendeten Switches mit reinschreiben, und dann kann er sehen dass er neu bauen muss, obwohl sich an den .c/.h Files nix geändert hat.

    ps: MSVC kann es und macht es und es ist gut so 🙂
    Ob die Sache mit den Compiler-Switches bzw. Updates von MSVC selbst erledigt wird oder von MSBuild weiss ich nicht. Die Include-Dependencies und das "nicht neu Bauen" von .c/.cpp Files die eben nicht neu gebaut werden müssen übernimmt aber wenn ich mich nicht sehr irre die cl.exe selbst.



  • hustbaer schrieb:

    Wieso sollte das nicht Aufgabe des Compilers sein? Natürlich ist es Aufgabe des Compilers! Bzw. sollte es eben sein.

    Nein. In C++ ist es anders. Der Compiler baut die Objekt Dateien, der Linker verbindet die Objekt Dateien.

    Klar kann es anders sein. Aber es ist nicht anders und ohne Modul System macht es auch keinen Sinn die Komplexität der Compiler sinnlos zu erhöhen.

    zumal, wie gesagt, die Abhängigkeiten das geringste Problem sind: ein Buildsystem brauchst du so oder so. Es würde also absolut keinen Vorteil bringen wenn Compiler selber die .dep Dateien anlegen würde.

    zu mal das recht kompliziert auf Compilerebene zu implementieren ist. Woher soll der Compiler denn merken dass du plötzlich NDEBUG gesetzt hast oder nicht? Da braucht er plötzlich was? ja genau, ein Buildsystem. Denn Änderungen der Präprozessor Flags können ja ändern was wie wovon abhängt. Man braucht also Targets. Diese muss man steuern können und wir sind wieder bei einem Buildsystem.

    Ist das super modern und super cool? Wahrscheinlich nicht. Aber so funktioniert das mit Präprozessorn nun mal leider. Und die Make funktionlität in die Compiler zu packen ist keine gute Idee.

    In Sprachen wie Java, da ist jede .class File eigenständig, in C++ gibt es sowas nicht. Deshalb braucht man ein komplexes System um das vernünftig handeln zu können: nämlich das Buildsystem.

    Und das hat im Compiler nichts verloren.



  • Shade Of Mine schrieb:

    Es würde also absolut keinen Vorteil bringen wenn Compiler selber die .dep Dateien anlegen würde.

    Wozu sollte man bitte .dep Dateien brauchen? Weil wir das schon immer so gemacht haben?

    Shade Of Mine schrieb:

    zu mal das recht kompliziert auf Compilerebene zu implementieren ist. Woher soll der Compiler denn merken dass du plötzlich NDEBUG gesetzt hast oder nicht? Da braucht er plötzlich was? ja genau, ein Buildsystem.

    Der Compiler bekommt von wem-auch-immer mitgeteilt wie er zu compilieren hat. Die ganzen Optionen/Switches halt. Dazu gehört NDEBUG oder nicht NDEBUG. Und genau so "merkt" er es.

    Shade Of Mine schrieb:

    Denn Änderungen der Präprozessor Flags können ja ändern was wie wovon abhängt. Man braucht also Targets. Diese muss man steuern können und wir sind wieder bei einem Buildsystem.

    Ich rede nicht davon dass man kein Build-System braucht. Das ist bloss dein Strohmann damit du dagegen argumentieren kannst.
    Ich rede davon dass das Build-System nicht die #include Dependencies checken sollte, sondern dieser Teil der Job des Compilers ist.

    Shade Of Mine schrieb:

    Ist das super modern und super cool? Wahrscheinlich nicht. Aber so funktioniert das mit Präprozessorn nun mal leider.

    Blödsinn. Du hast kein einziges Argument gebracht ausser "geht so nicht".

    Shade Of Mine schrieb:

    Und die Make funktionlität in die Compiler zu packen ist keine gute Idee.

    Weil. Ah, genau, deswegen.

    Shade Of Mine schrieb:

    In Sprachen wie Java, da ist jede .class File eigenständig, in C++ gibt es sowas nicht. Deshalb braucht man ein komplexes System um das vernünftig handeln zu können: nämlich das Buildsystem.

    Und das hat im Compiler nichts verloren.

    Nein.



  • Skizziere mir doch mal wie der Compiler die dependencies korrekt checkt. Denke dabei aber bitte an Änderungen der Compilerflags.

    Ich zeig dir dann deine Probleme auf, Ok?



  • Shade, mit dir zu diskutieren ist anstrengend. Aber OK.

    Variante 1:
    Trivial. Der Compiler macht genau das selbe wie das vom Build-System verwendete Tool zum Erzeugen der .dep Files. In einem extra Schritt vor dem Compilieren.
    Dadurch dass es der Compiler macht, und nicht ein unabhängiges Tool, ergeben sich aber Vorteile:
    * Der Compiler hat bereits Code für sämtliche Regeln die er umzusetzen hat, also z.B. den ganzen Präprozessor.
    Diesen kann er im Dependency-Check Teil wiederverwenden. Man vermeidet also Code-Duplizierung.
    * Man erspart sich dadurch dass zwei verschiedene Tools mit den selben Informationen gefüttert werden müssen, und diese gleich zu interpretieren haben. Man vermeidet also Fehler durch Unterschiede.
    * Im Prinzip nochmal das selbe: Der Compiler weiss sowieso schon welche vordefinierten Makros er mit den aktuellen Switches hat. Man vermeidet also weitere Code-Duplizierung.
    Wie man hier die Sache mit unterschiedlichen Switches handhaben würde kann ich nicht sagen, da ich nicht weiss wie es externe Dependency-Checker Tools machen. Ich nehme einfach mal an dass der Compiler das selbe machen kann wie diese Tools bzw. das Build-System.

    Variante 2:
    Der Compiler lässt über jedes *.c(pp) File den Präprozessor drüberlaufen. Dabei "merkt" er ja wohl welche anderen Files inkludiert werden. Er merkt sich das aktuellste "last write" Datum aller Files die er dabei gelesen hat (angefangen mit dem *.c(pp) File). Weiters erstellt er eine Liste mit sämtlichen Optionen/Switches/Settings, inklusive per Command-Line definierte Symbole, Compiler-Version, Such-Verzeichnisse die per Environment-Variablen eingestellt wurden etc.
    Nach dem Durchlauf des PP hat er also das oben erwähnte Datum und diese Settings-Collection.
    Jetzt macht er das .o File auf, und guckt ob das zusammenpasst:
    - Ist das Datum des .o Files >= dem ermittelten Datum? Nein? => Weitermachen.
    - Ist im .o File ein Eintrag mit genau den selben Settings zu finden wie er aktuell ermittelt hat? Nein? => Weitermachen.
    Ansonsten kann er das File überspringen.



  • ps:
    Hab jetzt ein wenig dazu gegoogelt... und ich war da wohl nicht so ganz auf dem Laufenden.

    Da ich nicht mit GCC/Clang/*NIX/... arbeite, und es schon recht lange her ist dass ich mir die entsprechenden Build-Systeme angesehen habe, kannte ich nur die Variante mit einem externem, vom Compiler getrennten Tool das die Source-Files scannt und dann die entsprechenden Dependency Files erzeugt. In einem eigenen Durchgang vom dem eigentlichen Compilieren.
    Also das:
    http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/#traditional
    Diese Variante halte ich aus den bereits Beschreibenen Gründen immer noch für hochgradig beknackt.

    Die Variante wo man das .d File vom Compiler erstellen lässt, ist dagegen weit weniger krank, da sie einen Grossteil der von mir angesprochenen Probleme/Nachteile eliminiert.
    Sowas macht wohl irgendwie Sinn, ja.

    Bin aber immer noch nicht ganz überzeugt davon dass mehr dafür spricht als dagegen.



  • hustbaer schrieb:

    Bin aber immer noch nicht ganz überzeugt davon dass mehr dafür spricht als dagegen.

    Eigentlich sollte Dein Rechner schon anno 2000 nicht weniger als 10000 Dateien pro Sekunde fstat()en gekonnt haben. Daß die IDE Informationen cachen müßte, ist sowas von 90-er.

    Und ab dann hängt's vom persönlichen Gusto ab.

    Ich zum Beispiel mag sehr Code::Blocks, dem ich ein eigenes universal makefile unterschubse. Code::Blocks kann dabei nicht mehr als Syntax-Highlighting und eventuell Tool-Tips und Autovervollständigung laut Doxygen-Kommentaren like IntelliSense. Ich habe das makefile in der Hand, was ich großartig finde.

    Mit MSVC kriege ich immer online farbige Kringellinien unter meinen Tippfehjlern angeuzigt. Das ist auch großartig. Aber MSVC benutzt NICHT dazu den Compiler! MSVC lügt mich mit den Kringeln echt oft an.

    Derzeit benutze ich zu 95% sowas wie notepad.exe und ganz selten Code::Blocks und MSVC nur, wenn ich muss.



  • @volkard
    Ich glaub du hast mich misverstanden.
    Wenn ich schreibe "darum soll der Compiler sich kümmern", dann meine ich schon den Compiler, NICHT die IDE.

    D.h man würde dann (per Makefile oder wie auch immer) immer
    cl.exe /Switch1 /Switch2 ... File1.cpp File2.cpp ...
    aufrufen, mit allen Files auf einmal. Also "immer alles compilieren", und der Compiler soll sich darum kümmern zu gucken was wirklich gebaut werden muss und was nicht.

    volkard schrieb:

    Ich zum Beispiel mag sehr Code::Blocks, dem ich ein eigenes universal makefile unterschubse.

    Und wie handhabt dein Makefile die Sache mit den include-Dependencies?



  • hustbaer schrieb:

    @volkard
    Ich glaub du hast mich misverstanden.
    Wenn ich schreibe "darum soll der Compiler sich kümmern", dann meine ich schon den Compiler, NICHT die IDE.

    Also eigentlich make.exe.

    hustbaer schrieb:

    Und wie handhabt dein Makefile die Sache mit den include-Dependencies?

    Optimalerweise ganz normal mit .dep-files.

    Qt und so Leute betreiben dailyWTF statt klarer Abhängigkeiten und build-Vorgängen. Oder boost mit jam, es ist zum Heulen.



  • volkard schrieb:

    hustbaer schrieb:

    @volkard
    Ich glaub du hast mich misverstanden.
    Wenn ich schreibe "darum soll der Compiler sich kümmern", dann meine ich schon den Compiler, NICHT die IDE.

    Also eigentlich make.exe.

    😕
    Nein, eigentlich cl.exe , g++.exe , clang.exe . Der Compiler halt.
    Make ist nicht der Compiler. Make ist Make.

    volkard schrieb:

    hustbaer schrieb:

    Und wie handhabt dein Makefile die Sache mit den include-Dependencies?

    Optimalerweise ganz normal mit .dep-files.

    Und wer generiert die .dep Files?



  • Ah, hustbaer, du wusstest also nicht dass man die Dependencies schon seit langem mit dem Compiler generieren kann? Dein "Problem" an der Sache war lediglich dass man 2 unterschiedliche Tools verwendet?

    Ich sehe da bei den Tools wenig Problem, warum nicht Compiler agnostische Tools verwenden? Aber klar, Compiler sollten sowas auch koennen.

    Es geht dabei aber gar nicht darum ob sie es koennen sondern darum wer die .dep Files verwaltet. Du bist ja schon wieder bei den .dep Files gelandet mit deinen 2 Ansaetzen - ob du das .dep File standalone hast oder in die .o Datei packst nimmt sich ja nicht viel. (Bis auf dass du die .o Dateien nicht so simpel deployen kannst wie die .dep Dateien)

    Das Problem ist aber das Verwalten. Leider skizzieren deine 2 Varianten ja keine relevanten Vorteile. Du hast naemlich die komplette Problematik einfach ignoriert: Wie mache ich ein make clean? Wann muessen die Dependencies neu generiert werden? Denn jedesmal neu generieren ist doof. Dann bin ich ja langsamer als herkoemmliches make.

    Der Compiler kennt das Konzept eines Projektes nicht. Er kennt auch keine Abhaengigkeiten zwischen Projekten. Der Compiler ist ein simples Tool um aus einer .cpp Datei eine .o Datei zu machen. make ist das simple Tool dass die Abhaengigkeiten zwischen Dateien und Projekten (bedenke: Abhaengigkeiten zwischen Dateien sind nur ein kleines Detail) etc, versteht und diese handled.

    Vielleicht hat es einen Grund das kein einziger Compiler dieses Verhalten dass du dir wuenscht implementiert 😉
    Denn es ist einfach viel praktischer dem Buildsystem diese Aufgabe zu geben.



  • Shade Of Mine schrieb:

    Ah, hustbaer, du wusstest also nicht dass man die Dependencies schon seit langem mit dem Compiler generieren kann? Dein "Problem" an der Sache war lediglich dass man 2 unterschiedliche Tools verwendet?

    Das war meine Hauptkritik, ja.
    War aber zugegebenermassen nur für gewaltige Intelligenzbestien aus meinen Beiträgen davor ersichtlich.

    Shade Of Mine schrieb:

    Ich sehe da bei den Tools wenig Problem, warum nicht Compiler agnostische Tools verwenden?

    Hab ich schon beschrieben. Haste vermutlich nicht mal gelesen.

    Shade Of Mine schrieb:

    Du hast naemlich die komplette Problematik einfach ignoriert: Wie mache ich ein make clean?

    Wie sonst auch? obj und bin Verzeichnis löschen?

    Shade Of Mine schrieb:

    Wann muessen die Dependencies neu generiert werden?

    Explizit nie.

    Shade Of Mine schrieb:

    Vielleicht hat es einen Grund das kein einziger Compiler dieses Verhalten dass du dir wuenscht implementiert 😉

    **Hallo?? MSVC????
    **Hab ich nur schon 3x oder so geschrieben.
    *kopf -> wand*


Log in to reply