webentwicklung-frage-antwort-db.com.de

Erstellen und Verteilen eines Python/Cython-Pakets, das von libFoo.so eines Drittanbieters abhängt

Ich habe ein Python-Modul geschrieben, das von einigen C-Erweiterungen abhängt. Diese C-Erweiterungen hängen wiederum von mehreren kompilierten C-Bibliotheken ab. Ich möchte dieses Modul gebündelt mit allen Abhängigkeiten verteilen können.

Ich habe ein minimales Beispiel zusammengestellt ( es kann auf GitHub in seiner Gesamtheit gefunden werden ).

Die Verzeichnisstruktur ist:

$ tree .
.
├── README.md
├── poc
│   ├── __init__.py
│   ├── cython_extensions
│   │   ├── __init__.py
│   │   ├── cvRoberts_dns.c
│   │   ├── cvRoberts_dns.h
│   │   ├── helloworld.c
│   │   ├── helloworld.pxd
│   │   ├── helloworld.pyx
│   │   ├── test.c
│   │   └── test.h
│   ├── do_stuff.c
│   └── do_stuff.pyx
└── setup.py

setup.py erstellt die Erweiterungen und Links für die erforderlichen Bibliotheken (libsundials_cvode, libsundials_nvectorserial in diesem Fall):

from setuptools import setup, find_packages
from setuptools.extension import Extension
from Cython.Build import cythonize


ext_module_dostuff = Extension(
    'poc.do_stuff',
    ['poc/do_stuff.pyx'],
)

ext_module_helloworld = Extension(
    'poc.cython_extensions.helloworld',
    ['poc/cython_extensions/helloworld.pyx', 'poc/cython_extensions/test.c', 'poc/cython_extensions/cvRoberts_dns.c'],
    include_dirs = ['/usr/local/include'],
    libraries = ['m', 'sundials_cvodes', 'sundials_nvecserial'],
    library_dirs = ['/usr/local/lib'],
)

cython_ext_modules = [
   ext_module_dostuff,
   ext_module_helloworld
]


setup (
  name = "poc",
  ext_modules = cythonize(cython_ext_modules),
  packages=['poc', 'poc.cython_extensions'],
)

Das ist alles in Ordnung und gut, aber es erfordert, dass der Endbenutzer zuerst Sonnenuhren installiert (und im konkreten Fall auch einige andere Bibliotheken, die extrem schwierig in Betrieb zu nehmen sind).

Im Idealfall möchte ich dies nur auf Entwicklungscomputern einrichten, eine Distribution mit den entsprechenden gemeinsam genutzten Bibliotheken erstellen und eine Art Bundle ausliefern können.

Angesichts der verschiedenen Tutorials, Beispiele und SO Posts, die ich bisher gefunden habe. Ich muss glauben, dass ich auf dem richtigen Weg bin. Es gibt jedoch eine Art letzten Schritt, den ich einfach nicht scherze.

Jede Hilfe wird gebeten :-).

12
Sevenless

Wie Sie wahrscheinlich wissen, wird empfohlen, ein Python-Modul mit kompilierten Komponenten im Format wheel zu verteilen. Es scheint keine plattformübergreifende Standardmethode zu geben, mit der native Bibliotheken von Drittanbietern in einem Rad gebündelt werden können. Es gibt jedoch plattformspezifische Tools für diesen Zweck.

Verwenden Sie unter Linux auditwheel.

auditwheel ändert eine vorhandene Linux-Wheel-Datei, um Bibliotheken von Drittanbietern hinzuzufügen, die nicht im Standard " manylinux " enthalten sind. Hier ist eine exemplarische Vorgehensweise für die Verwendung mit Ihrem Projekt bei einer Neuinstallation von Ubuntu 17.10:

Installieren Sie zunächst die grundlegenden Python-Entwicklungstools und die Drittanbieter-Bibliothek mit ihren Headern:

[email protected]:~# apt-get install cython python-pip unzip
[email protected]:~# apt-get install libsundials-serial-dev

Dann baue dein Projekt in eine Wheel-Datei:

