Die Entwicklung moderner Anwendungen erfordert meistens, dass die Entwickler eine Vielzahl von Tools und Services auf ihren Rechnern zur Verfügung haben: Compiler, Laufzeitumgebungen und IDEs um Quellcode editieren und bauen zu können, Infrastrukturservices wie Datenbanken und Messaging Dienste um die Anwendungen testen zu können, sowie Clients beispielsweise für Kubernetes um Probleme auf Test- oder Stagingumgebungen analysieren zu können. Für alle Entwickler eines Teams sollte transparent und nachvollziehbar sein welche Tools benötigt werden. Welche Möglichkeiten gibt es das umzusetzen?

Meistens wird die Installation der benötigten Software in Wikis oder READMEs dokumentiert. Dies birgt jedoch die Gefahr, dass die Informationen schnell veralten oder unvollständig sind, weil weitere Tools hinzugekommen sind und wird spätestens dann unübersichtlich, wenn Anleitungen für mehrere Betriebssysteme geschrieben werden müssen, beispielsweise weil im Entwicklungsteam mit Linux und macOS gearbeitet wird. Vor allem erfordert es aber ein manuelles Installieren und Aktualisieren der jeweiligen Toolchain, was aufwendig und fehleranfällig ist.

Beispiel für eine Anwendung und die benötigten Tools und Services
Beispiel für eine Anwendung und die benötigten Tools und Services

Vielfach arbeiten Entwickler auch an mehr als einer Anwendung, weshalb sie darauf angewiesen sind, schnell zu den spezifischen Tools der jeweiligen Anwendung – beispielsweise unterschiedliche Versionen von node.js, Python, Java oder auch Postgres – wechseln zu können. Dafür gibt es zwar eine Reihe plattformspezifischer Version Manager wie etwa nvm, den Node Version Manager, welche die parallele Installation mehrerer Versionen und den nahtlosen Wechsel dazwischen ermöglichen, diese lösen das Problem jedoch nur auf der Ebene der einzelnen Entwicklungsplattform, nicht für die komplette Toolchain.

Gesucht wird eine Möglichkeit, diese beiden Aufgaben plattformunabhängig zu automatisieren. Wünschenswert wäre ein Skript, welches zusammen mit dem Quelltext in die Versionsverwaltung eingecheckt wird, die Installation der benötigten Tools übernimmt und kurz und verständlich genug ist um von allen Entwicklern des Teams bei Bedarf angepasst und erweitert werden zu können. Im Idealfall weitgehend unabhängig davon, welches Betriebssystem ein Entwickler verwendet. Damit wäre es nicht nur möglich, neuen Mitarbeitern einen schnellen Start zu ermöglichen, sondern auch Änderungen schnell im Team zu verbreiten. Welche Möglichkeiten gibt es dafür?

Für Infrastrukturservices, wie Datenbanken, Messaging Server oder auch Authentisierungsprovider ist es oftmals sinnvoll mit Containern oder, sofern Kubernetes für den Betrieb verwendet wird, mit speziellen Entwicklungstools für Kubernetes zu arbeiten, welche zusätzliche Möglickeiten bieten. Genannt seien hier docker-compose, kubefwd, MicroK8s oder minikube.
Auch für Entwicklungstools wie Compiler, Laufzeitumgebungen, Testtools oder CLIs kann man prinzipiell auf Container zurückgreifen – auf DockerHub finden sich für die meisten Tools entsprechende Images. Das ist jedoch ein relativ umständliches und ressourcenintensives Unterfangen: Für jedes Tool muss ein Image heruntergeladen werden, für jeden Toolaufruf ein neuer Container erzeugt und nach Beendigung wieder gelöscht werden. Sollen Dateien verarbeitet werden, beispielsweise beim kompilieren, muss das lokale Quelltextverzeichnis in den Container gemountet werden, unter Umständen muss auch noch eine passende User ID für die Ausführung innerhalb des Containers gesetzt werden.

Beispiel für einen Maven Build:

$ docker run -it --rm --name my-project -v "$(pwd)":/usr/src/mymaven \
       -w /usr/src/mymaven maven:3.6.1-jdk-11 mvn install

