Python-Wheels am Cloud-Mac prüfen: Tags und native Bibliotheken
Du entwickelst unter Windows an einem Python-Projekt und lässt ein Paket mit nativen Erweiterungen auf einem Cloud-Mac bauen. Laut Protokoll wurde das Wheel erfolgreich erstellt. Bei der Installation durch einen Kollegen treten trotzdem Architekturkonflikte auf oder eine dynamische Bibliothek wird nicht gefunden. Entscheidend ist meist nicht, ob sich das Archiv erstellen lässt, sondern was es enthält, wohin seine Abhängigkeiten verweisen und ob die Tests unbemerkt auf das Quellverzeichnis zurückgreifen.
Ausgangspunkt ist hier ein bestehendes Projekt mit pyproject.toml. Darauf aufbauend führen wir drei Abnahmeprüfungen durch: Tags kontrollieren, native Bibliotheken untersuchen und die Installation in einer isolierten Umgebung testen. Das Hochladen in ein Repository gehört nicht dazu. Ebenso wenig gilt ein erfolgreicher lokaler Test als Nachweis für die Kompatibilität mit allen Systemen.
1. Interpreter und Prüfumfang festlegen
Führe die folgenden Befehle im Stammverzeichnis des Projekts auf dem entfernten Mac aus. Verwende einen bereits installierten Python-Interpreter, der die Projektanforderungen erfüllt. Aktualisiere ihn nicht nebenbei während der Abnahme.
python3 -m venv .wheel-build
.wheel-build/bin/python -m pip --version
.wheel-build/bin/python -c \
'import platform, sys; print(sys.version); print(platform.machine()); print(sys.executable)'
sw_vers
xcodebuild -version
Speichere die Ausgabe zusammen mit der Commit-ID. Interpreterversion, Prozessarchitektur und Betriebssystemversion gehören zu den Rahmenbedingungen der Abnahme. Nur weil der Rechner Apple Silicon verwendet, läuft der aktuelle Python-Prozess nicht zwingend als arm64-Prozess.
Lege fest, welche Python-Version und welche minimale macOS-Version du unterstützen möchtest und ob native Erweiterungen enthalten sind. Auch reine Python-Pakete können von nativen Paketen abhängen. Es reicht deshalb nicht, nur das eigene Paket zu prüfen.
Der Plattform-Tag im Dateinamen eines Wheels ist eine Kompatibilitätsangabe, kein Konverter. Wenn du eine arm64-Datei in universal2 umbenennst, erhält die Binärdatei dadurch keine zusätzliche Architektur.
2. Mit einem eindeutig zugeordneten Build beginnen
Stelle zunächst sicher, dass in dist keine Pakete aus einem früheren Build liegen. Erzeuge erst danach die Datei, die du jetzt abnehmen möchtest. Das Build-Backend und seine Version sollten über den im Projekt vorgesehenen Mechanismus zur Versionsbegrenzung festgelegt sein. Die standardmäßige Build-Isolation fixiert nicht automatisch sämtliche Build-Abhängigkeiten.
mkdir -p dist
find dist -maxdepth 1 -name '*.whl' -print
.wheel-build/bin/python -m pip wheel \
--no-deps --wheel-dir dist .
Falls die erste Prüfung alte Dateien auflistet, verschiebe sie vorher in ein separates Archivverzeichnis. Prüfe alte und neue Artefakte nicht gemeinsam. --no-deps bedeutet lediglich, dass keine Wheels für Laufzeitabhängigkeiten erstellt werden. Das Herunterladen von Abhängigkeiten während des Builds wird dadurch nicht verhindert.
Die folgende Prüfung verlangt, dass das Verzeichnis genau ein Wheel enthält, und gibt dessen interne Metadaten aus:
.wheel-build/bin/python - <<'PY'
from pathlib import Path
from zipfile import ZipFile
wheels = list(Path("dist").glob("*.whl"))
if len(wheels) != 1:
raise SystemExit("Expected exactly one wheel")
wheel = wheels[0]
print(wheel.name)
with ZipFile(wheel) as archive:
metadata = [
name for name in archive.namelist()
if name.endswith(".dist-info/WHEEL")
]
if len(metadata) != 1:
raise SystemExit("Invalid wheel metadata")
print(archive.read(metadata[0]).decode("utf-8"))
PY
Prüfe, ob Tag und die Angaben im Dateinamen zur vorgesehenen Zielumgebung passen. py3-none-any wird üblicherweise für plattformunabhängige Pakete verwendet. Enthält das Paket dennoch native ausführbare Dateien, musst du die Paketierung erneut prüfen, statt dem Tag ungeprüft zu vertrauen.
3. Unbeabsichtigte Verweise auf Bibliotheken des Build-Rechners aufspüren
Entpacke das Wheel in ein temporäres Verzeichnis und untersuche die Erweiterungsmodule und dynamischen Bibliotheken. Der folgende Code dient nur der Prüfung; er führt keine Programme aus dem Paket aus:
inspect_dir="$(mktemp -d)"
.wheel-build/bin/python - "$inspect_dir" <<'PY'
from pathlib import Path
from zipfile import ZipFile
import sys
wheel, = Path("dist").glob("*.whl")
with ZipFile(wheel) as archive:
archive.extractall(sys.argv[1])
PY
find "$inspect_dir" -type f \
\( -name '*.so' -o -name '*.dylib' \) \
-exec file {} \; -exec otool -L {} \;
Verwende dieses Verfahren nur für vertrauenswürdige Artefakte deines eigenen Projekts. Entpacke Archive unbekannter Herkunft nicht einfach in das Arbeitsverzeichnis.
Achte besonders auf Verweise in Benutzerverzeichnisse, Build-Arbeitsverzeichnisse und Installationspräfixe lokaler Paketmanager. Solche absoluten Pfade existieren auf einem anderen Rechner häufig nicht. Pfade zu Systembibliotheken sind dagegen nicht pauschal falsch. Auch @rpath und @loader_path sind nicht automatisch korrekt: Prüfe zusätzlich, welche Bibliotheken tatsächlich mitgeliefert werden und wie deren Ladepfade konfiguriert sind.
Nach jeder Änderung musst du das Wheel neu bauen und erneut prüfen. Korrigiere nicht nur Dateien im entpackten Verzeichnis, um anschließend das unveränderte Original-Wheel auszuliefern. Lösche nach Abschluss der Prüfung das dafür angelegte temporäre Verzeichnis.
4. Die Installation außerhalb des Quellverzeichnisses testen
Lege zunächst das Wheel und sämtliche Laufzeitabhängigkeiten in einem separaten Verzeichnis bereit. Dieser Schritt kann auf das Netzwerk zugreifen. Die Abhängigkeitsversionen sollten durch die Release-Vorgaben des Projekts begrenzt werden. Speichere außerdem eine abschließende Liste der heruntergeladenen Dateien.
mkdir -p wheelhouse
.wheel-build/bin/python -m pip download \
--only-binary=:all: --dest wheelhouse dist/*.whl
Gibt es für eine Abhängigkeit kein passendes Binärpaket, muss dieser Schritt fehlschlagen. Weiche nicht stillschweigend auf einen Build aus dem Quellcode aus, denn damit würdest du das Prüfziel verändern.
Die folgenden Befehle erstellen eine Umgebung außerhalb des Quellbaums. Gib bei der Ausführung den tatsächlichen Importnamen des Projekts ein. Er kann vom Namen des Distributionspakets abweichen:
read -r -p "Import module: " module
wheelhouse="$(pwd)/wheelhouse"
wheel="$(find "$(pwd)/dist" -maxdepth 1 -name '*.whl' -print)"
sandbox="$(mktemp -d)"
python3 -m venv "$sandbox/venv"
(
cd "$sandbox"
./venv/bin/python -m pip install \
--no-index --find-links "$wheelhouse" "$wheel"
./venv/bin/python -m pip check
./venv/bin/python -I - "$module" <<'PY'
import importlib
import sys
module = importlib.import_module(sys.argv[1])
print(getattr(module, "__file__", None))
print(list(getattr(module, "__path__", [])))
PY
)
Prüfe, ob der Importpfad zur neuen Umgebung gehört und nicht zum Quellbaum. pip check kontrolliert nur die deklarierten Abhängigkeitsbeziehungen. Es prüft weder das Laden nativer Bibliotheken noch führt es Funktionen aus, die erst bei Bedarf geladen werden. Führe deshalb zusätzlich einen realistischen Verarbeitungsschritt aus, der das Erweiterungsmodul tatsächlich aufruft, etwa die Verarbeitung eines kleinen, fest vorgegebenen Testdatensatzes mit einer Assertion für das erwartete Ergebnis.
5. Abnahmekriterien im Release-Protokoll dokumentieren
Bewahre für jede Abnahme mindestens die Commit-ID, die Interpreterinformationen, den Wheel-Dateinamen, den Hashwert, die Abhängigkeitsliste und das Ergebnis des fachlichen Smoke-Tests auf. Ermittle den Hash mit shasum -a 256 dist/*.whl, damit sichergestellt ist, dass du genau die zuvor getestete Datei auslieferst.
Gehe bei Fehlern schichtweise vor: Stimmen die Tags nicht, prüfe das Build-Ziel. Fehlen dynamische Bibliotheken, prüfe die Zusammenstellung der Abhängigkeiten. Funktioniert der Import nur im Quellverzeichnis, kontrolliere die Regeln zur Paketerkennung und die Deklarationen der mitzuliefernden Ressourcen. Kaschiere fehlende Dateien nicht durch zusätzliche Suchpfade.
Wiederhole Installation und Ausführung abschließend auf der ältesten als unterstützt angegebenen Betriebssystemversion sowie mit den übrigen Zielinterpretern. Ein erfolgreicher Test auf einem neueren System belegt nur, dass die aktuelle Kombination funktioniert. Den geprüften Umfang klar zu dokumentieren ist verlässlicher, als das Paket mit einem weiter gefassten Tag zu versehen.
Häufig gestellte Fragen
Ergibt ein Build auf Apple Silicon automatisch universal2?
Nein. Die betreffenden nativen Binärdateien müssen arm64 und x86_64 enthalten. Beide Ausführungswege müssen geprüft werden; Umbenennen genügt nicht.
Warum muss der Import außerhalb des Quellbaums erfolgen?
Lokale Module können fehlende Dateien im Wheel verdecken. Installiere es in einer frischen virtuellen Umgebung in einem temporären Verzeichnis und prüfe den Import isoliert.
Aufgaben auf einem exklusiven Mac in der Cloud erledigen
Mieten Sie einen exklusiven physischen Mac mini in der Cloud und wählen Sie Standort und Laufzeit passend zu Ihrem Arbeitsalltag. Nach der Bereitstellung finden Sie die Verbindungsdaten in der Konsole. Bei Fragen können Sie dort ein Supportticket erstellen.
Cloud-Mac-Tarife ansehen