[email protected]:~# cd cython-example/
[email protected]:~/cython-example# python setup.py bdist_wheel
[...]
[email protected]:~/cython-example# cd dist/
[email protected]:~/cython-example/dist# ll
total 80
drwxr-xr-x 2 root root  4096 Nov  8 11:28 ./
drwxr-xr-x 7 root root  4096 Nov  8 11:28 ../
-rw-r--r-- 1 root root 70135 Nov  8 11:28 poc-0.0.0-cp27-cp27mu-linux_x86_64.whl
[email protected]:~/cython-example/dist# unzip -l poc-0.0.0-cp27-cp27mu-linux_x86_64.whl
Archive:  poc-0.0.0-cp27-cp27mu-linux_x86_64.whl
  Length      Date    Time    Name
---------  ---------- -----   ----
    62440  2017-11-08 11:28   poc/do_stuff.so
        2  2017-11-08 11:28   poc/__init__.py
   116648  2017-11-08 11:28   poc/cython_extensions/helloworld.so
        2  2017-11-08 11:28   poc/cython_extensions/__init__.py
       10  2017-11-08 11:28   poc-0.0.0.dist-info/DESCRIPTION.rst
      211  2017-11-08 11:28   poc-0.0.0.dist-info/metadata.json
        4  2017-11-08 11:28   poc-0.0.0.dist-info/top_level.txt
      105  2017-11-08 11:28   poc-0.0.0.dist-info/WHEEL
      167  2017-11-08 11:28   poc-0.0.0.dist-info/METADATA
      793  2017-11-08 11:28   poc-0.0.0.dist-info/RECORD
---------                     -------
   180382                     10 files

Die Raddatei kann jetzt lokal installiert und getestet werden:

[email protected]:~/cython-example/dist# pip install poc-0.0.0-cp27-cp27mu-linux_x86_64.whl
[...]
[email protected]:~/cython-example/dist# python -c "from poc.do_stuff import hello; hello()"
hello cython
0.841470984808
trying to load the sundials program

3-species kinetics problem

At t = 2.6391e-01      y =  9.899653e-01    3.470564e-05    1.000000e-02
    rootsfound[] =   0   1
At t = 4.0000e-01      y =  9.851641e-01    3.386242e-05    1.480205e-02
[...]

Jetzt installieren wir das Tool auditwheel. Es erfordert Python 3, kann aber Räder für Python 2 oder 3 verarbeiten.

[email protected]:~/cython-example/dist# apt-get install python3-pip
[email protected]:~/cython-example/dist# pip3 install auditwheel

auditwheel verwendet ein anderes Tool namens patchelf, um seine Arbeit zu erledigen. Leider fehlt die in Ubuntu 17.10 enthaltene Version von patchelfein Bugfix ohne den auditwheel nicht funktioniert . Also müssen wir es aus dem Quellcode erstellen (Skript aus dem manylinux Docker-Image ):

[email protected]:~# apt-get install autoconf
[email protected]:~# PATCHELF_VERSION=6bfcafbba8d89e44f9ac9582493b4f27d9d8c369
[email protected]:~# curl -sL -o patchelf.tar.gz https://github.com/NixOS/patchelf/archive/$PATCHELF_VERSION.tar.gz
[email protected]:~# tar -xzf patchelf.tar.gz
[email protected]:~# (cd patchelf-$PATCHELF_VERSION && ./bootstrap.sh && ./configure && make && make install)

Jetzt können wir überprüfen, welche Bibliotheken von Drittanbietern das Rad benötigt:

[email protected]:~/cython-example/dist# auditwheel show poc-0.0.0-cp27-cp27mu-linux_x86_64.whl

poc-0.0.0-cp27-cp27mu-linux_x86_64.whl is consistent with the
following platform tag: "linux_x86_64".

The wheel references external versioned symbols in these system-
provided shared libraries: libc.so.6 with versions {'GLIBC_2.4',
'GLIBC_2.2.5', 'GLIBC_2.3.4'}

The following external shared libraries are required by the wheel:
{
    "libblas.so.3": "/usr/lib/x86_64-linux-gnu/blas/libblas.so.3.7.1",
    "libc.so.6": "/lib/x86_64-linux-gnu/libc-2.26.so",
    "libgcc_s.so.1": "/lib/x86_64-linux-gnu/libgcc_s.so.1",
    "libgfortran.so.4": "/usr/lib/x86_64-linux-gnu/libgfortran.so.4.0.0",
    "liblapack.so.3": "/usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.7.1",
    "libm.so.6": "/lib/x86_64-linux-gnu/libm-2.26.so",
    "libpthread.so.0": "/lib/x86_64-linux-gnu/libpthread-2.26.so",
    "libquadmath.so.0": "/usr/lib/x86_64-linux-gnu/libquadmath.so.0.0.0",
    "libsundials_cvodes.so.2": "/usr/lib/libsundials_cvodes.so.2.0.0",
    "libsundials_nvecserial.so.0": "/usr/lib/libsundials_nvecserial.so.0.0.2"
}

