Aktuelle Zeit: 27.04.2024, 05:58

Alle Zeiten sind UTC + 1 Stunde




Ein neues Thema erstellen Auf das Thema antworten  [ 7 Beiträge ] 
Autor Nachricht
BeitragVerfasst: 03.07.2007, 10:50 
Offline
Moderator
Benutzeravatar

Registriert: 15.04.2007, 20:20
Beiträge: 505
Wohnort: Reelsen
So, in diesem Tutorial will ich (möglichst für Linux-Anfänger) erklären, wie man Linux ohne eine IDE wie KDevelop/Code::Blocks benutzt. Dies hat den Vorteil, dass es überall funktioniert und man ohne zusätzliche Programme auskommt, Nachteile sehe ich aufgrund der mächtigen Editoren, die immer dabei sind (gedit, kate, wenns sein muss halt vim) nicht.

Als erstes compilieren wir Irrlicht. Dazu extrahieren wie die Irrlicht-Sources, sagen wir mal, in den Ordner irrlicht-1.3.1/sources/Irrlicht. In diesen Ordner wechseln wir nun in einem Terminal mit "cd <Pfad zum Irrlicht-Verzeichnis>/irrlicht-1.3.1/sources/Irrlicht".
Die eigentliche Compilierung ist sehr einfach, einfach "make sharedlib" tippen und sich zurücklehnen. Es kann sein, das Fehler auftreten, weil bestimmte Pakete betreffend OpenGL nicht installiert sind, in diesem Fall diese Pakete installieren (kA, wie die für die jeweiligen Distributionen heißen, im Zweifelsfall im Forum nachfragen :) ), und das "make sharedlib" wiederholen.
Nun sollte eine Datei "libIrrlicht.so.1.3.1" im Verzeichnis irrlicht-1.3.1/lib/Linux entstanden sein, diese entspricht einer DLL unter Windows und hat im Gegensatz zu der libIrrlicht.a den Vorteil, dass später nur noch diese .so-Datei verlinkt werden muss.
Der Einfachheit halber installieren wir sie nun per "su -c "make install""(inklusive innere Anführungszeichen).

Für die bequeme Compilierung des Projekts selbst erstellen wir nun eine Makefile, die sich dann später ganz bequem per "make" aufrufen lässt und das Projekt automatisch compiliert.
Eine Makefile besteht aus Targets (Zielen), die jeweils eine bestimmte Aufgabe haben, diese Targets haben nun bestimmte Abhängigkeiten (entweder Sourcecodes oder andere Targets), sie werden nun beim Aufruf von make nur compiliert, wenn die Abhängigkeiten seit dem letzten Compilieren verändert wurden, dies spart bei großen Projekten deutlich Zeit, wie man es auch von den meisten IDEs kennt (tatsächlich benutzen viele IDEs sogar make mit automatisch generierten Makefiles, zB Code::Blocks).

Eine Makefile sieht nun so aus:
Code:
program: abh1 abh2
abh1: src1
<TAB>Befehl1
<TAB>Befehl2

abh2: src2
<TAB>Befehl1
<TAB>Befehl2

Wenn man nun "make" aufruft, wird das erste Target in der Datei ausgeführt. In diesem Fall basiert es auf den Abhängigkeiten abh1 und abh2. Nun werden diese beiden Targets weiter verfolgt. Make sieht nun, dass abh2 auf dem Target src1 basiert, es schaut nun, ob sich src1 (Wir nehmen an, es handelt sich dabei um eine Datei) seit dem letzten Compilieren verändert hat (wichtig ist dabei, dass abh1 auch eine Datei diesen Namens erzeugt, um den Timestamp der Datei vergleichen zu können, wenn nicht, wird src1 IMMER compiliert). Wenn src1 neuer ist als abh1, werden nun die Befehle für abh1 ausgeführt, diese werden mit einem Tab (wichtig, sonst kann make die Makefile nicht korrekt auswerten!) eingerückt. Das selbe passiert auch mit abh2. Wurde jetzt entweder abh1 oder abh2 neu erstellt, dann wird auch program neu erstellt, denn die Abhängigkeiten haben sich verändert. In diesem Fall beinhaltet program jedoch keine Befehle und hat so keine Wirkung.
Das selbe noch mal etwas konkreter, das folgende Makefile kann benutzt werden, um ein einfaches HelloWorld-Programm oder ähnliches zu kompilieren:
Code:
all: helloworld
clean:
<TAB>-rm main.o helloworld
rebuild: clean helloworld
helloworld: main.o
<TAB>$(CXX) main.o -o helloworld
main.o: main.cpp
<TAB>$(CXX) -c main.cpp -o main.o

