Also, der Einstieg ist sehr simpel. Angenommen, du speicherst obiges
Programm unter foo.c ab und willst am Ende ein Programm names foo haben,
dann reicht schon dieses Makefile:
1
foo:foo.o
Make weiß von selbst, dass es foo.c in foo.o compilieren muss und wie
das geht.
Aber von hier an wird es schnell kompliziert. Der größte Fallstrick ist,
dass make immer das erste Target baut, das es im Makefile vorfindet.
Daher empfehle ich als erstes immer ein Target all (der Name ist aber
wurscht):
1
all:foo
2
3
foo:foo.o
Dann hat man für alle weiteren Targets darunter freie Hand.
Solltest du dann jetzt dein Programm in mehrere Dateien aufteilen,
foo1.c, foo2.c, bar.c..., dann schaut das so aus:
1
all:foo
2
3
foo:foo1.ofoo2.obar.o
Nächstes Problem ist, dass du ja auch Header-Dateien haben wirst, und du
alle C-Dateien neu kompilieren willst, wenn sich ein Header ändert. Das
kann man dann manuell verwalten und schaut so aus:
1
all:foo
2
3
foo:foo1.ofoo2.obar.o
4
5
foo1.o:foo1.hbar.h
6
foo2.o:foo2.hbar.h
7
bar.o:bar.h
Diese Zeilen bedeuten: Wenn sich eine der Dateien rechts vom :
verändert, dann muss die Datei links vom : neu gebaut werden.
Diese Abhängigkeiten können auch automatisch verwaltet werden. Aber das
ist ein Expertenthema, auf das ich jetzt nicht eingehen möchte.
Danke für deine ausführlichen Antworten.
Das hat schon mal gut funktioniert ( Anhang ).
So richtig klar, was "all" jetzt bedeutet, ist mir nicht.
Bedeutet "all" das nachher das exe-file den Namen von "all" bekommt?
Nein, am Namen "all" ist nichts besonderes, du könntest es auch
"frotzldiblubb" nennen. Wichtig ist nur, dass es die erste Regel im
Makefile ist. An der Regel selbst ist auch nichts besonderes, außer dass
sie die erste ist.
Man kann unterhalb einer Regel, eingerückt durch ein TAB (nicht
Leerzeichen) die Anweisung hinschreiben, die ausgeführt werden soll. Im
Fall von all: foo steht aber nichts dergleichen darunter, also wird auch
nichts weiter gemacht. Make versucht zwar "all" aus "foo" zu bauen, aber
da es keine Anweisungen gibt, passiert auch nichts.
Wie jede andere Regel auch, hat sie aber einen wichtigen Nebeneffekt:
Make schaut nämlich, bevor es versucht "all" zu bauen, ob nicht die
Dateien rechts vom Doppelpunkt, "foo", veraltet ist. Und wenn dem so
ist, versucht es vorher, diese Datei zu bauen. Das führt dann dazu,
dass Compiler und Linker für "foo" aufgerufen werden.
Christoph M. schrieb:> So richtig klar, was "all" jetzt bedeutet, ist mir nicht.> Bedeutet "all" das nachher das exe-file den Namen von "all" bekommt?
"all" ist in diesem Fall ein sogenanntes "phony target" (phony =
unecht). Da heißt, so wie es verwendet wird, wird keine Datei mit dem
Namen "all" gebaut. Es gibt dem Target "foo" nur einen weiteren Namen.
Manchmal kann es vorkommen, dass Make nicht alleine erkennt, dass ein
Target ein unechtes Ziel ist. Dann kann man den meisten Makes auch
deutlich sagen:
1
.PHONY: all
2
all: foo
3
4
foo: foo1.o foo2.o bar.o
Das .PHONY: in dem Beispiel ist ein in vielen Makes eingebautes "special
target". Sieht fast aus wie ein Target, steuert aber interne
Make-Abläufe.
Wenn du sehen willst was Make sich so "denkt" und welche eingebauten
Regeln es kennt, dann ruf dein Make mal mit der Option "-d" auf. Da wird
seitenweise Text kommen.
Hannes J. schrieb:> Da heißt, so wie es verwendet wird, wird keine Datei mit dem> Namen "all" gebaut. Es gibt dem Target "foo" nur einen weiteren Namen.
Falsch (oder zumindest nicht ganz richtig).
Daß Targets Dateien erzeugen ist gar nicht notwendig, deswegen muß nicht
jedes Target, daß keine Datei gleichen Namens erzeugt, gleich .PHONY
sein.
Das .PHONY schreibt man deswegen hin, um das zugehörige Recipe auch dann
auszuführen, wenn es eine aktuelle Datei gäbe, die wie das Target heißt.
Sonst würde ein zufällig rumliegendes "all" u.U. bewirken, daß dein
Makefile gar nicht "anspringt".
Markus F. schrieb:> Daß Targets Dateien erzeugen ist gar nicht notwendig, deswegen muß nicht> jedes Target, daß keine Datei gleichen Namens erzeugt, gleich .PHONY> sein.
Allerdings stehen Targets mit Files schon in einer sehr engen Beziehung:
make prüft vor dem Ausführen jeder Regel, ob es eine Datei mit dem Namen
des Targets gibt und ob die neuer ist als alle Abhängigkeiten. Nur wenn
das gilt, wird die Regel auch ausgeführt.
> Das .PHONY schreibt man deswegen hin, um das zugehörige Recipe auch dann> auszuführen, wenn es eine aktuelle Datei gäbe, die wie das Target heißt.>> Sonst würde ein zufällig rumliegendes "all" u.U. bewirken, daß dein> Makefile gar nicht "anspringt".
Eben. Deshalb ist das .PHONY schon sinnvoll. Vor allem wird all nicht
das einzige phony-Target bleiben. In einem komplexen Makefile kann es
sehr viele davon geben.
Christoph M. schrieb:> Interessanterweise habe ich mich beim Makefile oben vertippt.>> In meinem Makefile steht:> hallo: hallo.o> Das beste ist, es funktioniert einwandfrei. Die Datei hallo.c wird zur> ausführbaren Datei "hallo" compiliert.
Wenn du "make hallo" eingibst, geht es sogar ganz ohne Makefile.
Markus F. schrieb im Beitrag #5738660:
> Ich empfehle ein Duden-Studium.
Dann gib doch mal "daß" auf http://www.duden.de ein und schau mal, ob es
ein solches Wort dort gibt.
Mittlerweile habe ich ein extrem ausgefeiltes Softwarepaket erstellt (
die Dateien im Anhang ) ;-)
Jetzt stellt sich natürlich die Frage, wie ich das Makefile dazu bringe,
dass es alle Dateien kompiliert und mit "make clean" auch wieder alle
löscht.
Es erscheint mir umständlich, wenn ich für das löschen alle Namen von
Hand wieder eintippen muss.
Wer weiß Rat?
wenn Du das, was Du schon über Makefiles gelernt hast, richtig
anwendest, könntest Du da selbst draufkommen.
Alles was Du braucht ist ein Target (ja, es darf ruhig .PHONY sein) und
ein Recipe - das sind das/die Kommandos, die make ausführen soll, um das
Target zu bauen.
target: abhängigkeit
<TAB> Kommandos
Damit man nicht immer wieder dieselben Listen von Abhängigkeiten angeben
muß, kann make auch mit Variablen umgehen:
DATEILISTE=dateia dateib dateic
Wenn Du also eine Liste
OBJS=main.o eins.o zwei.o
hast, kannst Du die Liste überall dort verwenden, wo Du sonst einzelne
Namen verwenden würdest:
<TAB>rm $(OBJS)
Markus F. schrieb:> Wenn Du also eine Liste>> OBJS=main.o eins.o zwei.o
Ich würde
OBJS:=main.o eins.o zwei.o
vorschlagen. Spielt zwar an dieser Stelle keine große Rolle, aber man
sollte sich des Unterschieds bewusst sein.
Markus F. schrieb im Beitrag #5738718:
> Rolf M. schrieb:>> Markus F. schrieb:>>> Ich empfehle ein Duden-Studium.>>>> Dann gib doch mal "daß" auf http://www.duden.de ein und schau mal, ob es>> ein solches Wort dort gibt.>> Mein Duden ist schon - genau wie ich - ein wenig älter.
Also nicht mehr aktuell (dein Duden zumindest - Du vielleicht schon noch
;-)
Übrigens: Die zweite Verwendung im bemängelten Posting ist auch nach
alter Rechtschreibung falsch.
Autor: Markus F. (mfro)
>Alles was Du braucht ist ein Target (ja, es darf ruhig .PHONY sein) und>ein Recipe - das sind das/die Kommandos, die make ausführen soll, um das>Target zu bauen.
Meiner Meinung nach ist es beim lernen extrem hilfreich, wenn man die
englischen Begriffe durch deutsche ersetzt ( auch wenn ich das
verhandlungssicher spreche ).
Warum? Weil man dann den Befehlssyntax ( englische Befehle ), von den
erfundenen deutschen Wörtern besser trennen kann.
Hier mein neues Makefile:
Rolf M. schrieb:> Ich würde>> OBJS:=main.o eins.o zwei.o>> vorschlagen. Spielt zwar an dieser Stelle keine große Rolle, aber man> sollte sich des Unterschieds bewusst sein.
Wenn man gnu make benutzt.
Das Makefile ist weniger ideal. Du verwendest die Targets hier nicht in
dem Sinne, wie es eigentlich gedacht ist. Es gibt bestimmte Targets wie
all und clean, die PHONY sein sollen, aber du hast quasi ausschließlich
PHONY-Targets, was am Sinn von make vorbei geht. Zunächst einmal sollte
dein zu erstellenedes Executable, also halloExe ein Target sein, und
deine .o-Files sollten auch alle Targets sein. Das hast du mit den
Targets machMalDrucker und machMalHallo quasi umgangen. Warum?
Also eher so:
1
halloExe: drucker.o hallo.o
2
g++ drucker.o hallo.o -o halloExe
3
4
drucker.o: drucker.c
5
gcc -c drucker.c
6
7
hallo.o: hallo.c
8
gcc -c hallo.c
9
10
.PHONY: clean
11
clean:
12
rm -rf *.o halloExe
Dein "alles" kannst du davor noch hinzufügen:
1
.PHONY: alles
2
alles: halloExe
Das kann man dann nachher noch mit Variablen garnieren und mit
generischen Regeln:
1
ZIEL=halloExe
2
OBJEKTE=drucker.o hallo.o
3
4
$(ZIEL): $(OBJEKTE)
5
g++ $^ -o $@
6
7
%.o: %.c
8
gcc -c $< -o $@
9
10
.PHONY: clean
11
clean:
12
rm -rf $(OBJEKTE) $(ZIEL)
$@ wird durch den Namen des Ziels ersetzt, $< durch den Namen der ersten
Abhängigkeit, $^ durch die Namen aller Abhängigkeiten. %.o : %.c sagt,
dass für jedes Target, das auf .o endet, eine gleichnamige .c-Datei
gesucht und als Abhängigkeit verwendet werden soll.
Das gilt alles für GNU make. Wie weit das auf andere makes übertragbar
ist, weiß ich nicht.
Rolf M. schrieb:> Das gilt alles für GNU make. Wie weit das auf andere makes übertragbar> ist, weiß ich nicht.Rolf M. schrieb:> %.o: %.c
anderswo muß man sich evt. mit Suffix-Rules behelfen:
.c.o:
RolfM:
>Das hast du mit den>Targets machMalDrucker und machMalHallo quasi umgangen. Warum?
Ähm, ich weiß nicht.
Ich habe es so verstanden, dass hier
1
hallo.o:hallo.c
2
gcc-challo.c
hallo.o ein Name ist und sinnvollerweise durch einen deutschen Namen
ersetzt wird, damit man sieht, dass es sich hier nicht um das o-file
handelt.
Im nächsten Schritt befindet sich das Source-File im
examples-Verzeichnis.
Jetzt allerdings leider mit dem Ergebnis, dass der Compiler die includes
nicht mehr findet.
Wie teilt man die richtigen Pfade dem Makefile mit?
Christoph M. schrieb:> RolfM:>>Das hast du mit den>>Targets machMalDrucker und machMalHallo quasi umgangen. Warum?>> Ähm, ich weiß nicht.> Ich habe es so verstanden, dass hier> hallo.o: hallo.c> gcc -c hallo.c>> hallo.o ein Name ist und sinnvollerweise durch einen deutschen Namen> ersetzt wird, damit man sieht, dass es sich hier nicht um das o-file> handelt.
Aber genau um das sollte es sich handeln. Die Grundidee von make ist,
dass nur die Sachen neu gebaut werden, die sich seit dem letzten mal
geändert haben. Dazu sucht er nach einer Datei, deren Namen dem Target
entspricht, und wenn eine solche nicht existiert oder älter ist als
mindestens eine der Abhängigkeiten, wird das Kommando zu dieser Regel
ausgeführt. Und das wird für jede der Abhängigkeiten wiederum rekursiv
weitergeführt.
Das heißt, es sollte so sein: Wenn du make aufrufst, wird dein Programm
erstellt. Machst du das nochmal, ohne vorher was zu ändern, erkennt
make, dass alles gleich geblieben ist, und es wird nichts mehr gemacht.
Änderst du jetzt z.B. hallo.c, wird make danach nur hallo.o neu erzeugen
und daraus das Executable, aber drucker.o wird nicht neu gebaut, da sich
dort nichts geändert hat.
Wenn nun dein Target nicht dem Dateinamen entspricht, funktioniert
dieser Mechanismus nicht mehr.
Rolf M. (rmagnus)
>Wenn nun dein Target nicht dem Dateinamen entspricht, funktioniert>dieser Mechanismus nicht mehr.
Ah, danke. Jetzt habe ich's kapiert.
1
halloExe:drucker.ohallo.o
ist besser, weil hallo.o erzeugt wird.
im anderen Fall
1
alles:machMalDruckermachMalHallo
nützt das nichts, weil keine machMalDrucker Datei produziert wird.
Das Makefile funktioniert erst mal, aber es wird jedes mal alles gebaut
und es tritt kein Einspareffekt auf.
Jetzt die Frage zum "SchoenWetterProjekt": Wie sollte das Makefile
aussehen, damit die Include-Datei gefunden wird?
Christoph M. schrieb:> Jetzt die Frage zum "SchoenWetterProjekt": Wie sollte das Makefile> aussehen, damit die Include-Datei gefunden wird?
Es gibt eine Option die man dem gcc mitgeben kann dann erzeugt er für
jede kompilierte c-Datei ein Makefile-schnipsel das die Abhängigkeiten
von den Headern ausdrückt, das kann man in sein Makefile inkludieren.
Meine Makefiles enden alle so:
1
#####################
2
## Advanced Voodoo ##
3
#####################
4
5
# try to include any compiler generated dependency makefile snippet *.d
6
# that might exist in BUILDDIR (but don't complain if it doesn't yet).
# make the object files also depend on the makefile itself
11
$(OBJS): Makefile
dafür muß der compiler mit dem -MMD Schalter aufgerufen werden, dann
entstehen diese *.d Dateien. Spätestens beim nächsten make Aufruf werden
die dann ebenfalls berücksichtigt.
Das obige ist speziell auf mein Makefile zugeschnitten, ich habe eine
Variable BUILDDIR die den Ordner bezeichnet in dem alle binaries gebaut
werden (out of tree build), ich häng mal das ganze Makefile an.
> Also nur ein main file.
Für nur ein File bringst nichts. Es ist für viele Files und den Fall,
dass man nur eines ändert aber nicht alle schon kompilierten neu
kompilieren will.
Das kann bei großen Projekten schon mal mehrere Minuten gehen.
> Ich will mir nach und nach die Grundlagen erarbeiten.
Es gibt gerade auch zu make eines sehr gutes Handbuch von dem
gnu-Leuten.
Das kann man kostenlos runterladen und kostenlos lesen.
Wenigstens einmal im Leben sollte man das gemacht haben...
https://www.gnu.org/software/make/manual/make.html
Olaf
Christoph M. schrieb:>> Also nur ein main file.>> Für nur ein File bringst nichts. Es ist für viele Files und den Fall,> dass man nur eines ändert aber nicht alle schon kompilierten neu> kompilieren will.> Das kann bei großen Projekten schon mal mehrere Minuten gehen.
Die sind dann eher noch klein. Bei wirklich großen Projekten kann das
auch mal nen halben Tag dauern.
Wegen Inkompatibilitäten zwische BSD- und GNU-Make bin ich zu mk[1]
gewechselt. Das ist zwar auf keinem(?) BS standartmäßig installiert,
aber gut dokumentiert und verwendet keine kryptischen Variablen, wie $<
oder $@.
Bei Ubuntu heißt das Paket "9base".
[1] https://9fans.github.io/plan9port/man/man1/mk.html
W. schrieb:> keine kryptischen Variablen, wie $<> oder $@.
Wie heißen die dann? Man kann ja schlecht drauf verzichten, also welche
weniger kryptischen Zeichen oder Namen werden stattdessen verwendet?
sleep, sort, strings, tee, test, touch, tr, uniq, and yacc.
Was passiert wenn ich das installiere? Zerschießt mir das mein System
und jedes der oben genannten Standard-Tools hat von dann an eine subtil
andere Funktion und jedes zweite Shellscript fällt auf die Schnauze?
Wenn das nämlich nur im Kombipack mit so ner Streubombe erhältlich ist
verbietet sich der praktische Einsatz wohl leider.
@prof7bit,
$@ heisst $target,
$< heißt $prereq.
9base installiert die Programme in /usr/lib/plan9/bin/.
Wenn Dein $PATH "richtig" eingestellt ist, hat das keine Seiteneffekte.