In order to achieve the tag platform tag "manylinux1_x86_64" the
following shared library dependencies will need to be eliminated:

libblas.so.3, libgfortran.so.4, liblapack.so.3, libquadmath.so.0,
libsundials_cvodes.so.2, libsundials_nvecserial.so.0

Und erstelle ein neues Rad, das sie bündelt:

[email protected]u-17:~/cython-example/dist# auditwheel repair poc-0.0.0-cp27-cp27mu-linux_x86_64.whl
Repairing poc-0.0.0-cp27-cp27mu-linux_x86_64.whl
Grafting: /usr/lib/libsundials_nvecserial.so.0.0.2 -> poc/.libs/libsundials_nvecserial-42b4120e.so.0.0.2
Grafting: /usr/lib/libsundials_cvodes.so.2.0.0 -> poc/.libs/libsundials_cvodes-50fde5ee.so.2.0.0
Grafting: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.7.1 -> poc/.libs/liblapack-549933c4.so.3.7.1
Grafting: /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.7.1 -> poc/.libs/libblas-52fa99c8.so.3.7.1
Grafting: /usr/lib/x86_64-linux-gnu/libgfortran.so.4.0.0 -> poc/.libs/libgfortran-2df4b07d.so.4.0.0
Grafting: /usr/lib/x86_64-linux-gnu/libquadmath.so.0.0.0 -> poc/.libs/libquadmath-0d7c3070.so.0.0.0
Setting RPATH: poc/cython_extensions/helloworld.so to "$Origin/../.libs"
Previous filename tags: linux_x86_64
New filename tags: manylinux1_x86_64
Previous WHEEL info tags: cp27-cp27mu-linux_x86_64
New WHEEL info tags: cp27-cp27mu-manylinux1_x86_64

Fixed-up wheel written to /root/cython-example/dist/wheelhouse/poc-0.0.0-cp27-cp27mu-manylinux1_x86_64.whl
[email protected]:~/cython-example/dist# unzip -l wheelhouse/poc-0.0.0-cp27-cp27mu-manylinux1_x86_64.whl
Archive:  wheelhouse/poc-0.0.0-cp27-cp27mu-manylinux1_x86_64.whl
  Length      Date    Time    Name
---------  ---------- -----   ----
      167  2017-11-08 11:28   poc-0.0.0.dist-info/METADATA
        4  2017-11-08 11:28   poc-0.0.0.dist-info/top_level.txt
       10  2017-11-08 11:28   poc-0.0.0.dist-info/DESCRIPTION.rst
      211  2017-11-08 11:28   poc-0.0.0.dist-info/metadata.json
     1400  2017-11-08 12:08   poc-0.0.0.dist-info/RECORD
      110  2017-11-08 12:08   poc-0.0.0.dist-info/WHEEL
    62440  2017-11-08 11:28   poc/do_stuff.so
        2  2017-11-08 11:28   poc/__init__.py
   131712  2017-11-08 12:08   poc/cython_extensions/helloworld.so
        2  2017-11-08 11:28   poc/cython_extensions/__init__.py
   230744  2017-11-08 12:08   poc/.libs/libsundials_cvodes-50fde5ee.so.2.0.0
  7005072  2017-11-08 12:08   poc/.libs/liblapack-549933c4.so.3.7.1
   264024  2017-11-08 12:08   poc/.libs/libquadmath-0d7c3070.so.0.0.0
  2039960  2017-11-08 12:08   poc/.libs/libgfortran-2df4b07d.so.4.0.0
    17736  2017-11-08 12:08   poc/.libs/libsundials_nvecserial-42b4120e.so.0.0.2
   452432  2017-11-08 12:08   poc/.libs/libblas-52fa99c8.so.3.7.1
---------                     -------
 10206026                     16 files

Wenn wir die Bibliotheken von Drittanbietern deinstallieren, funktioniert das zuvor installierte Rad nicht mehr:

[email protected]:~/cython-example/dist# apt-get remove libsundials-serial-dev && apt-get autoremove
[...]
[email protected]:~/cython-example/dist# python -c "from poc.do_stuff import hello; hello()"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "poc/do_stuff.pyx", line 1, in init poc.do_stuff
ImportError: libsundials_cvodes.so.2: cannot open shared object file: No such file or directory