Das ganze sollte zum größten Teil nun verständlich sein, dazu gekommen sind:

- Bestimmte Aufgaben, die in den meisten Makefiles vorhanden sind, wurden als Targets definiert, und zwar:
all - Erstellt einfach das Programm (helloworld) und wird als das erste Target standardmäßig ausgeführt.
clean - Löscht alle Ausgabedateien (main.o, helloworld), das "-" vor dem Befehl macht, dass make nicht abbricht, wenn main.o oder helloworld nicht vorhanden sind, dies ist wichtig, damit "rebuild" funktioniert.
rebuild - Nicht so oft zu sehen, aber ich finde es praktisch, es löscht erst alle Ausgabedateien (clean) und erstellt dann wieder das Programm.
Diese Targets können per "make clean" oder "make rebuild" aufgerufen werden, für all reicht "make".

- Zum Linken des komplette Programmes wird der folgende Befehl benutzt:
Code:
$(CXX) main.o -o helloworld

$(CXX) ist eine Variable, die unter Linux auf den G++-Compiler verweist, sie kann dürch "g++" ersetzt werden, so ist es aber einfacher, wenn zB zum Crosscompilieren ein anderer Compiler verwendet werden soll.
main.o ist eine Eingabedatei, sie ist die Objektdatei, die beim Compilieren von main.c entsteht, dieser Befehl compiliert also nichts selber, sondern linkt nur die ganzen Objektdateien zusammen.
"-o helloworld" bestimmt den Ausgabedateinamen, wird diese Option weggelassen, heißt die entstehende Datei "a.out". Wir hatten gesagt, die Ausgabedatei soll wie das Target heißen - Stimmt.

- Zum Compilieren von main.c wird folgender Befehl benutzt:
Code:
$(CXX) -c main.cpp -o main.o

Hier ist $(CXX) wieder der Compiler G++.
Mit "-c" erreichen wir, das kein fertiges Programm erstellt wird, sondern eine Objektdatei, da nur compiliert und nicht gelinkt wird.
main.c ist wieder die Eingabedatei und "-o main.o" die Ausgabedatei.

Diese Makefile lässt sich jetzt sehr bequem per "make" ausführen (Man sollte allerdings die gesamte Zeit eine Konsole in dem Projektordner geöffnet haben).

Nun ist das schon für kleine Projekte ganz nett, aber sobald man sehr viele Dateien hat, wird die Makefile sehr lang. Für große Projekte deutlich vereinfachen kann man alles, indem man Variablen für sowohl Dateinamen als auch Compileroptionen einsetzt:
Code:
SRC = main.cpp datei1.cpp datei2.cpp datei3.cpp
OBJ = $(SRC:%cpp=%o)

CFLAGS = -O2
CXXFLAGS = $(CFLAGS)
LDFLAGS =

all: program
clean:
<TAB>-rm $(OBJ) program
rebuild: clean program

program: $(OBJ)
<TAB>$(CXX) $^ -o $@ $(LDFLAGS)

%o: %cpp
<TAB>$(CXX) -c -o $@ $< $(CXXFLAGS)

Heh... das ist wirklich halb so schlimm, wie es aussieht :)

