Forum: Compiler & IDEs Makefile Grundlagen


von Christoph M. (mchris)


Lesenswert?

Kennt sich hier jemand mit make aus?

Ich will mir nach und nach die Grundlagen erarbeiten.

Der erste Schritt: ein einzelnes File compilieren.
1
#include <stdio.h>
2
int main(int argc, char **argv) 
3
{
4
  printf("hallo");
5
  return 0;
6
}

Wie geht man vor?

von tictactoe (Gast)


Lesenswert?

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.

von tictactoe (Gast)


Lesenswert?

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.o foo2.o bar.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.o foo2.o bar.o
4
5
foo1.o: foo1.h bar.h
6
foo2.o: foo2.h bar.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.

von Christoph M. (mchris)


Angehängte Dateien:

Lesenswert?

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?

von tictactoe (Gast)


Lesenswert?

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.

von Hannes J. (Firma: _⌨_) (pnuebergang)


Lesenswert?

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.

von Markus F. (mfro)


Lesenswert?

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".

von Rolf M. (rmagnus)


Lesenswert?

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.

Beitrag #5738487 wurde von einem Moderator gelöscht.
Beitrag #5738660 wurde von einem Moderator gelöscht.
von Christoph M. (mchris)


Lesenswert?

Interessanterweise habe ich mich beim Makefile oben vertippt.

In meinem Makefile steht:
1
hallo: hallo.o

Das beste ist, es funktioniert einwandfrei. Die Datei hallo.c wird zur 
ausführbaren Datei "hallo" compiliert.

von Rolf M. (rmagnus)


Lesenswert?

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.

von Christoph M. (mchris)


Lesenswert?

Ich habe gerade einen neuen Versuch gemacht. Mein Makefile soll jetzt 
mit
1
make clean

die erzeugten Files wieder löschen.

So sieht es im Moment aus:
1
hallo: hallo.o
2
3
clean:
4
  rm hallo.o
5
  rm hallo

: Bearbeitet durch User
Beitrag #5738718 wurde von einem Moderator gelöscht.
von Christoph M. (mchris)


Angehängte Dateien:

Lesenswert?

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?

von Markus F. (mfro)


Lesenswert?

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)

von Rolf M. (rmagnus)


Lesenswert?

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.

Beitrag #5738761 wurde von einem Moderator gelöscht.
von Christoph M. (mchris)


Lesenswert?

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:
1
alles: machMalDrucker machMalHallo
2
  g++ drucker.o hallo.o -o halloExe
3
4
machMalDrucker: drucker.c
5
  gcc -c drucker.c
6
7
machMalHallo: hallo.c
8
  gcc -c hallo.c
9
10
clean:
11
  rm -rf *.o halloExe

inspiriert durch
https://www.youtube.com/watch?v=aw9wHbFTnAQ

von Markus F. (mfro)


Lesenswert?

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.

von Rolf M. (rmagnus)


Lesenswert?

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.

: Bearbeitet durch User
von Markus F. (mfro)


Lesenswert?

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:

von Christoph M. (mchris)


Angehängte Dateien:

Lesenswert?

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 -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.

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?

von Rolf M. (rmagnus)


Lesenswert?

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.

: Bearbeitet durch User
von Christoph M. (mchris)


Lesenswert?

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.o hallo.o
ist besser, weil hallo.o erzeugt wird.

im anderen Fall
1
alles: machMalDrucker machMalHallo
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?

von Bernd K. (prof7bit)


Angehängte Dateien:

Lesenswert?

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).
7
DEPS = $(addprefix $(BUILDDIR),$(patsubst %.c,%.d,$(filter %.c,$(SRCS))))
8
-include $(DEPS)
9
10
# 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.

: Bearbeitet durch User
von Vincent H. (vinci)


Lesenswert?

Ich verwende eine Variante des makefiles von folgendem Link für meine 
Projekte:
https://spin.atomicobject.com/2016/08/26/makefile-c-projects/

Da braucht man sich seine targets eigentlich nur noch drumherum basteln.

von Esmu P. (Firma: privat) (max707)


Lesenswert?

Und ich hätte gerne mal gewußt, wozu die make files überhaupt benötigt 
werden, wenn es auch ohne geht beim compilieren. Also nur ein main file.

von Christoph M. (mchris)


Lesenswert?

> 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.

von Olaf (Gast)


Lesenswert?

> 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

von Rolf M. (rmagnus)


Lesenswert?

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.

von W. (Gast)


Lesenswert?

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

von Bernd K. (prof7bit)


Lesenswert?

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?

von Bernd K. (prof7bit)


Lesenswert?

W. schrieb:
> Bei Ubuntu heißt das Paket "9base".
1
9base is a port of following original Plan 9 userland tools to Unix:
2
 awk, basename, bc, cat, cleanname, date, dc, echo, grep, mk, rc, sed, seq,
3
 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.

: Bearbeitet durch User
von J. W. (dilbert)


Lesenswert?

@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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.