Aaron Fischer Ingenieur, Vater, Heimwerker, Problemlöser

21 Dezember, 2009

Tag 22: Deployment und Portierung auf andere Systeme

TL;DR:

Das Spiel ist (theoretisch) fertig. Klar, alles wurde nur ansatzweise programmiert, aber gehen wir mal davon aus, dass wir nun eine stabile Version haben, die wir der Öffentlichkeit zeigen wollen. Wir müssen jetzt dafür sorgen, dass wir den Programmcode unters Volk bekommen und eine möglichst große Zielgruppe ansprechen. Deshalb werden wir uns noch ein paar Gedanken über Portierung machen.

Um unser Spiel auf einem Rechner zu installieren, muss der User zunächst die Software per Git herunterladen, danach compilieren und die so erzeugten Dateien in die richtigen Verzeichnisse legen. Dies ist ziemlich umständlich, sogar für uns. Bauen wir also zunächst unser Makefile etwas um, so dass wir es etwas leichter haben.

Fügen wir zwei neue Tasks hinzu.

install: compile man
       cp $(OUTDIR)/$(NAME) /usr/local/bin/
       cp $(OUTDIR)/$(NAME).6.gz /usr/local/man/

uninstall:
       rm /usr/local/bin/adventgame || true
       rm /usr/local/man/adventgame.6.gz || true

Der install Task hängt von compile und man ab, sprich bevor wir installieren können, müssen wir den Sourcecode compilieren und die Manpage packen. Die Zielverzeichnisse sind hier noch händisch eingetragen, dies sollten wir schnellstmöglich über einen Flag dem Benutzer überlassen. Beim uninstall Task löschen wir einfach die beiden kopierten Dateien.

Somit hätten wir schon mal die erste Hürde genommen, doch die Benutzer müssen sich immer noch mit Git auskennen und in der Lage sein, unser Projekt auszuchecken. Um dies zu vereinfachen, erstellen wir einen weiteren Task in unserem Makefile, der uns ein Archiv erzeugt, in dem alles wichtige enthalten ist.

export: clean
       mkdir -p $(OUTDIR)/adventgame-$(VERSION)/
       cp -r src/ doc/ dist/ Makefile $(OUTDIR)/adventgame-$(VERSION)/
       cp README.rdoc $(OUTDIR)/adventgame-$(VERSION)/README
       tar cjf $(OUTDIR)/adventgame-$(VERSION).tar.bz2 -C $(OUTDIR)/ adventgame-$(VERSION)

Wir packen alles notwendige zusammen, benennen die README.rdoc um und versehen alles mit einer Versionsnummer. Dies ist essenziell, da wir hier Git verlassen und somit auch die Versionierung. Dem Sourcecode sieht man ohne eine Versionsnummer nicht mehr an, ob er neuer oder älter ist als eine andere Version. Diese Datei können wir nun wieder auf Github hochladen, und jeder kann diese Datei direkt über den Browser herunterladen.

Da wir schon beim Hürden einreißen sind, wäre es natürlich toll, wenn wir nicht nur den Sourcecode herausgeben, sondern auch ein schon fertig compiliertes Binary. Die allermeisten Linux-Distributionen bieten hier einen Paketmanager an, unter OSX werden Image-Files verwendet und unter Windows die Installer (oder wie nennt man die setup.exe dort?).

Für Gentoo Linux habe ich bereits angefangen, ein ebuild zu erstellen, für Ubuntu und Debian ist dies auch nicht mehr sehr viel mehr. Es muss hier im Grunde nur eine Konfigurationsdatei angelegt werden, die das Paket beschreibt und die einzelnen Schritte wie compilieren, installieren usw. festlegt. Wer also möchte, kann mir bei der Erstellung eines Packages für seine Distribution gerne helfen. Solche Personen werden Maintainer genannt und sind übrigens sehr gefragt. Wenn man einmal den Dreh raus hat, ist es nicht mehr schwer und macht richtig Spaß! Man muss hierfür nicht am Projekt mitwirken, man muss noch nicht einmal programmieren können.

Gehen wir noch einen Schritt weiter: Um Pakete für OSX und Windows zu schnüren, müssen wir die Software irgendwie auf den Zielplattformen compilieren, da der Kernel des jeweiligen Betriebssystems nichts (oder nur sehr umständlich) mit einem Linux Binary anfangen kann.

Damit wir nicht drei oder mehr parallele Entwicklungszweige warten müssen, versuchen wir den Sourcecode so zu ändern, dass er einfach überall compiliert. Dazu können wir ein paar Präprozessordirektiven verwenden, um festzustellen, auf welcher Plattform wir uns befinden. Ein kleines Beispiel:

#ifdef __unix__ || __linux__
// Spezielle Befehle nur für Linux
#elif __WIN32__ || _MSC_VER
// Spezielle Befehle nur für Windows
#endif

Somit können wir beispielsweise verschiedene Header-Dateien einbinden, je nach dem auf welcher Zielplattform wir uns befinden. Etwas kniffliger wird es, wenn wir z.B. unser Spiel auf die Wii portieren wollen. Hier gibt es ganz andere Eingabegeräte und offensichtlich keine Tastatur im herkömmlichen Sinne. Auch die Ausgabe verläuft etwas anders, da wir hier nur ein großes Vollbild-Fenster haben.

Um dieses Problem zu lösen, könnten wir Makros verwenden. Dazu wieder ein kleines Beispiel:

#ifdef __wiippc__
#define GET_WIDTH() (...)
#else
#define GET_WIDTH() (800)
#endif

Die Funktion GET_WIDTH() kann hier unterschiedliches zurückliefern, je nach dem für welche Plattform wir compilieren. In manchen Projekten wird diese Methode ziemlich häufig verwendet, was den Code zwar leichter portierbar macht aber auch etwas unübersichtlicher gestaltet. Man sollte sich also genau überlegen, an welchen Stellen es Sinn ergibt und an welchen Stellen man lieber eine andere Technik verwendet.

Der große Vorteil an den Präprozessordirektiven ist der, dass im eigentlichen Binary nur der Plattformspezifische Code enthalten ist, der Rest wird schon vor dem Compilieren weggeworfen.

Das Portieren des Spiels auf Windows und OSX habe ich jetzt nicht gemacht. Ebenso das Erstellen der Linux Packages steht noch aus, da zähle ich auf euch. Am letzten Tag werden wir noch einmal zusammenfassen, was wir geschafft haben. Zudem werde ich noch einen kleinen Ausblick geben und ein paar Aspekte ansprechen, die wir hier noch gar nicht beleuchtet haben, die aber alle noch in das Gebiet von Open Source mit einfließt. Freut euch drauf.