Python-Programme für Windows über GitLab CI automatisiert übersetzen

Build unter Microsoft Windows

Vor einiger Zeit hatte ich bereits über GitLab Continuous Integration berichtet und anhand eines Beispiels erklärt, wie sich damit automatisch auf mehreren Systemen Software-Pakete nach erfolgten Änderungen erstellen bzw. aktualisieren lassen. Für die Kommunikation zwischen GitLab und den Entwicklungssystemen gibt es zahlreiche Agenten, unter anderem auch für Microsoft Windows - und darum geht es in diesem Artikel.

In dem von mir geschilderten Beispiel geht es um die automatische Konvertierung von Python-Anwendungen (.py) in ausführbare Dateien (.exe) nach erfolgten Quellcode-Änderungen. Doch, warum sollte man das tun? Ich nutze das nachfolgend geschilderte Konzept für eigens entwickelte, generische Icinga Monitoring-Plugins. Sicherlich funktionieren Python-Skripte nach Installation der Laufzeitumgebung auch unter Windows - aber wenn man einzelne Skripte auf vielen Systemen verteilen muss, erhöht das den administrativen Aufwand ungemein. Eine Binärdatei lässt sich einfacher verteilen und bietet auch ein geringeres Sicherheitsrisiko als eine komplette Laufzeit-Umgebung, die aus vielen Unterprogrammen besteht.

Zur Konvertierung von Python-Skripten in ausführbare Dateien existieren viele Programme, ich verwende pyInstaller. Dieses Tool integriert alle benötigten Python-Module sowie auch die Microsoft Visual C++-Laufzeitumgebung, die zur Ausführung unter Windows benötigt wird, in einer einzelnen Datei. pyInstaller ist auch für andere Plattformen erhältlich und unterstützt derzeit Python 2.7 und 3.

Ziel war es, dass sämtliche Code-Änderungen des Master-Zweiges eine erneute Paketierung der Binärdateien auslöste.

Projekt CI-Einstellungen

Vorab ist es wichtig, CI für ein GitLab-Projekt zu aktivieren - dazu genügen die folgenden Schritte:

  1. Auswahl des Projekts, Anklicken von Settings und Project Settings.
  2. Anklicken von Builds und Save Changes.
  3. Auswahl von CI Settings, um erweiterte Parameter zu setzen. Dazu zählen u.a. Timeouts und automatische Builds.
  4. Anklicken von Save Changes.
  5. Auswahl von Runners, Notieren/Kopieren der CI-URL und dem CI-Token - diese Informationen werden später benötigt, um die Runner zu registrieren.

Host-Vorbereitung

Damit der Kommunikation und automatischen Erstellung von Binärdateien nichts mehr im Weg steht, gilt es noch einige Vorbereitungen zu treffen. Die wichtigste davon ist wohl das Erstellen eines dedizierten Benutzerkontos - bzw. eines Servicebenutzers. Je nach Infrastruktur variiert dieser Schritt (lokales Konto, Active Directory-Benutzer). Wichtig ist, dass der Benutzer die Berechtigungen zur Ausführung von Services erhält. Auf Client-Systemen, wie beispielsweise Windows 7, gibt es hierfür keine Einstellungsmöglichkeit - hier bleibt nur übrig, lokale Administratorrechte zu vergeben. 🙁

Darüber hinaus werden noch einige Software-Pakete benötigt:

Die Installation der einzelnen Software-Pakete gestaltet sich recht unkompliziert. Bei der Python-Installation ist zu beachten, dass der Haken "Add python.exe to path" gesetzt wird - nur dann können die Python-Programme auch von der Kommandozeile bzw. PowerShell aufgerufen werden. Bei der Git-Installation gibt es mit "Use Git from the Windows Command Prompt" eine ähnliche Einstellung, die gesetzt werden muss.

Die Installation von PyInstaller erfolgt über ein Python-Skript - dieses wird idealerweise in einer Kommandozeile mit Administratorrechten gestartet:

1C:\pyInstaller> python setup.py install
2...
3Finished processing dependencies for PyInstaller==3.1.1

Python und PyInstaller

Zeit, zu testen, ob Python und PyInstaller auch wirklich funktionieren. Der folgende Quellcode sollte problemlos von PyInstaller paketiert werden:

1#!/usr/bin/env python
2import os
3print "Hello world!"
4raw_input("Press ENTER...")

