Makefile optimieren



  • Hallo zusammen,

    mein Projekt wird derzeit immer größer und ich habe (so wie denke) ein sehr schlechtes Makefile. Jedesmal wenn ich kompiliere, dann kompiliert er mir den ganzen Code und nicht nur die Klasse an der ich gerade arbeite.

    Mein Projekt sieht derzeit circa so aus: https://media-cdn.ubuntu-de.org/forum/attachments/54/27/7656878-Project.pdf

    Mein Makefile sieht wie folgt aus (bestimmt chaotisch für euch)

    WORK_DIR=$(PWD)
    
    INC_DIR=-I$(WORK_DIR)/chemistry \
    		-I$(WORK_DIR)/thermo \
    		-I$(WORK_DIR)/transport \
    		-I$(WORK_DIR)/properties \
    		-I$(WORK_DIR)/mixtureFraction \
    		-I$(WORK_DIR)/constants \
    		-I$(WORK_DIR)/typedef \
    		-I$(WORK_DIR)/stringManipulator
    
    SOURCE_FILES=automaticFlameletCreator.cpp \
    			 chemistry/chemistry.cpp \
    			 chemistry/chemistryReader.cpp \
    			 chemistry/chemistryData.cpp \
    			 chemistry/chemistryCalc.cpp \
    			 thermo/thermo.cpp \
    			 thermo/thermoReader.cpp \
    			 thermo/thermoData.cpp \
    			 thermo/thermoCalc.cpp \
    			 transport/transport.cpp \
    			 transport/transportReader.cpp \
    			 transport/transportData.cpp \
    			 properties/properties.cpp \
    			 properties/propertiesReader.cpp \
    			 mixtureFraction/mixtureFraction.cpp \
    			 typedef/typedef.cpp\
    			 stringManipulator/stringManipulator.cpp
    
    CPPFLAGS=-g -Wall -Wextra -pedantic-errors -std=c++11 -Wno-unused-parameter -Wno-unused-variable
    
    automaticFlameltCreator : automaticFlameletCreator.cpp
    	g++ $(CPPFLAGS) $(INC_DIR) $(SOURCE_FILES) -o automaticFlameletCreator
    

    Ich dachte mir das es ggf. sinnvoll wäre die Klassen "Thermo", "Transport", "Chemistry" als Libraries zu erzeugen und die dann mit dem Hauptprogramm zu linken. Sollte doch möglich sein. Somit würde er ja dann auch nur das Re-Kompilieren, an dem ich was geändert habe oder?

    Über den Thread für Neulinge habe ich bereits folgendes gefunden:

    https://www.c-plusplus.net/forum/88418

    https://www.c-plusplus.net/forum/41477

    Allerdings bin ich mit dem makefile nicht sonderlich bewandert. Bezüglich dem Aufbau des Projekts lässt sich sicherlich streiten aber da es mein erstes großes ist, denke ich das es ganz "okay" ist. Wäre für ne Hilfestellung bezüglich dem Makefile sehr erfreut.

    Grüße Tobi



  • Statt alle Source Dateien einfach g++ vorzuwerfen solltest du jede der *.cpp Dateien separat zu Object Dateien compilieren und die dann nur noch zusammen linken am Ende. Dazu brauchst du erstmal eine Liste der Objekt Dateien. Die kriegst du beispielsweise so (ersetzt die Dateiendung .cpp durch .o und setzt ein 'obj/' an den Anfang damit die Objekt Dateien nicht zwischen den ganzen .cpp Dateien liegen):

    OBJ := $(addprefix obj/,$(addsuffix .o,$(basename $(SOURCE_FILES))))
    

    Dann brauchst du eine Regel, welche die Objekt Dateien erstellt:

    obj/%.o: %.cpp | obj
    	g++ $(CPPFLAGS) $(INC_DIR) -c $< -o $@
    

    Das obj Ziel sollte den Ordner obj und entsprechende Unterordner erstellen. Dabei steht $< für die Quelldatei und $@ für die Zieldatei.

    Am Ende dann noch alles linken:

    automaticFlameltCreator: $(OBJ)
        g++ $(CPPFLAGS) $(INC_DIR) -o $@ $^
    

    Ich hoffe das war verständlich. Die Compilezeiten sollten dadurch deutlich besser werden, da du nur noch die *.cpp Dateien neu compilierst die sich geändert haben.



  • Hey Sebi,

    danke für die Rückmeldung. Ich wiederhole mal das von dir, um sicher zu gehen das ich es verstanden habe:

    OBJ := $(addprefix obj/,$(addsuffix .o,$(basename $(SOURCE_FILES))))
    

    Die Objekte werden in den Ordner "obj/" gelegt (zwecks der Übersichtlichkeit wie du schon erwähnt hast). Prefix wird vorangestellt, suffix wird nachgestellt. Das geschieht mit allen Dateien die ich unter $(SOURCE_FILES) gesetzt habe. Basename ist dann der Dateiname. Somit wird folgendes festgelegt:

    obj/nameDerDatei.o

    SOURCE_FILES=automaticFlameletCreator.cpp \
                 chemistry/chemistry.cpp
    

    wird dann zu

    obj/automaticFlameletCreator.o
    obj/chemistry.o
    

    Die anderen Befehle sind einleuchtend und selbsterklärend. Demnach sieht mein makefile nun folgendermaßen aus:

    WORK_DIR=$(PWD)
    
    INC_DIR=-I$(WORK_DIR)/chemistry \
            -I$(WORK_DIR)/thermo \
            -I$(WORK_DIR)/transport \
            -I$(WORK_DIR)/properties \
            -I$(WORK_DIR)/mixtureFraction \
            -I$(WORK_DIR)/constants \
            -I$(WORK_DIR)/typedef \
            -I$(WORK_DIR)/stringManipulator
    
    SOURCE_FILES=automaticFlameletCreator.cpp \
                 chemistry/chemistry.cpp \
                 chemistry/chemistryReader.cpp \
                 chemistry/chemistryData.cpp \
                 chemistry/chemistryCalc.cpp \
                 thermo/thermo.cpp \
                 thermo/thermoReader.cpp \
                 thermo/thermoData.cpp \
                 thermo/thermoCalc.cpp \
                 transport/transport.cpp \
                 transport/transportReader.cpp \
                 transport/transportData.cpp \
                 properties/properties.cpp \
                 properties/propertiesReader.cpp \
                 mixtureFraction/mixtureFraction.cpp \
                 typedef/typedef.cpp\
                 stringManipulator/stringManipulator.cpp
    
    CPPFLAGS=-g -Wall -Wextra -pedantic-errors -std=c++11 -Wno-unused-parameter -Wno-unused-variable
    
    OBJ := $(addprefix obj/,$(addsuffix .o,$(basename $(SOURCE_FILES))))
    
    obj/%.o: %.cpp | obj
            g++ $(CPPFLAGS) $(INC_DIR) -c $< -o $@
    
    automaticFlameltCreator : $(OBJ)
        g++ $(CPPFLAGS) $(INC_DIR) -o $@ $^
    

    Wobei SOURCE_FILES und INC_DIR sicherlich auch mit einer Art regulärer Ausdrücke verkürzt werden könnte. Beim Kompilieren erhalte ich allerdings noch den Fehler, dass diverse Dateien nicht erstellt werden konnten:

    g++ -g -Wall -Wextra -pedantic-errors -std=c++11 -Wno-unused-parameter -Wno-unused-variable -I/home/shorty/OpenFOAM/development/flameletcreator/src/chemistry -I/home/shorty/OpenFOAM/development/flameletcreator/src/thermo -I/home/shorty/OpenFOAM/development/flameletcreator/src/transport -I/home/shorty/OpenFOAM/development/flameletcreator/src/properties -I/home/shorty/OpenFOAM/development/flameletcreator/src/mixtureFraction -I/home/shorty/OpenFOAM/development/flameletcreator/src/constants -I/home/shorty/OpenFOAM/development/flameletcreator/src/typedef -I/home/shorty/OpenFOAM/development/flameletcreator/src/stringManipulator -c chemistry/chemistry.cpp -o obj/chemistry/chemistry.o
    Assembler messages:
    Fatal error: can't create obj/chemistry/chemistry.o: No such file or directory
    make: *** [obj/chemistry/chemistry.o] Error 1
    

    Problem ist, dass er mir den Unterordner nicht erstellt. Nachdem ich die alle selber erstellt habe, gehts einwandfrei. Die Zeiten sind extrem besser. Danke (:

    Diesbezüglich müsste ich hier noch was abändern oder:

    OBJ := $(addprefix obj/ >>> irgendwie der Ordnername <<< ,$(addsuffix .o,$(basename $(SOURCE_FILES))))
    

    Dank dir ❤
    Tobi



  • Dieser Thread wurde von Moderator/in SeppJ aus dem Forum C++ (alle ISO-Standards) in das Forum Compiler- und IDE-Forum verschoben.

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

    Dieses Posting wurde automatisch erzeugt.



  • Ich empfehle Dir, auf ein besseres Build-System umzusteigen. Es ist echt schwer, ein korrektes Makefile von Hand zu schreiben. Du willst ja auch die Sourcen compilieren, wenn Du einen Header änderst. Und nur die Sourcen, die davon abhängen. Und zwar direkt oder auch indirekt. Die Abhängigkeiten von Hand zu pflegen ist nicht wirklich sinnvoll.

    Schau Dir beispielsweise cmake an. Ich persönlich verwende autoconf/automake (autotools). Eine einfache Einführung findest Du unter https://autotools.io/index.html.

    Für Dein Projekt musst Du für autotools 2 Dateien anlegen.
    configure.ac:

    AC_INIT([automaticFlameltCreator], [0.1], [your@e-mail.nowhere])
    AM_INIT_AUTOMAKE([foreign])
    AC_PROG_CXX
    AX_CXX_COMPILE_STDCXX_11(noext)
    AC_OUTPUT([Makefile])
    

    Makefile.am:

    bin_PROGRAMS = automaticFlameltCreator
    automaticFlameltCreator_SOURCES = \
    	automaticFlameltCreator.cpp
    	chemistry/chemistry.cpp
    AM_CPPFLAGS = -I$(top_srcdir)/chemistry
    

    Zum initialisieren des Bulid systems dann folgendes:

    autoreconf -i
    ./configure
    

    Du bekommst ein Makefile. Wenn Du Sourcen in Makefile.am einfügst, wird das Makefile automatisch neu generiert. Also dann immer nur make.

    Ach ja, die lange Liste an includes directories sieht nicht gut aus. Ich würde eher in den sourcen so was wie:

    #include <chemistry/chemistry.h>
    

    schreiben. Dann brauchst Du die -I flags nicht.



  • Shor-ty schrieb:

    SOURCE_FILES=automaticFlameletCreator.cpp \
                 chemistry/chemistry.cpp
    

    wird dann zu

    obj/automaticFlameletCreator.o
    obj/chemistry.o
    

    Fast. Die Ordnerstruktur wird bei behalten also die zweite Datei heißt dann obj/chemistry/chemistry.o

    Zu dem Ordner Problem: Du könntest die Ordner entsprechend erstellen bevor du den g++ Aufruf machst. Wenn du davor ein mkdir -p $(@D) schreibst wird der Ordner erstellt. Dabei steht $(@D) für den Ordner indem die Target Datei liegt (eine Übersicht zu den ganzen automatischen Variablen gibts übrigens hier)

    Dein Makefile hat jetzt allerdings noch einen großen Nachteil den tntnet schon angesprochen hat. Und zwar die Abhängigkeiten der Headerdateien. Momentan werden die noch gar nicht mit berücksichtig und wenn du einen Header Datei änderst wird dir make sagen, dass es nichts zu tun gibt. Die vollständige Lösung alle Abhängigkeiten zwischen den *.cpp und Headerdateien zu erkennen ist recht kompliziert. Für kleine Projekte nutze ich aber gerne eine etwas schlechte Version bei der jede *.cpp Datei von allen Headern abhängt. Wenn man dann einen Header ändert wird alles neu compiliert.



  • Hallo ihr zwei,

    vielen Dank für eure Hilfe und Erklärung zu dem makefile und der Alternative. Ich werde mich diesbezüglich morgen hinsetzen. Wurde aber heute schon sehr zum Affen gemacht, da ich das Projekt an OpenFOAM anlehne und die folgende Formatierung gewählt habe: http://www.openfoam.org/contrib/code-style.php

    Somit war die Kritik von einem Kollegen nicht gerade erfreulich, da ich 80 Zeichen pro Zeile einhalte und das 80ger Jahre Programmierung ist. Die Funktionen die ich definiere:

    scalar myFunction
    (
        const scalar&,
        const scalar&,
        const Object&,
        const whatever
    ) const;
    

    (Analogie zu OpenFOAM), sind furchtbar zu lesen und und und. Entsprechend bin ich etwas geknickt, da ich da schon sehr lange dran arbeite. Ich weiß das ich kein super Programmierer bin aber wenn man nie anfängt, dann versteht man nie was. Naja egal, die Kritik hätte man auch anders formulieren können. Bestimmt ist das Projekt eh murx.

    Zuletzt ... wisst ihr ein Programm das sein eigenes Programm analysiert bzw. irgendwie mitteilt was es alles macht, welche Loops Zeitintensiv sind etc. um ggf. Optimierungen durchzufürhen?



  • tntnet schrieb:

    Ich empfehle Dir, auf ein besseres Build-System umzusteigen.

    Ich empfehle das nicht.

    Lange Zeit bei make bleiben. Irgendwann, wenn make nicht mehr reicht, ganz große Haufen machen und was selber schreiben. Aber nicht diese Zwischendinger, die haben nicht spürbar mehr Puste als make, können auch kein auto-linking und kein zyklischen Projekt-Abhängigkeiten. Nur die Anfängerbeispiele mit einer Hand voll Dateien sehen ganz toll einfach aus. Will man die einpflegen, weshalb man make-Probleme aufziehen sah, wirds um so komplizierter.

    Ordentlicher Code fühlt sich mit make sauwohl. Wenn make nicht zu reichen scheint, würde ich mir zuerst Gedanken drüber machen, ob man wirklich aus der selben *.cpp eine *.dll, eine *.lib UND eine *.exe erzeugen will. Oder ob man wirklich ALLE *.obj in eine einzige riesengroße *.dll ballern will; ganz sicher kommt später der Wunsch, manche wieder davon auszuschließen, der Alle-Hack wird nochmal gehackt.

    tntnet schrieb:

    Es ist echt schwer, ein korrektes Makefile von Hand zu schreiben. Du willst ja auch die Sourcen compilieren, wenn Du einen Header änderst. Und nur die Sourcen, die davon abhängen.

    Das wäre leicht. Siehe Links im Eröffnungsposting.

    automake und Konsorten empfinde ich als eine Pest. Alles dauert länger, klappt auf fremden Rechner nicht und die "platformunabhängigen" automakefiles haben die Tendenz, ins Unermessliche zu wachsen. Dann sitze ich als armer Wurm vor einem ein wenig ungeöhnlich eingereichtetem Rechner, das generierte makefile läuft nicht durch, und der generierte Code ist zu durcheinander, um ernstlich ins Auge zu fassen, ihn zu reparieren. Die inzwischen 120k großen Steuerdateien des "besseren" maketools ebenso. 🤡


Anmelden zum Antworten