Frage zu makefiles



  • 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*



  • hustbaer schrieb:

    Shade Of Mine schrieb:

    Wann muessen die Dependencies neu generiert werden?

    Explizit nie.

    So funktioniert das aber einfach nicht.
    Aber gut, du hast ja gesagt dass du von Buildsystemen keine Ahnung hast.

    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*

    MSVC ist eine IDE und kein Compiler.
    Eine IDE mit Buildsystem.

    Bezeichnend wie du so schnell Beleidigend wirst...



  • Shade Of Mine schrieb:

    So funktioniert das aber einfach nicht.
    Aber gut, du hast ja gesagt dass du von Buildsystemen keine Ahnung hast.

    Naja, so explizit habe ich das glaube ich nicht gesagt. Bin aber sicher nicht der Experte, das stimmt schon. Andrerseits ist die Sache ausreichend einfach so dass man wohl kein Experte sein muss um sich überlegen zu können was möglich wäre.

    Was das "so funktioniert das aber einfach nicht" angeht...
    Ich glaube dir schon dass es aktuell so nicht funktioniert. Weil die .d Files eben keine Informationen über Switches etc. beinhalten. In meinem Beispiel würden sie das aber, bzw. würden die nötigen Informationen in den .o Files stehen. Und dann sehe ich keine Notwendigkeit die Dependencies überhaupt irgendwann neu zu generieren.

    MSVC ist eine IDE und kein Compiler.
    Eine IDE mit Buildsystem.

    Ich meinte mit MSVC ja auch nur den Compiler.
    Sorry, es war für mich nicht klar dass du davon ich würde die ganze IDE meinen, wenn ich doch mehrfach schreibe dass ich nur den Compiler meine. BTW: Wenn "MSVC" für dich die IDE mit einschliesst, was schreibst du dann wenn du nur den Compiler meinst?

    Davon abgesehen hab ich diesen Punkt aber nochmal etwas nachrecherchiert, und ... naja, da hatte ich wohl ebenso falsche Informationen. Soll heissen: ich war der ehrlichen Meinung dass der Visual C++ Compiler (ohne IDE!) selbst entscheidet was neu gebaut werden muss und was nicht. Hab das in einigen Beiträgen (Stackoverflow, ...) so gelesen und einfach mal geglaubt. Ich hab' jetzt allerdings andere Beiträge gefunden die darauf hindeuten dass doch MSBuild sich darum kümmert. (Ich habe auch versucht eine "offizielle" Beschreibung zu finden wie C++ Builds mit MSBuild funktionieren, inklusive Beschreibung wie die Sache mit den include-Dependencies gelöst wird. Konnte aber nix finden.)



  • hustbaer schrieb:

    In meinem Beispiel würden sie das aber, bzw. würden die nötigen Informationen in den .o Files stehen. Und dann sehe ich keine Notwendigkeit die Dependencies überhaupt irgendwann neu zu generieren.

    Machen wir doch mal ein kleines Beispiel:

    a.c:

    #include "b.h"
    

    b.h:

    #include "c.h"
    

    Welche Information würdest Du in der a.o hinterlegen? Was passiert, wenn die b.h geändert wird zu

    #include "d.h"
    

    ?



  • Welche Information würdest Du in der a.o hinterlegen?

    `

    built with path/to/a.c {datum}

    built with path/to/b.h {datum}

    built with other_path/c.h {datum}

    built with switches {XYZ}

    built with compiler version {XYZ}

    ...

    `

    Was passiert, wenn die b.h geändert wird zu

    Das changed-date von b.h ändert sich, und der Test gegen
    built with path/to/b.h {datum}
    ergibt dass neu gebaut werden muss?

    Das Beispiel funktioniert aber auch noch wunderschön mit klassischen, externen .d Files 😉



  • hustbaer schrieb:

    Und dann sehe ich keine Notwendigkeit die Dependencies überhaupt irgendwann neu zu generieren.

    Du brauchst immer ein Clean. Immer. Egal was.
    Und da wird es doof wenn der Compiler dein Make-Clone ist. Denn du brauchst ein make clean, ein make all, ein make target, etc.

    Sobald du Metainformationen generierst, musst du diese auch handeln.

    Wenn wir jetzt davon ausgehen dass wir nie auf diese Metadaten zugreifen muessen und das System mit den Compilern perfekt funktioniert: welchen Vorteil haettest du?

    Du brauchst immer noch ein Buildsystem. Abhaengigkeiten gibt es viel mehr als nur die Includes. Mehrere Buildschritte, unterschiedliche Targets, etc. Du wuerdest dir hier deutlich mehr Komplexitaet einkaufen, weil Compiler ploetzlich "mitdenken" und das beachtet werden muss.

    Das tolle an dem Unix-Ansatz ist ja, dass jede Anwendung nur fuer einen kleinen Teil zustaendig ist und das man sich komplexe Loesungen baut indem man viele kleine Anwendungen aneinander stoppelt.

    Also ja, wenn du unbedingt willst dass der Compiler die Dependencies aufloesen soll, dann soll er das machen. Aber er soll bloss nicht versuchen ein mini-make zu sein.


Anmelden zum Antworten