Nun, am Anfang definieren wie erst mal die Variable "SRC", sie wird selbst nicht direkt benutzt und enthält sämtliche Quellcodes, durch Leerzeichen getrennt.
Die nächste Zeile ("OBJ = $(SRC:%.cpp=%.o)") erstellt nun aus SRC eine neue Variable, OBJ, diese enthält alle Objektdateien, indem alle Vorkommen von ".cpp" durch ".o" ersetzt werden.
CFLAGS enthält die Optionen, die zum Compilieren von C-Dateien benutzt werden, hier wird es nicht benötigt, da wir von einem reinen C++-Projekt ausgehen. CXXFLAGS enthält die Optionen für C++-Dateien, meistens sind es die selben Optionen wie für C-Dateien, die Variable wird nun einfach kopiert. In diesem Beispielprogramm hat nun CXXFLAGS den Wert "-O2", dadurch wird der g++ dazu gebracht, das Programm zu optimieren.
LDFLAGS enthält nun die Optionen für den Linker, hier kommen die benötigten Libraries und ähnliches hin.

Nun kommen die Befehle, in die die Variablen eingefügt werden:

"-rm $(OBJ) program" - hier werden nun alle Objektdateien gelöscht, dazu kommt noch die Datei program.

"program: $(OBJ)" - Hier werden dem Target program alle Objektdateien als Abhängigkeiten angefügt.
"$(CXX) $^ -o $@ $(LDFLAGS)" - hier werden nun alle Objektdateien gelinkt. Einfach zu verstehen ist das "LDFLAGS", hier werden einfach die Linker-Optionen angehängt. $@ und $^ sind zwei spezielle Variablen: $@ steht immer für den Namen des Targets, in diesem Fall "program". Dadurch wird "-o $@ ersetzt durch "-o program". $^ steht für ALLE Abhängigkeiten. Dadurch wird $^ ersetzt durch "$(OBJ)", sodass alle Objektdateien als Eingabedateien benutzt werden.

"%o: %cpp" - dies ist ein automatisches Target: Es gilt für alle .o-Dateien und setzt als abhängigkeit automatisch den selben Dateinamen, allerdings mit .cpp ein - genau das, was wir brauchen :)
"$(CXX) -c -o $@ $< $(CXXFLAGS)" - Dies entspricht dem Linkerbefehl, $@ zeigt dieses Mal auf eine .o-Datei, und $< auf die ERSTE Abhängigkeit, also die C++-Datei. An dieser Stelle wird auch klar, wozu diese Variablen gut sind - da wir nicht wissen, was jeweils als Target/Abhängigkeit eingesetzt wird, können wir diese Daten nicht fest in den Befehl schreiben. -c macht wieder, das nicht gelinkt wird, und $(CXXFLAGS) bindet die Optionen für den Compiler ein.

Nun, daraus lässt sich doch schon eindeutig mehr machen, oder?
Eine Beispielmakefile für die Irrlichtengine sähe so aus:
Code:
SRC = main.cpp player.cpp world.cpp menu.cpp network.cpp
OBJ = $(SRC:%cpp=%o)

PATH_TO_IRRLICHT = /home/phoenix/irrlicht-1.3.1
CFLAGS = -I $(PATH_TO_IRRLICHT)/include
CXXFLAGS = $(CFLAGS)
LDFLAGS = -lIrrlicht

all: mygame
clean:
<TAB>-rm $(OBJ) mygame
rebuild: clean mygame

mygame: $(OBJ)
<TAB>$(CXX) $^ -o $@ $(LDFLAGS)

%o: %cpp
<TAB>$(CXX) -c -o $@ $< $(CXXFLAGS)

Hier muss nur noch CFLAGS angepasst werden:
Per -I $(PATH_TO_IRRLICHT)/include wird dem Compiler gesagt, dass er in diesem Ordner nach den Irrlicht-Headern suchen soll. PATH_TO_IRRLICHT muss dabei selbst angepasst werden.
Dazu kommt noch, dass den LDFLAGS "-lIrrlicht" angehängt wird, um Irrlicht zu verlinken (Ich nehme hierbei an, dass ihr am Anfang Irrlicht per "su -c "make install"" in einen von g++ automatisch durchsuchten Pfad kopiert habt).