Uns erschien das für einen dauerhaften Einsatz zu kompliziert, weshalb wir uns auf die Suche nach Alternativen gemacht haben. Und genau darum geht es in diesem ersten Teil einer zweiteiligen Reihe zur Automatisierung von Entwicklungsumgebungen: Wie kommen Entwicklungstools von denen jeder Entwickler häufig mehr als ein Dutzend benötigt möglichst umkompliziert auf seinen Rechner? Im zweiten Teil wird es dann um das Setup von Infrastrukturdiensten wie Datenbanken und Messaging Server und das Arbeiten mit Docker Containern und Kubernetes Clustern gehen.

Package Manager

Package Manager gibt es in unzählichen Varianten: Von plattformspezifisch, wie beispielsweise npm für node.js, über Linuxdistributions-spefisich wie apt für Debian bis hin zu Meta-Varianten in unterschiedlichen Ausprägungen. Für unseren Einsatzzweck suchen wir nach Paketmanagern mit den folgenden Eigenschaften:

Nix

Nix ist ein Package Manager für alle Linux-Distributionen sowie macOS und außerdem Bestandteil der Linux-Distribution NixOS. Er entstand aus einer Doktorarbeit heraus und beinhaltet eine eigene Scripting-Sprache, die ebenfalls nix heißt. Das Paketverzeichnis kann entweder über eine Weboberfläche oder mittels Kommandozeile durchsucht werden. Bei der Auswahl der Pakete ist darauf zu achten, dass einige wenige Pakete nur für Linux oder nur für macOS verfügbar sind. Mehrere Versionen eines Pakets, beispielsweise Python 2.7 und 3.7 können parallel installiert und je nach Projektanforderungen geladen werden. Um einzelne Pakete oder eine komplette Projektumgebung zu laden, wird das Tool Kommandozeilentool nix-shell verwendet. $ nix-shell -p nodejs-11_x startet beispielsweise eine Shell in der node.js 11 vorhanden ist. Allerdings nur solange die Shell aktiv ist. Wird die Shell mit Strg + D wieder verlassen, wird auch node.js 11 wieder aus dem Pfad entfernt.

Das folgende Skript-Beispiel installiert eine Reihe von Tools – OpenJDK, Maven, curl, etc. und führt anschließend noch zwei npm Kommandos aus.

pkgs.stdenv.mkDerivation {
  name = "meinProjekt";

  buildInputs = with pkgs; [
    openjdk8 maven nodejs-11_x docker_compose curl kubectl kubernetes-helm
  ];

  shellHook = ''
   npm install
   npm run compile
  '';
}

Diese Datei wird als default.nix im Projektverzeichnis abgelegt. Geladen wird sie, indem im Verzeichnis nix-shell aufgerufen wird. Es ist auch möglich Version Pinning zu verwenden und so immer die exakt gleiche Softwareversionen zu installieren.

Pakete die nicht im Store vorhanden sind, können selbst paketiert werden. Hierbei ist darauf zu achten, dass von manchen Projekten unterschiedliche Pakete für macOS und Linux bereitgestellt werden. Je nach Programmtyp – im Folgenden gekürzten Beispiel handelt es sich um eine Electron Desktopanwendung – muss außerdem ein entsprechender Wrapper verwendet werden.

camunda_modeler = stdenv.mkDerivation rec {
  name = "camunda_modeler";
  version= camundaVersion;
  src =
    if os == "linux" then
      pkgs.fetchurl { url = camundaUrlLinux; sha256 = camundaShaLinux; }
    else
      pkgs.fetchurl { url = camundaUrlMac; sha256 = camundaShaMac; };

  nativeBuildInputs = [ pkgs.electron pkgs.makeWrapper ];

  phases = [ "installPhase" ];
  installPhase =
    ''
    mkdir -p $out/var/lib/camunda $out/bin
    tar -xzf $src
    mv camunda-modeler-* camunda-modeler
    cp camunda-modeler/resources/app.asar $out/var/lib/camunda
    makeWrapper ${pkgs.electron}/bin/electron $out/bin/camunda-modeler --add-flags "$out/var/lib/camunda/app.asar"
    '';
};

