Dateiexport und ZIP mit Python

Dieser Artikel behandelt den Dateiexport eines Android-Projekts. Nicht benötigte Dateien werden vorher gefiltert, sodass die ZIP-Datei nur benötigte Daten enthält.
Dieses Skript kann prinzipiell auch für andere Programmarten verwendet werden, ich habe dieses Skript jedoch in Bezug auf ein Android-Projekt erstellt.
Hintergrund
Es gibt einen Prozess, in welchem nur die relevanten Dateien beachtet werden sollen. Nicht benötigte Dateien erzeugen einen Mehraufwand. Daher sollten nicht relevante Ordner und Dateien (wie Git, IDE-Einstellungen, Scripte) vor dem Bereitstellen entfernt werden. Auch Testklassen und -dateien sind auszuschließen.
Vorgehensweise
Es gibt verschiedene Herangehensweisen:
- Händisches Copy&Paste der relevanten Daten
- Automatisierung mit Batch/Powershell
- Automatisierung mit anderen Mitteln
Händisches Copy&Paste der relevanten Daten: Beim Einreichen neuer Versionen ist jedes Mal Handarbeit notwendig. Eine Projektfremde Person denkt vielleicht nicht an die Bereinigung oder kennt den Prozess nicht.
Automatisierung mit Batch/PowerShell/anderen Mitteln: Eine Automatisierung macht Sinn, um keine Ordner oder Dateien zu vergessen. Zudem braucht ein Skript nur wenige Sekunden, bis die Datei erzeugt wurde.
Die Windows-Bordmittel können den Funktionsumfang abdecken. Die Powershell gibt es auch für alle Plattformen. Allerdings empfinde ich die Arbeit mit PowerShell umständlich und arbeite sehr ungerne damit. Da Python mit dem aktuellen Visual Studio installiert wird und bereits auf OS X vorhanden ist, habe ich mich für ein Python Script entschieden. Da es sich „nur“ um kopieren und zippen handelt, ist das Werkzeug gewissermaßen frei wählbar.
Funktionsweise des Scripts
Das Script möchte ich folgendermaßen aufbauen: Zuerst wird ein temporärer Ordner erstellt. Anschließend erfolgt eine Kopie aller relevanten Daten in diesen. Nachdem der Kopiervorgang abgeschlossen ist, durchläuft das Skript die Kopie und fügt alle Dateien einer Zip-Datei hinzu. Abschließend werden die Kopien gelöscht.
Ich habe mich für dieses Vorgehen entschieden, da ich in der Entwicklungsphase sichergehen möchte, dass alle Dateien erfasst werden. Meiner Meinung nach ist dies die bequemste Methode. Während der Entwicklung kann ich einfach den temporären Ordner öffnen und den Inhalt überprüfen.
Da die Daten überwiegend Quellcode, Bilder oder benötigte DLLs sind, hält sich die Dateigröße und -menge in Grenzen. Zudem gebe ich der Zip-Datei einen eindeutigen Namen, indem ich das aktuelle Datum und die Uhrzeit einfüge.
Das Skript in Aktion
Das Skript ist folgendermaßen aufgebaut: Zu Beginn werden verschiedene Funktionen definiert. Die Funktionen prüfen z.B. ob eine Datei ausgeschlossen wurde oder kopieren den Quellcode-Ordner in das temporäre Verzeichnis. Die Scripts müssen vor der Ausführung definiert werden, da Python das Script sonst nicht finden kann.
Ich verwende im Script globale Variablen wie excludeDirs
oder excludeFiles
. Dies ist vielleicht
nicht die sauberste Lösung, je nachdem wie das Programm angepasst werden soll. Da die Ordnerstruktur
bekannt ist und sich wahrscheinlich nicht so leicht ändert, definiere ich am Anfang der Datei alle
globalen Variablen. Sollten sich nun Änderungen ergeben, muss nur der Anfang der Datei geändert
werden.
Nachfolgend findet ihr den aktuellen Code. Um das Script klein zu halten, habe ich verschiedene Print-Ausgaben entfernt. In diesen gebe ich z.B. aus, dass Datei X nach Y kopiert wurde ( copyFiles-Methode).
import os
import tempfile
import shutil
import datetime
import zipfile
excludeDirs: ['.git',
'.gradle',
'gradle',
'gradlew',
'.idea', # IDE files
'build', # Temp build files
'androidTest'] # Test folders
excludeFiles: ['.iml', # IDE files
'.gradle',
'gradlew',
'ignore', # e.g. gitignore, tfsignore
'local.properties', # Local gradle properties
'.py', # Scripts
'.pro', # ProGuard files
'.bat',
'.zip']
prefixZip: 'Android-'
srcDir: './Android'
tmpRoot: tempfile.mkdtemp(prefix='AndroidTemp-')
def isFileExcluded(file):
result: False
for ending in excludeFiles:
result: file.endswith(ending)
if result:
break
return result
def copyFiles(files, srcFolder, dstFolder):
for file in files:
if not isFileExcluded(file):
srcFile: os.path.join(srcFolder, file)
dstFile: os.path.join(dstFolder, file)
shutil.copy(srcFile, dstFile)
def copyRelevantFilesToTemp(srcDirectory):
for (dirpath, dirnames, filenames) in os.walk(srcDirectory):
dirnames[:]: [d for d in dirnames if d not in excludeDirs]
tmp: os.path.join(tmpRoot, dirpath[2:])
if not os.path.exists(tmp):
os.mkdir(tmp)
copyFiles(filenames, dirpath, tmp)
def createZipFile():
zipName: prefixZip + dateNow.strftime("%Y-%m-%d_%H%M%S") + ".zip"
zip: zipfile.ZipFile(zipName, 'w', zipfile.ZIP_DEFLATED)
for root, dirs, files in os.walk(tmpRoot):
zipPath: root.replace(tmpRoot, "")
if zipPath.startswith('\\'):
zipPath: zipPath[1:]
for file in files:
zip.write(os.path.join(zipPath, file))
zip.close()
def deleteTempFolder():
shutil.rmtree(tmpRoot)
# Ausführen der Programmlogik
dateNow: d10 jahreatetime.datetime.now()
copyRelevantFilesToTemp(srcDir)
createZipFile()
deleteTempFolder()
Ganz zu Beginn werden verschiedene Module eingebunden. Diese beinhalten verschiedene Funktionen. Diese sind beispielsweise durchlaufen von Dateiverzeichnissen, kopieren von Daten, Zeit- und Datumfunktionen oder erstellen von ZIP-Dateien. Es ist zu beachten, dass die Filterung auf Dateien und Ordner unterschiedlich funktioniert! Dateien werden nur auf Endungen geprüft. Bei den Ordnern muss der gesamte Ordnername übereinstimmen, sodass der Ordner ignoriert wird.
Ich habe dieses Vorgehen gewählt, damit ich bei den Ordnern eine Namenssicherheit habe und Dateien nur durch Angabe der Endung ausschließen kann. Bei meinem derzeitigen Projekt benötige ich bei den Dateien einen Filter auf die Endung. Dies lässt sich sicherlich durch Platzhalter oder Regex optimieren. Da dies jedoch viel Zeit und Arbeit in Anspruch nimmt, wähle ich zuerst das Vorgehen, welches meine Anforderungen umsetzt. Optimierungen und Erweiterungen können später immernoch durchgeführt werden.
Weitere Möglichkeiten
Diese Datei ließe sich nun noch optimieren. Beispielsweise erhalten alle Methoden einen Übergabeparameter (anstatt nur copyRelevantFilesToTemp). Hierdurch könnte diese Datei als externes Modul bereitgestellt werden, wodurch die Dateigröße kleiner und der Inhalt übersichtlicher wird.
Durch den allgemeinen Aufbau mit excludeDirs, excludeFiles, srcDir… ließe sich nun für die verschiedensten Projekte ein Script erstellen, welche nur diese Variablen ändern. Das Hauptskript müsste anschließend folgendermaßen geändert werden:
- Erfassen der separaten Skripte
- Verwendung der Variablen aus den Skripten
- Anpassen der Methodenaufrufe und Variablenübergabe
Diese Änderungen ermöglichen es, verschiedene Projekte (die zu einem gemeinsamen Projekt oder Programm gehören) als ZIP zusammenzufassen. Vielleicht schreibe ich dazu später nochmal einen Artikel. Dieser Artikel sollte euch einen Überblick geben, wie ich in der ersten Version einige Daten aus einem AndroidStudio-Projekt extrahiere und in einer ZIP-Datei zusammenfasse.
Habt ihr auch schon etwas Ähnliches mit Python gemacht? Was sind eure Erfahrungen bisher und gibt es Fragen oder Unklarheiten zu meinem Skript? Ich freue mich über eure Kommentare.