Aber das Rad mit den gebündelten Bibliotheken wird gut funktionieren:

[email protected]:~/cython-example/dist# pip uninstall poc
[...]
[email protected]:~/cython-example/dist# pip install wheelhouse/poc-0.0.0-cp27-cp27mu-manylinux1_x86_64.whl
[...]
[email protected]:~/cython-example/dist# python -c "from poc.do_stuff import hello; hello()"
hello cython
0.841470984808
trying to load the sundials program

3-species kinetics problem

At t = 2.6391e-01      y =  9.899653e-01    3.470564e-05    1.000000e-02
    rootsfound[] =   0   1
At t = 4.0000e-01      y =  9.851641e-01    3.386242e-05    1.480205e-02
[...]

Verwenden Sie unter OSX delocate.

delocate für OSX funktioniert anscheinend sehr ähnlich zu auditwheel. Leider habe ich keinen OSX-Rechner zur Verfügung, um eine exemplarische Vorgehensweise bereitzustellen.

Kombiniertes Beispiel:

Ein Projekt, das beide Tools verwendet, ist SciPy. Dieses Repository enthält trotz seines Namens den offiziellen SciPy-Erstellungsprozess für alle Plattformen, nicht nur für Mac. Vergleichen Sie insbesondere das Linux-Erstellungsskript (das auditwheel verwendet) mit dem OSX-Erstellungsskript (das delocate verwendet).

Um das Ergebnis dieses Vorgangs zu sehen, möchten Sie möglicherweise einige der SciPy-Räder von PyPI herunterladen und entpacken. Beispielsweise enthält scipy-1.0.0-cp27-cp27m-manylinux1_x86_64.whl Folgendes:

 38513408  2017-10-25 06:02   scipy/.libs/libopenblasp-r0-39a31c03.2.18.so
  1023960  2017-10-25 06:02   scipy/.libs/libgfortran-ed201abd.so.3.0.0

Während scipy-1.0.0-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl dies enthält:

   273072  2017-10-25 07:03   scipy/.dylibs/libgcc_s.1.dylib
  1550456  2017-10-25 07:03   scipy/.dylibs/libgfortran.3.dylib
   279932  2017-10-25 07:03   scipy/.dylibs/libquadmath.0.dylib
7
mhsmith

Um mhsmith 's exzellentes answer zu verbessern, sind hier die Schritte aufgeführt, die unter MacOS mit delocate ausgeführt werden:

  1. Installiere sundials, zum Beispiel mit Homebrew:

    $ brew install sundials
    
  2. Bauen Sie das Paket:

    $ python setup.py bdist_wheel
    
  3. Die Anhänger von auditwheel show/auditwheel repair sind delocate-listdeps/delocate-wheel. Analysieren Sie daher zunächst die resultierende Raddatei:

    $ delocate-listdeps --all dist/poc-0.0.0-cp27-cp27m-macosx_10_13_intel.whl
    /usr/lib/libSystem.B.dylib
    /usr/local/Cellar/sundials/2.7.0_3/lib/libsundials_cvodes.2.9.0.dylib
    /usr/local/Cellar/sundials/2.7.0_3/lib/libsundials_nvecserial.2.7.0.dylib
    
  4. Reparatur der Radfeile:

    $ delocate-wheel -v -w dist_fixed dist/poc-0.0.0-cp27-cp27m-macosx_10_13_intel.whl 
    Fixing: dist/poc-0.0.0-cp27-cp27m-macosx_10_13_intel.whl
    Copied to package .dylibs directory:
      /usr/local/Cellar/sundials/2.7.0_3/lib/libsundials_cvodes.2.9.0.dylib
      /usr/local/Cellar/sundials/2.7.0_3/lib/libsundials_nvecserial.2.7.0.dylib
    

Im Verzeichnis dist_fixed befindet sich das gebündelte Rad. Sie werden den Größenunterschied bemerken:

$ ls -l dist/ dist_fixed/
dist/:
total 72
-rw-r--r--  1 hoefling  wheel  36030 10 Nov 20:25 poc-0.0.0-cp27-cp27m-macosx_10_13_intel.whl

dist_fixed/:
total 240
-rw-r--r--  1 hoefling  wheel  120101 10 Nov 20:34 poc-0.0.0-cp27-cp27m-macosx_10_13_intel.whl