Das Skript gibt einen Satz aus und wartet anschließend auf Bestätigung, bevor es sich beendet. Der folgende Aufruf pakiertiert das Skript samt Laufzeitumgebung und benötigter Python-Module:

1C:\test> pyinstaller -F -y test.py

Der Schalter -F integriert die Python- und Microsoft Visual C++-Laufzeitumgebungen, -y überschreibt ggf. vorher existierende Dateien. Sofern das Programm fehlerfrei durchlaufen ist, finden sich neue Ordner build und dist im aktuellen Verzeichnis. Unterhalb des dist-Ordners existiert ein weiterer Ordner, der den Namen des Skripts trägt und die fertige Binärdatei enhält. Diese lässt sich also wie folgt aufrufen:

1c:\test> c:\test\dist\test\test.exe

Für Python-Skripte, die unter Windows lauffähig sein sollen, ist es übrigens unabdingbar das Python-Modul os zu laden - andernfalls erscheint folgende Fehlermeldung beim Ausführen der Binärdatei:

1ImportError: No module named os

Powershell und CI-Runner

Für die spätere Automatisierung der Paketierung kommt in meinem Beispiel ein PowerShell-Skript zum Einsatz. Damit eigens entwickelte und somit per se erstmal unsignierte PowerShell-Skripte ausgeführt werden können, musste ich die Ausführungsrichtlinie anpassen:

1PS C:> Set-ExecutionPolicy Unrestricted

In der Microsoft Knowledge-Base gibt es eine genauere Erklärung der einzelnen Einstellungsmöglichkeiten.

Der GitLab CI-Runner wird in einen Ordner freier Wahl kopiert, beispielsweise c:\gitlab-ci-runner. Es empfiehlt sich auch, den viel zu langen Dateinamen abzukürzen, um manuelle Tests zu erleichtern. Anschließend wird der Runner mit dem GitLab-System registriert - hierzu wird das Token des entsprechenden Projekts hinterlegt, damit der Runner automatisch verwendet wird. Anschließend wird der Runner als Windows-Dienst installiert und gestartet:

 1c:\gitlab-ci-runner> gitlab-ci-runner.exe register
 2 Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/ci):
 3 http://gitlab.localdomain.loc/ci
 4 Please enter the gitlab-ci token for this runner:
 5 xxx
 6 Please enter the gitlab-ci description for this runner:
 7 [windows]: windows.localdomain.loc
 8 Please enter the gitlab-ci tags for this runner (comma separated):
 9 win,python
10 INFO[0035] 7ab95543 Registering runner... succeeded
11 Please enter the executor: ssh, shell, parallels, docker, docker-ssh:
12 shell
13 INFO[0143] Runner registered successfully.
14c:\gitlab-ci-runner> gitlab-ci-runner.exe install
15c:\gitlab-ci-runner> gitlab-ci-multi-runner.exe start

Integration in GitLab

Damit GitLab die Code-Änderungen an die Runners übergibt, muss die .gitlab-ci.yml-Datei erstellt werden. Wie schon im letzten Artikel bemerkt, steuert diese Datei wesentlich das Verhalten der Runner. Das folgende Skript führt automatisch bei jeder Code-Änderung im master-Zweig ein PowerShell-Skript zur Übersetzung aus.

1build:
2  script:
3    - "powershell.exe -File build_binaries.ps1"

Der Quellcode des PowerShell-Skripts sieht wie folgt aus:

1$folders= ls | where {$_.mode -match "d"}
2foreach($folder in $folders)
3{
4  Write-Host "Moving to '$folder'"
5  cd $folder
6  Write-Host "Building binary by executing: pyinstaller.exe -F -y $folder.py"
7  pyinstaller.exe -F -y "$folder.py"
8  cd ..
9}

Das Skript ist recht simpel, es wechselt in jeden Unterordner und übersetzt dort ein gleichnamiges Python-Skript.

Ausblick

In diesem Setup gibt es noch einige Dinge, die man optimieren könnte - beispielsweise:

  • Erstellung nur auf bestimmten Knoten (z. B. anhand weiterer Abhängigkeiten oder Hardware-Ressourcen)
  • Anschließende Funktionsprüfung anhand definierter Tests, beispielsweise mit unittest
  • Automatischer Upload der übersetzten Binärdateien mithilfe von Artefakten

Insbesondere über das Thema Artefakte werde ich Zukunft sicherlich noch berichten. 🙂

Übersetzungen: