Entwickler komplexer Projekte müssen oft Open-Source Bibliotheken einbinden. Insbesondere in der nativen Entwicklung können diese Bibliotheken eine hohe Anzahl an Abhängigkeiten haben. Diese Abhängigkeiten können sowohl in Form von weiteren Bibliotheken, Tools oder sogar ganze Programmiersprachen auftreten. Ein gutes Beispiel hierfür ist libVLC. libVLC ist die Bibliothek, auf die der populäre VLC Player aufgebaut ist. Als Entwickler kann man libVLC nutzen, um Audio und Video in eigenen Anwendungen abzuspielen.
Wichtig! Die folgenden Ausführungen setzen ein voll funktionsfähiges Linux mit X-Server und aktueller Docker (v1.9.1) Installation voraus.
In einem aktuellen Projekt hatten wir die Aufgabe, eine reproduzierbare Entwicklungsumgebung zu schaffen, auf deren Basis wir eine komplexe Qt5 Anwendung unter Linux implementieren wollten, die unter anderem Videos abspielen sollte. Das stellte uns vor zwei Herausforderungen:
- wie können wir eine reproduzierbare Entwicklungsumgebung mit einer definierten Version von libVLC schaffen, und trotzdem den Entwicklern die Freiheit lassen, mit ihren bevorzugten Tools und Editoren zu arbeiten?
- libVLC bindet Qt4 ein. Wir nutzen Qt5. Es ist bekannt, dass sich Qt4 und Qt5 im selben Projekt nicht nebeneinander ausführen lassen, ohne dass es Probleme zur Laufzeit gibt. Wie können wir also eine libVLC Version ohne Qt4 bauen?
Für die erste Herausforderung war eine Lösung relativ schnell gefunden. Wir haben ein Docker Image erstellt, das eine Basis Ubuntu Distribution mit der zu diesem Zeitpunkt aktuellen Version 2.2.1 von libVLC und allen nötigen Tools und Abhängigkeiten enthält. Die Idee ist, dass man als Entwickler den entsprechenden Container lokal ausführt und über einen Volume Mount sein Sourcecode mit dem Container teilt und baut. Somit kann das Editieren des Codes und das Arbeiten mit git und anderen Tools wie gewohnt auf dem lokalen Rechner statt finden. Nur zum Bauen muss der Container bemüht werden.
Das Qt4 Problem haben wir gelöst, indem wir einen Patch in libVLC während der Erstellung des Docker Images eingebaut haben, der zu einer Version von libVLC ohne Qt4 führt. Entgegen anfänglicher Bedenken ist das unproblematisch, weil Qt4 hauptsächlich für Benutzeroberflächen von Plugin-Einstellungen und ähnliches verwendet wird. Da wir libVLC lediglich einbinden und unsere eigene UI entwickelt haben, ist es für uns kein Problem, auf diese Funktionen zu verzichten.
Im Folgenden werde ich zeigen, wie man ein minimales C Programm, das eine Mediendatei mit libVLC wiedergibt, mit dem Image kompilieren und linken kann.
Das Image befindet sich auf unserem öffentlichen Repository, von dort können wir es erst einmal mittels pull auf unserem lokalen Rechner ziehen:
docker pull pulsarsolutions/libvlc_no_qt:2.2.1
Und dann interaktiv ausführen:
docker run --rm -it pulsarsolutions/libvlc_no_qt:2.2.1 bash
Nach dem o.g. Befehl sollte man sich in einer bash Shell im Container befinden. Damit kann man allerdings noch nicht viel anfangen, da es gar keinen Sourcecode zum kompilieren gibt. Das ändern wir jetzt. Wir gehen wieder aus dem laufendem Container indem wir
exit
eingeben. Als nächstes legen wir ein neues Verzeichnis an
mkdir /home/marcelo/projects/vlc_minimal_sample
und erzeugen dort eine neue C-Quelldatei test.c
mit folgendem Inhalt:
#include <stdio.h>
#include <stdlib.h>
#include <vlc/vlc.h>
int main(int argc, char **argv)
{
libvlc_instance_t *inst;
libvlc_media_player_t *mp;
libvlc_media_t *m;
// load the vlc engine
inst = libvlc_new(0, NULL);
// create a new item
m = libvlc_media_new_location(inst, "http://www.sample-videos.com/video/mp4/360/big_buck_bunny_360p_5mb.mp4");
// create a media play playing environment
mp = libvlc_media_player_new_from_media(m);
// no need to keep the media now
libvlc_media_release(m);
// play the media_player
libvlc_media_player_play(mp);
getchar();
// stop playing
libvlc_media_player_stop(mp);
// free the media_player
libvlc_media_player_release(mp);
libvlc_release(inst);
return 0;
}
Das Programm ist eine minimalistische Implementierung einer Videowiedergabe mit libVLC. Das Video wird direkt aus dem Internet abgespielt, daher sollte der Host, also die Maschine auf der der Container gestartet wird, auch mit dem Internet verbunden sein.
Unser Ziel ist es, diese Datei nun im laufenden Container zu kompilieren und zu linken, denn nur im Container sind die dafür nötigen Tools, Bibliotheken und Abhängigkeiten installiert. Um die Datei dem Container bereitzustellen verwenden wir einen sog. Volume Mount. Das bedeutet, dass ein lokales Verzeichnis (Volume) in ein Verzeichnis im Container gemounted wird. Damit sieht unser docker Aufruf jetzt so aus:
docker run --rm -it -v /home/marcelo/projects/vlc_minimal_sample/:/home/developer/src pulsarsolutions/libvlc_no_qt:2.2.1
Damit wird das lokale Verzeichnis /home/marcelo/projects/vlcminimalsample
auf das Verzeichnis /home/developer/src
im Container gemounted. An dieser Stelle sei darauf hingewiesen, dass der Container einen Default-Benutzer namens developer
anlegt. Dieser Benutzer hat die id 1000 und gehört einer Gruppe die ebenfalls die id 1000 hat. Wenn der lokale Benutzer auf dem Host eine andere Kombination von uid:gid hat, kann es zu einem Rechteproblem kommen. In einem zukünftigen Artikel werde ich beschreiben, wie man damit umgehen kann.
Nachdem wir den o.g. Befehl ausgeführt haben, können wir cd src
im Container in das Verzeichnis wechseln, in das unsere Quelldatei liegt. Nun können wir diese wie folgt übersetzen:
gcc $(pkg-config --cflags libvlc) -c test.c -o test.o
gcc test.o -o test $(pkg-config --libs libvlc)
Im Anschluss daran sollte eine ausführbare Datei test
im aktuellen Verzeichnis liegen. Mit
./test
kann die Datei ausgeführt werden. Da wir uns in der Kommandozeile befinden werden wir aber nicht viel zu sehen bekommen, die Shell wird höchstens versuchen, das Video im Text-Modus zu rendern. Als letzten Schritt in diesem Artikel werden wir nun die Ausgabe des Containers auf den lokalen X-Server im Host umleiten, so dass wir das Video-Fenster auch sehen können.
Das geschieht mit einem weiteren Volume Mount und der Übergabe der DISPLAY
Umgebungsvariable und sieht dann so aus:
docker run --rm \
-v --net=host \
-e DISPLAY -v \ /home/marcelo/projects/vlc_minimal_sample/:/home/developer/src \
-it pulsarsolutions/libvlc_no_qt:2.2.1 bash
Indem wir die DISPLAY
Umgebungsvariable sowie die Option --net=host
verwenden, kann der Docker Container auf den X Server und somit auf das Display zugreifen.
Die Option
--net=host
stellt auf Produktivsystemen ein Sicherheitsrisiko dar, da es im Grunde das Netzwerk des Containers nicht kapselt. Daher sollte diese Option nur für lokale Umgebungen verwendet werden, die man selber kontrolliert.
Wenn wir nun
./test
ausführen, sollte das Video in einem neuen Fenster im Host abgespielt werden.
Das war nur ein kurzer Einblick in die Möglichkeiten die Docker bietet, um reproduzierbare Entwicklungsumgebungen bereit zu stellen. Aus Platzgründen sind hier viele Themen nicht erwähnt worden, wie z.B. Audio oder IDEs. Das wird dann Gegenstand zukünftiger Artikel sein.