Nun, das wars eigentlich schon - mit solch einer Makefile könnt ihr eure Irrlicht-Projekte schön bequem kompilieren.

EDIT:
Was diese Makefile natürlich noch nicht kann, ist, wenn man eine Headerdatei ändert, alle darauf basierenden Sourcedateien zu ändern.
Die Irrlicht-Engine macht dies durch:
Code:
%.d:%.cpp

   $(CXX) $(CXXFLAGS) -MM -MF $@ $<


-include $(OBJ:.o=.d)

Hier werden durch "$(CXX) -MM -MF" alle Headerdateien aufgelistet und als Target/Abhängigkeit-Information in einer .d-Datei gespeichert, da diese Datei nun das selbe Format wie eine Makefile hat, können alle .d-Dateien per include eingebunden werden (das "-" erreicht wiederum, dass make nicht fehlschlägt, wenn .d-Dateien fehlen, weil sie noch nicht erstellt wurden.
Dementsprechend muss dann halt "mygame: $(OBJ)" in "mygame: $(OBJ:.o=.d) $(OBJ)" umgewandelt werden, damit die Dateien erstellt werden (Denke ich mal, nicht ausprobiert). Dies kann halt die Compilierzeit vergrößern, wenn das Projekt nur ein Mal erstellt werden soll, später hilft es dann aber, Fehler zu vermeiden.

EDIT: $< teilweise durch $^ ersetzt.

_________________
Meine Gameengine :)
Bild


Zuletzt geändert von thephoenix am 05.03.2008, 20:23, insgesamt 1-mal geändert.

Nach oben
 Profil  
 
BeitragVerfasst: 21.09.2007, 17:43 
Offline
Benutzeravatar

Registriert: 21.09.2007, 16:21
Beiträge: 1
Erst einmal, vielen Dank für dieses Tutorial! Die meistens Tricks mit dem Makefile habe ich zwar irgendwann schon einmal gekannt, aber so zusammengefasst hat es mir trotzdem sehr geholfen.

Allerdings bekam ich nach dem (erfolgreichen) kompilieren beim ausführen des Programms die folgende Fehlermeldung:
Code:
error while loading shared libraries: libIrrlicht.so: cannot open shared object file: No such file or directory

Ich benutze Ubuntu 7.04 (Feisty Fawn) und habe Irrlicht 1.3.1 mit Hilfe des mitgelieferten Makefiles kompiliert und installiert (wie im Tutorial beschrieben). Dabei wird die libIrrlicht.so.1.3.1 in das Verzeichnis /usr/local/lib kopiert.

Anscheinend ist dieser Pfad jedoch nicht im Standard-Suchpfad für shared libraries enthalten (mag sein dass das Ubuntu-spezifisch ist).
Nach kurzer suche bin ich auf diese Seite gestossen, in der erklärt wird wie man das Problem beseitigen kann.
Für alle, die das selbe Problem haben, hier die Kurzfassung:

Variante 1 (vom Autor der Seite empfohlen):
Den Pfad zum System-Suchpfad hinzufügen. Dazu "/usr/local/lib" (ohne Anführungszeichen) in die Date '/etc/ld.so.conf' eintragen (einfach am Ende hinzufügen).

Variante 2 (so hab ich es gemacht):
Den Pfad in der ausführbaren Datei einkompilieren. Dazu folgendes in dem Makefile verändern:
Code:
LDFLAGS = -lIrrlicht

wird zu
Code:
LDFLAGS =  -Wl,-rpath /usr/local/lib -lIrrlicht

Durch das -Wl wird der nach dem Komma folgende Parameter vom Compiler ignoriert und an den Linker (der die ausführbare Date "zusammensetzt") weitergereicht.

Ich hoffe das hilft einigen weiter!

Gruß,
- Crayzee Ivan


Nach oben
 Profil  
 
BeitragVerfasst: 05.03.2008, 18:05 
Offline

Registriert: 08.04.2007, 16:55
Beiträge: 115
Hi, ich hab meien Probleme mit diesem Setup...
Ich habe zwei cpp files : main.cpp und ribbon.cpp

Das makefile erstellt korreckt die zwei *.o files, versagt dann aber bezüglich des Linkens.
Das versucht offenbar für jedes *.o - file einzelnd zu linken: (die *.o liegen im /obj/ folder)

Zitat:
g++ -c -o obj/main.o main.cpp
g++ -c -o obj/ribbon.o ribbon.cpp
g++ obj/main.o -o ../MyTestGame -l*libs here*
obj/main.o: In function `main':
main.cpp:(.text+0x980): undefined reference to `sf::Ribbon::Ribbon()'
obj/main.o: In function `sf::Ribbon::~Ribbon()':
main.cpp:(.text._ZN2sf6RibbonD1Ev[sf::Ribbon::~Ribbon()]+0x7): undefined reference to `vtable for sf::Ribbon'
collect2: ld gab 1 als Ende-Status zurück
make: *** [MyTestGame] Fehler 1

die Letzte g++ zeile müßte correcterweise so lauten:
g++ obj/main.o obj/ribbon.o -o ../MyTestGame-l*libs here*
kannst du mir da nen tipp geben, wie ich das bewerkstellige??


Nach oben
 Profil  
 
BeitragVerfasst: 05.03.2008, 20:20 
Offline
Moderator
Benutzeravatar

Registriert: 15.04.2007, 20:20
Beiträge: 505
Wohnort: Reelsen
Ah, thx. Da ist ein Fehler im Tut.
bei dem Linken muss statt "$<" "$^" genommen werden, damit nicht nur die erste Datei eingefügt wird.

_________________
Meine Gameengine :)
Bild


Nach oben
 Profil  
 
BeitragVerfasst: 07.03.2008, 20:51 
Offline

Registriert: 08.04.2007, 16:55
Beiträge: 115
hmm joa stimmt so gehts.. aber nochmal ne ganz dumme Frage:
ich habe meine .o im verscheichniß /obj/ liegen..

Die Zeile:
$(CXX) -c -o obj/$@ $< $(CXXFLAGS)
hängt aber nur vor das erste file ein "obj/" lässt sich da was drehen??


Nach oben
 Profil  
 
BeitragVerfasst: 07.03.2008, 22:46 
Offline
Moderator
Benutzeravatar

Registriert: 15.04.2007, 20:20
Beiträge: 505
Wohnort: Reelsen
Was du möchtest, ist wahrscheinlich das da:
Code:
OBJ = $(SRC:%cpp=obj/%o)

Im Übrigen ist es nie gut, wenn die Ausgabedatei vom Namen des Targets abweicht, da make dann nicht richtig funktionieren kann.

_________________
Meine Gameengine :)
Bild


Nach oben
 Profil  
 
BeitragVerfasst: 08.03.2008, 20:57 
Offline

Registriert: 08.04.2007, 16:55
Beiträge: 115
okeydokey ;) ich dachte das das der übersicht gut tut^^


Nach oben
 Profil  
 
Beiträge der letzten Zeit anzeigen:  Sortiere nach  
Ein neues Thema erstellen Auf das Thema antworten  [ 7 Beiträge ] 

Alle Zeiten sind UTC + 1 Stunde


Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 3 Gäste


Du darfst keine neuen Themen in diesem Forum erstellen.
Du darfst keine Antworten zu Themen in diesem Forum erstellen.
Du darfst deine Beiträge in diesem Forum nicht ändern.
Du darfst deine Beiträge in diesem Forum nicht löschen.
Du darfst keine Dateianhänge in diesem Forum erstellen.

Suche nach:
Gehe zu:  
cron
Powered by phpBB® Forum Software © phpBB Group
Deutsche Übersetzung durch phpBB.de