Wenn Sie deps für das gebündelte Rad auflisten, werden Sie feststellen, dass die benötigten Bibliotheken jetzt gebündelt sind (angegeben durch das Präfix @loader_path):

$ delocate-listdeps --all dist_fixed/poc-0.0.0-cp27-cp27m-macosx_10_13_intel.whl 
/usr/lib/libSystem.B.dylib
@loader_path/../.dylibs/libsundials_cvodes.2.9.0.dylib
@loader_path/../.dylibs/libsundials_nvecserial.2.7.0.dylib

Installieren des gebündelten Rades (beachten Sie, dass die gebündelten Bibliotheken korrekt installiert sind):

$ pip install dist_fixed/poc-0.0.0-cp27-cp27m-macosx_10_13_intel.whl 
Processing ./dist_fixed/poc-0.0.0-cp27-cp27m-macosx_10_13_intel.whl
Installing collected packages: poc
Successfully installed poc-0.0.0
$ pip show -f poc
Name: poc
Version: 0.0.0
Summary: UNKNOWN
Home-page: UNKNOWN
Author: UNKNOWN
Author-email: UNKNOWN
License: UNKNOWN
Location: /Users/hoefling/.virtualenvs/stackoverflow-py27/lib/python2.7/site-packages
Requires: 
Files:
  poc-0.0.0.dist-info/DESCRIPTION.rst
  poc-0.0.0.dist-info/INSTALLER
  poc-0.0.0.dist-info/METADATA
  poc-0.0.0.dist-info/RECORD
  poc-0.0.0.dist-info/WHEEL
  poc-0.0.0.dist-info/metadata.json
  poc-0.0.0.dist-info/top_level.txt
  poc/.dylibs/libsundials_cvodes.2.9.0.dylib
  poc/.dylibs/libsundials_nvecserial.2.7.0.dylib
  poc/__init__.py
  poc/__init__.pyc
  poc/cython_extensions/__init__.py
  poc/cython_extensions/__init__.pyc
  poc/cython_extensions/helloworld.so
  poc/do_stuff.so
2
hoefling

Ich würde vorschlagen, einen völlig anderen Ansatz zu wählen. Richten Sie eine Linux-Paketverwaltungsinfrastruktur ein. Unter Ubuntu/Debian könnte dies mit reprepro geschehen. https://wiki.ubuntuusers.de/reprepro/ könnte ein Anfang sein, aber es gibt viel mehr Tutorials. Sie können dann Ihr eigenes Linux-Paket erstellen, das Ihre Bibliotheken und alle erforderlichen Dateien zusammen mit Ihrer Python-Anwendung verteilt.

Dies wäre eine sehr saubere und bequeme Vorgehensweise für Ihre Kunden. Besonders in Bezug auf Updates. (Sie können bei Bedarf auch verschiedene Betriebssystemversionen gleichzeitig ansprechen.)

Wie immer ist ein sauberer Ansatz mit Kosten verbunden. Die Implementierung dieses sauberen Ansatzes ist mit erheblichem Aufwand verbunden. Sie müssen nicht nur einen Server einrichten - das ist der einfachere Teil -, sondern Sie müssen auch lernen, wie Pakete erstellt werden - was nicht schwierig ist, aber Sie müssen ein wenig lesen, wie das geht, und einiges experimentieren, um das zu beenden mit Paketen, die genau so sind, wie Sie sie möchten. Wie auch immer, dann wird alles so sein, wie Sie es wollen. Zukünftige Updates sind für Sie und Ihre Client-Computer sehr einfach.

Ich würde diesen Ansatz empfehlen, wenn Sie Aktualisierungen in Zukunft vereinfachen möchten, sich mit Linux vertraut machen möchten und möglicherweise in Zukunft Anforderungen an eigene Pakete haben. Oder eine große Anzahl von Kunden.


Das über einen sehr "hohen" Ansatz. Im Gegensatz dazu wäre ein sehr "niedriger" Ansatz der folgende:

  • Überprüfen Sie das Vorhandensein Ihrer Bibliotheken beim Start Ihres Programms
  • Wenn nicht vorhanden: Beenden Sie die Anwendung. Drucken Sie einen Text, der auf ein Skript zur Installation der erforderlichen Bibliotheken verweist. Das könnte sogar eine URL sein, unter der das Skript heruntergeladen werden kann, z. mit:

bash <(curl -s http://mywebsite.com/myscript.txt)

0
Regis May