Python Distutils-SIG: Design Proposal
Python Distutils-SIG
(Voraussetzungen: Bitte lesen Sie die vorgeschlagene Schnittstelle, bevor Sie versuchen, dieses Designdokument durchzuarbeiten; es ist sehr stark eine Fortsetzung des Schnittstellendokuments.)
Design Proposal
Der Blickwinkel von Distutils
setup.py muss nur ein Modul importieren: distutils.core. Dieses Modul ist verantwortlich für das Parsen aller Kommandozeilenargumente an setup.py (obwohl die Interpretation von Optionen über die verschiedenen Distutils-Befehle und möglicherweise das Client-setup.py verteilt ist). Es kümmert sich auch um den Empfang der Steuerung von setup.py und leitet sie entsprechend an die Distutils-Befehle weiter. Am wichtigsten ist, dass distutils.core die Distribution-Klasse definiert, die das Herz und die Seele von Distutils ist. Der Client (setup.py) existiert hauptsächlich, um Attribute für eine Distribution-Instanz bereitzustellen, und alle Distutils-Befehle arbeiten mit dieser Instanz. distutils.core definiert auch die Command-Klasse, die sich bei der Implementierung von Distutils-Befehlen als nützlich erweist.
Apropos Distutils-Befehle: Jeder wird als Python-Modul implementiert, z.B. der build-Befehl wird durch das Modul distutils.build implementiert. Jedes Befehlsmodul muss eine Klasse definieren, die ebenfalls nach dem Befehl benannt ist – z.B. distutils.build.Build. Diese Befehlsklassen erben von der Command-Klasse, die (zumindest) eine Möglichkeit bietet, mit befehlspezifischen Optionen umzugehen. (Command stellt einen Konstruktor bereit, der eine Distribution-Klasse und eine optionale Liste von Argumenten für diesen Befehl entgegennimmt und die Argumentliste durch Inspektion von getopt-ähnlichen Optionsspezifizierern in der von Command abgeleiteten Instanz parst.) Jede Befehlsklasse muss eine Methode run bereitstellen, die die Informationen in der Distribution-Instanz und die Befehlsoptionen verwendet, um "ihre Sache zu tun". Gut geschriebene Befehlsklassen werden diese Aufgabe in mehrere gut definierte (und dokumentierte) Methoden aufteilen, so dass der Client setup.py von einer Distutils-Befehlsklasse erben und spezifische Verhaltensweisen überschreiben kann. Das bedeutet auch, dass die Distribution-Klasse eine Möglichkeit haben muss, überschriebene Befehlsklassen an den Hauptdispatcher zu kommunizieren.
Der Blickwinkel des Clients
Wie bereits erwähnt, muss der Client (setup.py) nur distutils.core importieren – alles andere Distutils-bezogene wird von diesem Kernmodul übernommen. Der Client benötigt jedoch eine Möglichkeit, seine spezifischen Optionen an den Distutils-Kern (und an die Befehlsmodule) zu kommunizieren.
Dafür gibt es zwei mögliche Schemata: eines kurz und praktisch (aber nicht sehr erweiterbar) und das andere etwas wortreich und umständlich (aber objektorientierter und erweiterbarer). Es gibt keinen Grund, warum wir nicht beides haben können; die praktische Schnittstelle könnte einfach ein Wrapper um die vollwertige Schnittstelle für die vielen Modulverteilungen sein, die keine umfangreichen Anpassungen benötigen.
Zuerst hier ein Beispiel für die einfache Schnittstelle, verwendet für eine Modulverteilung mit einem einzelnen "reinen Python"-Modul (mymod.py).
from distutils.core import setup
setup (name = "mymod",
version = "1.2",
author = "Greg Ward <gward@cnri.reston.va.us>",
description = "A very simple, one-module distribution")
Beachten Sie, dass wir mymod.py nirgendwo explizit auflisten: Distutils geht davon aus, dass es sich um eine Ein-Modul-Verteilung handelt, die nach ihrem einzigen Modul benannt ist (mymod).
Diejenigen, die gerne Unterklassen definieren, werden es vielleicht vorziehen, dies anders zu formulieren
from distutils.core import Distribution, setup
class MyDistribution (Distribution):
name = "mymod"
version = "1.2",
author = "Greg Ward <gward@cnri.reston.va.us>",
description = "A very simple, one-module distribution")
setup (distclass = MyDistribution)
Das ist für eine kleine Verteilung übertrieben: Wir definieren eine neue Klasse nur, um Attributwerte bereitzustellen, wenn distutils.core.setup hauptsächlich existiert, damit wir dies sowieso tun können. Dennoch werden OO-Puristen dies mögen – und zweifellos wird es Zeiten geben, in denen der Client Verhalten überschreiben muss, nicht nur Daten, und die OO-Schnittstelle notwendig sein wird.
Und komplexere Modulverteilungen mit vielen zu anpassenden Attributen sind möglicherweise leichter zu lesen/zu warten, wenn die Dinge so aufgeteilt sind. Betrachten Sie eine Verteilung mit zwei reinen Python-Modulen (mymod und my_othermod) und einer C-Erweiterung (myext); die C-Erweiterung muss mit zwei zusätzlichen C-Dateien und einer C-Bibliothek verknüpft werden. Ach ja, diese Verteilung erfordert Python 1.5 und jede Version des re-Moduls (ignoriert die Tatsache, dass das eine das andere normalerweise impliziert)
from distutils.core import Distribution, setup
class MyDistribution (Distribution):
name = "mydist",
version = "1.3.4",
author = "Greg Ward <gward@cnri.reston.va.us>"
description = """\
This is an example module distribution. It provides no useful code,
but is an interesting example of the Distutils in action."""
# Dependencies
requires = { 'python': '1.5', # I like class-based exceptions
're': '*', # and I love Perl-style regexps! ;-)
}
# Actual files that need to be processed and installed in some form
py_modules = ['mymod.py', 'my_othermod.py'],
ext_modules = {'myext.c':
{'other_c': ['extra1.c', 'extra2.c'],
'c_libraries': ['mylib']}
}
setup (distclass = MyDistribution)
Ein paar Dinge zu beachten
- Ich habe keine Angst, tief verschachtelte Datenstrukturen zu verwenden; wenn Sie Python-Module schreiben und verteilen, sollte das kein Problem sein!
- jedes Attribut hat einen bestimmten Typ (String, Liste, Wörterbuch, ...)
- die Attribute mit komplexen Typen (insbesondere Wörterbücher) haben eine bekannte und gut dokumentierte interne Struktur, z.B.
"""ext_modules is a hash mapping names of C source files (each containing a Python extension module) to a nested hash of information about how to build that module. The allowed keys to this nested hash are: - other_c: other C files that must be compiled and linked with the main C file to create the module - c_libraries: C libraries that must be included in the link ... """
Zweifellos hätten die verschachtelten Hashes von ext_modules mehr Optionen, und zweifellos hätten andere Distribution-Attribute eine komplexe, dokumentierte Struktur.
Schließlich muss die Liste aller Distribution-Attribute bekannt und gut dokumentiert sein! Diese scheinen in einige breite Kategorien zu fallen. Hier ist ein erster Versuch einer Liste
- Distributions-Metadaten
nameversionauthordescription
- Abhängigkeiten
requires
- Zu verarbeitende und zu installierende Dateien
py_modulesext_modulesdoc_files
- Build-Verzeichnisse (standardmäßig alle unter
./blib)build_lib– wo plattformunabhängige Bibliotheksdateien abgelegt werden sollenbuild_platlib– wo plattformabhängige Bibliotheksdateien abgelegt werden sollenbuild_exe– wo ausführbare Programme (d.h. Skripte) abgelegt werden sollenbuild_html– wo verarbeitete Dokumentation (HTML) abgelegt werden soll
- Installationsverzeichnisse (standardmäßig unter
sysconfig.LIBDEST)install_libinstall_platlibinstall_exeinstall_html
Der Blickwinkel von Distutils im Rückblick
Zusammenfassend lässt sich sagen, dass wir durchgehen, was passiert, wenn der Benutzer setup.py ausführt. Ob setup.py in der einfachen (Funktionsaufruf) oder allgemeinen (Unterklasse definieren) Form geschrieben ist, spielt keine große Rolle, daher werde ich die Dinge nicht in zwei Ströme aufteilen.
setup.pyimportiertdistutils.core- Der Startcode von
distutils.coreparst Kommandozeilenargumente: verarbeitet globale Optionen, die ihm bekannt sind, und speichert den Rest für den Client (setup.py) zur Verarbeitung; ermittelt die Befehle und Optionen für jeden Befehl und speichert sie alle für die spätere Verarbeitung setup.pyruftdistutils.core.setupauf (möglicherweise mit einem Argumentdistclass, das eine Unterklasse vonDistributionangibt, wahrscheinlich mit vielen anderen benannten Argumenten, die verschiedene Attribute für dieDistribution-Instanz angeben)distutils.core.setupinstanziiertDistribution(oder die vom Client bereitgestellte Unterklasse) und verwendet ihre Argumente (außerdistclass), um Attribute dieser Instanz zu überschreibendistutils.core.setuplädt das Befehlsmodul (z.B.distutils.build)distutils.core.setupermittelt die Befehlsklasse (normalerweise einfach nach dem Befehl benannt, z.B.distutils.build.Build, aber möglicherweise eine vom Client als eines der Attribute derDistribution-Instanz bereitgestellte Klasse) und instanziiert sie- Der Konstruktor der Befehlsklasse nimmt als Argumente die
Distribution-Instanz und alle spezifischen Kommandozeilenargumente für diesen Befehl auf der Kommandozeile vonsetup.pyentgegen - Der Konstruktor der Befehlsklasse parst seine Optionen, um einige Instanzattribute festzulegen/zu überschreiben
distutils.core.setupruft die Methoderunauf dem Befehlsobjekt auf- diese Methode tut, was auch immer der Befehl tun soll: Module erstellen, Dokumentation verarbeiten, Dateien installieren usw.
distutils.core.setupermittelt die nächste Befehlsklasse (falls mehrere Befehle angegeben wurden) und fährt wie zuvor fort