Conda

Ist ein Package Manager für Windows, macOS und Linux der ursprünglich aus dem Datenanalyseumfeld stammt, mittlerweile aber um sehr viele allgemeine Tools erweitert wurde. Zu unterscheiden sind die beiden Ausgaben Anaconda und Miniconda. Anaconda beinhaltet einen kompletten Python und R Stack, Miniconda mehr oder weniger nur den Package Manager Conda, was für unsere Zwecke völlig ausreichend ist. Das Paketverzeichnis kann wieder entweder über eine Weboberfläche oder mittels Kommandozeilentool durchsucht werden.
Conda beinhaltet das Konzept Environments, damit sind voneinander isolierte Umgebungen gemeint in denen jeweils unterschiedliche Tools installiert werden können. Diese Environments können entweder über die Kommandozeile angelegt oder aus einem Skript geladen werden.

Die Versionen für die zu installierenden Pakete können auch explizit angegeben werden. Zu beachten ist, das nicht immer für alle Betriebssysteme die gleichen Versionen vorliegen.

Weitere Möglichkeiten

Bei macOS Benutzern ist Homebrew sehr beliebt, dieses unterstützt seit einiger Zeit auch Pakete für Linux. Allerdings werden unter Linux keine Casks unterstützt, weshalb sich in diesem Fall keine graphischen Tools installieren lassen. Snapcraft ist ein Package Manager der ursprünglich für das Ubuntu Phone entwickelt wurde mittlerweile aber für alle gängigen Linux-Distributionen verfügbar ist. Es ist gut für die Installation einzelner Tools geeignet, da das Angebot an Packages sehr groß ist. Es bringt jedoch keine Möglichkeit mit, ein komplettes Toolset zu verwalten, auch wird momentan keine Möglichkeit geboten zwischen verschiedenen Versionen eines Tools zu wechseln. Chocolatey ist ein Package Manager für Windows der in einer Open Source und einer kommerziellen Version angeboten wird. Die kommerzielle Version beinhaltet unter anderem Tools, um das Erstellen neuer Pakete zu vereinfachen. Die Dokumentation ist umfangreich und es sind Pakete für alle gängigen Entwicklungsplattformen enthalten. Die Installation mehrerer Versionen eines Tools ist grundsätzlich möglich, die Verwendung erfordert jedoch manuelle Anpassungen am PATH.

Name unterstützte Betriebssysteme Fileformat/Scripting zur Definition von Umgebungen mehrere Versionen Webseite
Nix Linux, macOS ja ja https://nixos.org/nix
Conda Linux, macOS, Windows ja ja https://conda.io
Homebrew macOS, Linux mit Einschränkungen nein ja https://brew.sh/ bzw. https://docs.brew.sh/Homebrew-on-Linux
Snapcraft Linux nein nein https://snapcraft.io
Chocolatey Windows nein jain ( manuelle Anpassungen erforderlich ) https://chocolatey.org

Conclusion

Fazit

Abhängig davon, welche Betriebssysteme unterstützt werden sollen, gibt es gleich mehrere Möglichkeiten, wie sich die Installation und Aktualisierung von Entwicklungstools relativ einfach automatisieren lässt und damit für erhebliche Zeitersparnis sorgen kann, auch wenn der Nutzungskomfort der einzelnen Tools sicher noch verbesserungswürdig ist. Bei Package Managern, die für Linux verfügar sind, ergeben sich durch das Windows Subsystem für Linux in Windows 10 weitere Nutzungsmöglichkeiten. nix ist der Package Manager mit den fundiertesten theoretischen Grundlagen und einem großen Funktionsumfang, der weit über das hier vorgestellte hinausreicht, erfordert aber auch einen hohen Einarbeitungsaufwand. Conda ist momentan der wahrscheinlich beste Kompromiss aus Funktionsumfang und Benutzerfreundlichkeit.
Interessant sind die hier vorgestellten Package Manager auch im Bereich Continuous Integration - die für die Entwicklung geschriebenen Skripte können unter Umständen für die Erstellung von Container Images zum Ausführen von Builds und Tests weiterverwendet werden.

TAGS