Création de liaisons Python pour les bibliothÚques C / C ++ à l'aide de SIP. Partie 1

Parfois, en travaillant sur un projet en Python, on souhaite utiliser une bibliothĂšque qui n'est pas Ă©crite en Python, mais, par exemple, en C ou C ++. Les raisons peuvent ĂȘtre diffĂ©rentes. PremiĂšrement, Python est un langage merveilleux, mais dans certaines situations, il n'est pas assez rapide. Et si vous voyez que les performances sont limitĂ©es par les fonctionnalitĂ©s du langage Python, alors il est logique d'Ă©crire une partie du programme dans un autre langage (dans cet article, nous parlerons de C et C ++), d'organiser cette partie du programme en tant que bibliothĂšque, de crĂ©er des liaisons Python (liaisons Python) par dessus et utiliser le module ainsi obtenu comme une bibliothĂšque Python normale. DeuxiĂšmement, une situation se produit souvent lorsque vous savez qu'il existe une bibliothĂšque qui rĂ©sout le problĂšme requis, mais, malheureusement, cette bibliothĂšque n'est pas Ă©crite en Python, mais dans le mĂȘme C ou C ++.Dans ce cas, nous pouvons Ă©galement crĂ©er une liaison Python sur la bibliothĂšque et l'utiliser sans penser au fait que la bibliothĂšque n'a pas Ă©tĂ© Ă©crite Ă  l'origine en Python.

Il existe différents outils pour créer des liaisons Python, allant de celles de niveau inférieur comme l' API Python / C à celles de niveau supérieur comme SWIG et SIP .

Je n'avais pas pour objectif de comparer différentes façons de créer des liaisons Python, mais je voudrais parler des bases de l'utilisation d'un seul outil, à savoir SIP . Initialement, SIP a été conçu pour créer une liaison autour de la bibliothÚque Qt - PyQt , et est également utilisé pour développer d'autres grandes bibliothÚques Python, par exemple, wxPython .

Dans cet article, gcc sera utilisé comme compilateur pour C, et g ++ sera utilisé comme compilateur C ++. Tous les exemples ont été testés sous Arch Linux et Python 3.8. Afin de ne pas compliquer les exemples, le sujet de la compilation pour différents systÚmes d'exploitation et l'utilisation de différents compilateurs (par exemple, Visual Studio) n'est pas inclus dans la portée de cet article.

Vous pouvez télécharger tous les exemples de cet article depuis le référentiel sur github .
Le référentiel avec les sources SIP est situé sur https://www.riverbankcomputing.com/hg/sip/ . Mercurial est utilisé comme systÚme de contrÎle de version pour SIP.

Faire une liaison sur une bibliothĂšque en C


Écrire une bibliothùque en C


Cet exemple se trouve dans le dossier pyfoo_c_01 dans la source, mais dans cet article, nous supposerons que nous faisons tout à partir de zéro.

Commençons par un exemple simple. Tout d'abord, nous allons créer une bibliothÚque C simple, que nous exécuterons ensuite à partir d'un script Python. Que notre bibliothÚque soit la seule fonction

int foo(char*);

qui prendra une chaßne et retournera sa longueur multipliée par 2. Le

fichier d'en-tĂȘte foo.h peut ressembler, par exemple, Ă  ceci:

#ifndef FOO_LIB
#define FOO_LIB

int foo(char* str);

#endif

Et le fichier avec l'implémentation de foo.cpp :

#include <string.h>

#include "foo.h"

int foo(char* str) {
	return strlen(str) * 2;
}

Pour tester la fonctionnalité de la bibliothÚque, nous allons écrire un simple programme main.c :

#include <stdio.h>

#include "foo.h"

int main(int argc, char* argv[]) {
	char* str = "0123456789";
	printf("%d\n", foo(str));
}

Pour plus de précision, créez un Makefile :

CC=gcc
CFLAGS=-c
DIR_OUT=bin

all: main

main: main.o libfoo.a
	$(CC) $(DIR_OUT)/main.o -L$(DIR_OUT) -lfoo -o $(DIR_OUT)/main

main.o: makedir main.c
	$(CC) $(CFLAGS) main.c -o $(DIR_OUT)/main.o

libfoo.a: makedir foo.c
	$(CC) $(CFLAGS) foo.c -o $(DIR_OUT)/foo.o
	ar rcs $(DIR_OUT)/libfoo.a $(DIR_OUT)/foo.o

makedir:
	mkdir -p $(DIR_OUT)

clean:
	rm -rf $(DIR_OUT)/*

Laissez toutes les sources de la bibliothĂšque foo se trouver dans un sous-dossier de foo dans le dossier source:

foo_c_01 /
└── foo
    ├── foo.c
    ├── foo.h
    ├── main.c
    └── Makefile


Nous allons dans le dossier foo et compilons les sources en utilisant la commande

make

Pendant la compilation, le texte sera affiché.

mkdir -p bin
gcc -c main.c -o bin/main.o
gcc -c foo.c -o bin/foo.o
ar rcs bin/libfoo.a bin/foo.o
gcc bin/main.o -Lbin -lfoo -o bin/main

Le résultat de la compilation sera placé dans le dossier bin à l'intérieur du dossier foo :

foo_c_01 /
└── foo
    ├── bin
    │ ├── foo.o
    │ ├── libfoo.a
    │ ├── principal
    │ └── main.o
    ├── foo.c
    ├── foo.h
    ├── main.c
    └── Makefile


Nous avons compilé une bibliothÚque de liens statiques et un programme qui l'utilise sous le nom main . AprÚs la compilation, vous pouvez vérifier que le programme principal démarre.

Faisons une liaison Python sur la bibliothĂšque foo.

Bases SIP


Vous devez d'abord installer SIP. Cela se fait de maniĂšre standard, comme avec toutes les autres bibliothĂšques utilisant pip:

pip install --user sip

Bien sĂ»r, si vous travaillez dans un environnement virtuel, le paramĂštre --user, indiquant que la bibliothĂšque SIP doit ĂȘtre installĂ©e dans le dossier utilisateur et non globalement dans le systĂšme, ne doit pas ĂȘtre spĂ©cifiĂ©.

Que devons-nous faire pour que la bibliothĂšque foo puisse ĂȘtre appelĂ©e Ă  partir du code Python? Au minimum, vous devez crĂ©er deux fichiers: l'un au format TOML et nommez-le pyproject.toml , et le second est un fichier avec l'extension .sip. Traitons chacun d'eux sĂ©quentiellement.

Nous devons nous mettre d'accord sur la structure de la source. A l' intĂ©rieur du pyfoo_c dossier est le foo dossier , qui contient la source pour la bibliothĂšque. AprĂšs la compilation , le dossier bin est crĂ©Ă© dans le dossier foo, qui contiendra tous les fichiers compilĂ©s. Plus tard, nous ajouterons la possibilitĂ© pour l'utilisateur de spĂ©cifier les chemins d'accĂšs aux fichiers d'en-tĂȘte et d'objet de la bibliothĂšque via la ligne de commande.

Les fichiers requis pour SIP seront situĂ©s dans le mĂȘme dossier que le dossier foo .

pyproject.toml


Le fichier pyproject.toml n'est pas une invention des dĂ©veloppeurs SIP, mais le format de description de projet Python dĂ©crit dans PEP 517 «Un format indĂ©pendant du systĂšme de construction pour les arborescences sources» et dans PEP 518 «SpĂ©cification des exigences minimales du systĂšme de construction pour les projets Python» . Il s'agit d'un fichier TOML , qui peut ĂȘtre considĂ©rĂ© comme une version plus avancĂ©e du format ini, dans lequel les paramĂštres sont stockĂ©s sous la forme de "clĂ© = valeur", et les paramĂštres peuvent ĂȘtre localisĂ©s non seulement dans des sections comme [foo], appelĂ©es tables en termes TOML, mais et dans les sous-sections du formulaire [foo.bar.spam]. Les paramĂštres peuvent contenir non seulement des chaĂźnes, mais Ă©galement des listes, des nombres et des valeurs boolĂ©ennes.

Ce fichier est censé décrire tout ce qui est nécessaire pour construire un package Python, et pas nécessairement en utilisant SIP. Cependant, comme nous le verrons un peu plus tard, dans certains cas, ce fichier ne sera pas suffisant, et en plus il faudra créer un petit script Python. Mais parlons de tout dans l'ordre.

Une description complÚte de tous les paramÚtres possibles du fichier pyproject.toml liés à SIP se trouve sur la page de documentation SIP .

Pour notre exemple, crĂ©ez le fichier pyproject.toml au mĂȘme niveau que le dossier foo :

foo_c_01 /
├── foo
│ ├── bin
│ │ ├── foo.o
│ │ ├── libfoo.a
│ │ ├── principal
│ │ └── main.o
│ ├── foo.c
│ ├── foo.h
│ ├── main.c
│ └── Makefile
└── pyproject.toml


Le contenu de pyproject.toml sera le suivant:

[build-system]
requires = ["sip >=5, <6"]
build-backend = "sipbuild.api"

[tool.sip.metadata]
name = "pyfoo"
version = "0.1"
license = "MIT"

[tool.sip.bindings.pyfoo]
headers = ["foo.h"]
libraries = ["foo"]
include-dirs = ["foo"]
library-dirs = ["foo/bin"]

La section [build-system] («table» en termes TOML) est standard et est décrite dans PEP 518 . Il contient deux paramÚtres:


D'autres paramÚtres sont décrits dans les sections [tool.sip. *] .

La section [tool.sip.metadata] contient des informations générales sur le paquet: le nom du paquet à construire (notre paquet s'appellera pyfoo , mais ne confondez pas ce nom avec le nom du module, que nous importerons plus tard dans Python), le numéro de version du paquet (dans notre cas numéro de version «0.1») et licence (par exemple, « MIT »).

Le plus important du point de vue de l'assemblage est décrit dans les [tool.sip.bindings. pyfoo ].

Notez le nom du package dans l'en-tĂȘte de section. Nous avons ajoutĂ© deux paramĂštres Ă  cette section:

  • headers - une liste des fichiers d'en-tĂȘte nĂ©cessaires pour utiliser la bibliothĂšque foo.
  • bibliothĂšques - une liste de fichiers objets compilĂ©s pour la liaison statique.
  • include-dirs est le chemin oĂč chercher des fichiers d' en- tĂȘte supplĂ©mentaires en plus de ceux qui sont attachĂ©s au compilateur C. Dans ce cas, oĂč chercher le foo.h fichier .
  • bibliothĂšque-dirs est le chemin oĂč rechercher des fichiers objets supplĂ©mentaires en plus de ceux qui sont attachĂ©s au compilateur C. Dans ce cas, il s'agit du dossier dans lequel le fichier de bibliothĂšque compilĂ© foo est crĂ©Ă© .

Nous avons donc créé le premier fichier nécessaire pour SIP. Nous passons maintenant à la création du prochain fichier qui décrira le contenu du futur module Python.

pyfoo.sip


CrĂ©ez le fichier pyfoo.sip dans le mĂȘme dossier que le fichier pyproject.toml :

foo_c_01 /
├── foo
│ ├── bin
│ │ ├── foo.o
│ │ ├── libfoo.a
│ │ ├── principal
│ │ └── main.o
│ ├── foo.c
│ ├── foo.h
│ ├── main.c
│ └── Makefile
├── pyfoo.sip
└── pyproject.toml


Un fichier avec l'extension .sip dĂ©crit l'interface de la bibliothĂšque source, qui sera convertie en module en Python. Ce fichier a son propre format, que nous allons maintenant considĂ©rer, et ressemble au fichier d'en-tĂȘte C / C ++ avec un balisage supplĂ©mentaire, ce qui devrait aider SIP Ă  crĂ©er un module Python.

Dans notre exemple, ce fichier doit s'appeler pyfoo.sip , car auparavant, dans le fichier pyproject.toml, nous avons créé le [tool.sip.bindings. pyfoo]. Dans le cas général, il peut y avoir plusieurs partitions de ce type et, par conséquent, il doit y avoir plusieurs fichiers * .sip. Mais si nous avons plusieurs fichiers SIP, alors c'est un cas particulier du point de vue de SIP, et nous ne le considérerons pas dans cet article. Veuillez noter que dans le cas général, le nom du fichier .sip (et, par conséquent, le nom de la section) peut ne pas coïncider avec le nom du package spécifié dans le paramÚtre name de la section [tool.sip.metadata] .

Considérez le fichier pyfoo.sip de notre exemple:

%Module(name=foo, language="C")

int foo(char*);

Les lignes qui commencent par le caractĂšre "%" sont appelĂ©es directives. Ils devraient indiquer Ă  SIP comment assembler et styliser correctement le module Python. Une liste complĂšte des directives est dĂ©crite sur cette page de documentation . Certaines directives ont des paramĂštres supplĂ©mentaires. Les paramĂštres peuvent ne pas ĂȘtre requis.

Dans cet exemple, nous utilisons deux directives; nous découvrirons d'autres directives dans les exemples suivants.

Le fichier pyfoo.sip commence par la directive % Module (name = foo, language = “C”) . Veuillez noter que nous avons spĂ©cifiĂ© la valeur du premier paramĂštre ( nom ) sans guillemets, et la valeur du deuxiĂšme paramĂštre ( langue) avec des guillemets, comme des chaĂźnes en C / C ++. Il s'agit d'une exigence de cette directive, comme dĂ©crit dans la documentation de la directive % Module .

Dans la directive % Module , seul le paramÚtre name est requis , ce qui définit le nom du module Python à partir duquel nous importerons la fonction de bibliothÚque. Dans ce cas, le module s'appelle foo , il contiendra la fonction foo , donc aprÚs assemblage et installation nous l'importerons en utilisant le code:

from foo import foo

Nous pourrions rendre ce module imbriqué dans un autre module en remplaçant cette ligne, par exemple, par ceci:

%Module(name=foo.bar, language="C")
...

Ensuite, importez la fonction foo devrait ĂȘtre comme suit:

from foo.bar import foo

Le paramĂštre de langue de la directive % Module indique la langue dans laquelle la bibliothĂšque source est Ă©crite. La valeur de ce paramĂštre peut ĂȘtre «C» ou «C ++». Si ce paramĂštre n'est pas spĂ©cifiĂ©, SIP supposera que la bibliothĂšque est Ă©crite en C ++.

Regardez maintenant la derniĂšre ligne du fichier pyfoo.sip :

int foo(char*);

Il s'agit d'une description de l'interface de la fonction de la bibliothĂšque que nous voulons mettre dans le module Python. Sur la base de cette dĂ©claration, sip crĂ©era une fonction Python. Je pense que tout devrait ĂȘtre clair ici.

Nous collectons et vérifions


Maintenant, tout est prĂȘt pour construire un paquet Python avec une liaison pour une bibliothĂšque C. Tout d'abord, vous devez construire la bibliothĂšque elle-mĂȘme. AccĂ©dez au dossier pyfoo_c_01 / foo / et dĂ©marrez la gĂ©nĂ©ration Ă  l'aide de la commande make :

$ make

mkdir -p bin
gcc -c main.c -o bin/main.o
gcc -c foo.c -o bin/foo.o
ar rcs bin/libfoo.a bin/foo.o
gcc bin/main.o -Lbin -lfoo -o bin/main

Si tout s'est bien passĂ©, le dossier bin sera crĂ©Ă© Ă  l'intĂ©rieur du dossier foo , dans lequel, entre autres fichiers, il y aura une bibliothĂšque libfoo.a compilĂ©e . Permettez-moi de vous rappeler qu'ici, afin de ne pas ĂȘtre distrait du sujet principal, nous ne parlons que de construire sous Linux en utilisant gcc. Retournez dans le dossier pyfoo_c_01 . Il est maintenant temps de dĂ©couvrir les Ă©quipes SIP. AprĂšs l'installation de SIP, les commandes de ligne de commande suivantes seront disponibles ( page de documentation ):



  • sip-build . CrĂ©e un fichier objet d'extension Python.
  • sip-install . CrĂ©e un fichier objet d'extension Python et l'installe.
  • sip-sdist. .tar.gz, pip.
  • sip-wheel. wheel ( .whl).
  • sip-module. , , SIP. , , . , standalone project, , , , .
  • sip-distinfo. .dist-info, wheel.

Ces commandes doivent ĂȘtre exĂ©cutĂ©es Ă  partir du dossier oĂč se trouve le fichier pyproject.toml .

Pour commencer, pour mieux comprendre le fonctionnement de SIP, exécutez la commande sip-build , avec l'option --verbose pour une sortie plus détaillée sur la console, et voyez ce qui se passe pendant le processus de construction.

$ sip-build --verbose

Ces liaisons seront construites: pyfoo.
Génération des liaisons pyfoo ...
Compilation du module 'foo' ...
construction de l'extension 'foo'
création de build
création de build / temp.linux-x86_64-3.8
gcc -pthread -Wno-non-result-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c sipfoocmodule.c -o build / temp.linux-x86_64-3.8 / sipfoocmodule.o
sipfoocmodule.c: Dans la fonction
func_foo : sipfoocmodule .c: 29: 22: avertissement: déclaration de fonction implicite «foo» [-Wimplicit-function-declaration]
29 | sipRes = foo (a0);
| ^ ~~
gcc -pthread -Wno-non-result-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c array.c -o build / temp.linux-x86_64-3.8 / array.o
gcc -pthread -Wno -used-result -Wsign -comparer -DNDEBUG -g -fwrapv -O3 -Mur -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -fno-sémantique-interposition -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c bool.cpp -o build / temp.linux-x86_64-3.8 / bool.o
gcc -pthread -Wno-non-result-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c objmap.c -o build / temp.linux-x86_64-3.8 / objmap.o
gcc -pthread -Wno-non-result-result -Wsign -comparer -DNDEBUG -g -fwrapv -O3 -Mur -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -fno-sémantique-interposition -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c qtlib.c -o build / temp.linux-x86_64-3.8 / qtlib.o
gcc -pthread -Wno-non-result-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c int_convertors.c -o build / temp.linux-x86_64-3.8 / int_convertors.o
gcc -pthread -Wno-non utilisé-résultat -Wsign -comparer -DNDEBUG -g -fwrapv -O3 -Mur -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -fno-sémantique-interposition -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c voidptr.c -o build / temp.linux-x86_64-3.8 / voidptr.o
gcc -pthread -Wno-non-result-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c apiversions.c -o build / temp.linux-x86_64-3.8 / apiversions.o
gcc -pthread -Wno-non-result-result -Wsign -comparer -DNDEBUG -g -fwrapv -O3 -Mur -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -fno-sémantique-interposition -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c descriptors.c -o build / temp.linux-x86_64-3.8 / descriptors.o
gcc -pthread -Wno-non-result-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c threads.c -o build / temp.linux-x86_64-3.8 /
threads.o gcc -pthread -Wno-non utilisé-result -Wsign -comparer -DNDEBUG -g -fwrapv -O3 -Mur -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -fno-sémantique-interposition -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -march = x86-64 -mtune = générique -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c siplib.c -o build / temp.linux-x86_64-3.8 / siplib.o
siplib.c: Dans la fonction "slot_richcompare":
siplib.c: 9536: 16: avertissement: "st" peut ĂȘtre utilisĂ© sans initialisation dans cette fonction [-Wmaybe-uninitialized]
9536 | slot = findSlotInClass (ctd, st);
| ^ ~~~~~~~~~~~~~~~~~~~~~~~~
siplib.c: 10671: 19: remarque: "st" a été déclaré ici
10671 | sipPySlotType st;
| ^ ~
siplib.c: Dans la fonction "parsePass2":
siplib.c: 5625: 32: avertissement: "propriĂ©taire" peut ĂȘtre utilisĂ© sans initialisation dans cette fonction [-Wmaybe-uninitialized]
5625 | * propriétaire = arg;
| ~~~~~~~ ^ ~
g ++ -pthread -shared -Wl, -O1, - sort-common, - as-needed, -z, relro, -z, now -fno-semantic-interposition -Wl, -O1, - sort-common, --as-needed, -z, relro, -z, now build / temp.linux-x86_64-3.8 / sipfoocmodule.o build / temp.linux-x86_64-3.8 / array.o build / temp.linux-x86_64-3.8 /bool.o build / temp.linux-x86_64-3.8 / objmap.o build / temp.linux-x86_64-3.8 / qtlib.o build / temp.linux-x86_64-3.8 / int_convertors.o build / temp.linux-x86_64 -3.8 / voidptr.o build / temp.linux-x86_64-3.8 / apiversions.o build / temp.linux-x86_64-3.8 / descriptors.o build / temp.linux-x86_64-3.8 / threads.o build / temp.linux -x86_64-3.8 / siplib.o -L ../../ foo / bin -L / usr / lib -lfoo -o / home / jenyay / projects / soft / sip-examples / pyfoo_c_01 / build / foo / foo. cpython-38-x86_64-linux-gnu.so
Le projet a été construit.

Nous n'entrerons pas en profondeur dans le travail de SIP, mais il ressort de la sortie que certaines sources sont en train de compiler. Ces sources peuvent ĂȘtre vues dans le dossier build / foo / crĂ©Ă© par cette commande :

pyfoo_c_01
├── construire
│ └── foo
│ ├── apiversions.c
│ ├── array.c
│ ├── array.h
│ ├── bool.cpp
│ ├── construire
│ │ └── temp.linux-x86_64-3.8
│ │ ├── apiversions.o
│ │ ├── array.o
│ │ ├── bool.o
│ │ ├── descriptors.o
│ │ ├── int_convertors.o
│ │ ├── objmap.o
│ │ ├── qtlib.o
│ │ ├── sipfoocmodule.o
│ │ ├── siplib.o
│ │ ├── threads.o
│ │ └── voidptr.o
│ ├── descriptors.c
│ ├── foo.cpython-38-x86_64-linux-gnu.so
│ ├── int_convertors.c
│ ├── objmap.c
│ ├── qtlib.c
│ ├── sipAPIfoo.h
│ ├── sipfoocmodule.c
│ ├── sip.h
│ ├── sipint.h
│ ├── siplib.c
│ ├── threads.c
│ └── voidptr.c
├── foo
│ ├── bin
│ │ ├── foo.o
│ │ ├── libfoo.a
│ │ ├── principal
│ │ └── main.o
│ ├── foo.c
│ ├── foo.h
│ ├── main.c
│ └── Makefile
├── pyfoo.sip
└── pyproject.toml


Des sources auxiliaires sont apparues dans le dossier build / foo . Par curiosité, regardons le fichier sipfoocmodule.c , car il concerne directement le module foo qui sera créé:

/*
 * Module code.
 *
 * Generated by SIP 5.1.1
 */

#include "sipAPIfoo.h"

/* Define the strings used by this module. */
const char sipStrings_foo[] = {
    'f', 'o', 'o', 0,
};

PyDoc_STRVAR(doc_foo, "foo(str) -> int");

static PyObject *func_foo(PyObject *sipSelf,PyObject *sipArgs)
{
    PyObject *sipParseErr = SIP_NULLPTR;

    {
        char* a0;

        if (sipParseArgs(&sipParseErr, sipArgs, "s", &a0))
        {
            int sipRes;

            sipRes = foo(a0);

            return PyLong_FromLong(sipRes);
        }
    }

    /* Raise an exception if the arguments couldn't be parsed. */
    sipNoFunction(sipParseErr, sipName_foo, doc_foo);

    return SIP_NULLPTR;
}

/* This defines this module. */
sipExportedModuleDef sipModuleAPI_foo = {
    0,
    SIP_ABI_MINOR_VERSION,
    sipNameNr_foo,
    0,
    sipStrings_foo,
    SIP_NULLPTR,
    SIP_NULLPTR,
    0,
    SIP_NULLPTR,
    SIP_NULLPTR,
    0,
    SIP_NULLPTR,
    0,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    {SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR,
            SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR},
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR
};

/* The SIP API and the APIs of any imported modules. */
const sipAPIDef *sipAPI_foo;

/* The Python module initialisation function. */
#if defined(SIP_STATIC_MODULE)
PyObject *PyInit_foo(void)
#else
PyMODINIT_FUNC PyInit_foo(void)
#endif
{
    static PyMethodDef sip_methods[] = {
        {sipName_foo, func_foo, METH_VARARGS, doc_foo},
        {SIP_NULLPTR, SIP_NULLPTR, 0, SIP_NULLPTR}
    };

    static PyModuleDef sip_module_def = {
        PyModuleDef_HEAD_INIT,
        "foo",
        SIP_NULLPTR,
        -1,
        sip_methods,
        SIP_NULLPTR,
        SIP_NULLPTR,
        SIP_NULLPTR,
        SIP_NULLPTR
    };

    PyObject *sipModule, *sipModuleDict;
    /* Initialise the module and get it's dictionary. */
    if ((sipModule = PyModule_Create(&sip_module_def)) == SIP_NULLPTR)
        return SIP_NULLPTR;

    sipModuleDict = PyModule_GetDict(sipModule);

    if ((sipAPI_foo = sip_init_library(sipModuleDict)) == SIP_NULLPTR)
        return SIP_NULLPTR;

    /* Export the module and publish it's API. */
    if (sipExportModule(&sipModuleAPI_foo, SIP_ABI_MAJOR_VERSION, SIP_ABI_MINOR_VERSION, 0) < 0)
    {
        Py_DECREF(sipModule);
        return SIP_NULLPTR;
    }
    /* Initialise the module now all its dependencies have been set up. */
    if (sipInitModule(&sipModuleAPI_foo,sipModuleDict) < 0)
    {
        Py_DECREF(sipModule);
        return SIP_NULLPTR;
    }

    return sipModule;
}

Si vous avez travaillé avec l'API Python / C, vous verrez des fonctions familiÚres. Portez une attention particuliÚre à la fonction func_foo à partir de la ligne 18.

À la suite de la compilation de ces sources, le fichier build / foo / foo.cpython-38-x86_64-linux-gnu.so sera crĂ©Ă© et contient l'extension Python, qui doit encore ĂȘtre installĂ©e correctement.

Afin de compiler l'extension et de l'installer immĂ©diatement, vous pouvez utiliser la commande sip-install , mais nous ne l'utilisons pas, car par dĂ©faut, il essaie d'installer l'extension Python crĂ©Ă©e globalement dans le systĂšme. Cette commande a un paramĂštre --target-dir, avec lequel vous pouvez spĂ©cifier le chemin oĂč vous souhaitez installer l'extension, mais nous ferons mieux d'utiliser d'autres outils qui crĂ©ent des packages, qui peuvent ensuite ĂȘtre installĂ©s Ă  l'aide de pip.

Tout d'abord, utilisez la commande sip-sdist . Son utilisation est trĂšs simple:

$ sip-sdist

The sdist has been built.

AprĂšs cela, le fichier pyfoo-0.1.tar.gz sera crĂ©Ă© , qui peut ĂȘtre installĂ© Ă  l'aide de la commande:

pip install --user pyfoo-0.1.tar.gz

En conséquence, les informations suivantes seront affichées et le package sera installé:

Processing ./pyfoo-0.1.tar.gz
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
    Preparing wheel metadata ... done
Building wheels for collected packages: pyfoo
  Building wheel for pyfoo (PEP 517) ... done
  Created wheel for pyfoo: filename=pyfoo-0.1-cp38-cp38-manylinux1_x86_64.whl size=337289 sha256=762fc578...
  Stored in directory: /home/jenyay/.cache/pip/wheels/54/dc/d8/cc534fff...
Successfully built pyfoo
Installing collected packages: pyfoo
  Attempting uninstall: pyfoo
    Found existing installation: pyfoo 0.1
    Uninstalling pyfoo-0.1:
      Successfully uninstalled pyfoo-0.1
Successfully installed pyfoo-0.1

Assurons-nous que nous avons réussi à créer une liaison Python. Nous démarrons Python et essayons d'appeler la fonction. Permettez-moi de vous rappeler que selon nos paramÚtres, le paquet pyfoo contient le module foo , qui a la fonction foo .

>>> from foo import foo
>>> foo(b'123456')
12

Veuillez noter qu'en tant que paramÚtre de la fonction, nous transmettons non seulement une chaßne, mais une chaßne d'octets b'123456 '- un analogue direct de char * en C. Un peu plus tard, nous ajouterons la conversion de char * en str et vice versa. Le résultat était attendu. Permettez-moi de vous rappeler que la fonction foo renvoie la double taille d'un tableau de type char * , qui lui est passée en paramÚtre.

Essayons de passer une chaßne Python réguliÚre à la fonction foo au lieu d'une liste d'octets.

>>> from foo import foo
>>> foo('123456')

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo(str): argument 1 has unexpected type 'str'

La liaison créée n'a pas pu convertir la chaßne en char * ; nous verrons comment lui apprendre comment procéder dans la section suivante.

Félicitations, nous avons créé la premiÚre liaison sur une bibliothÚque écrite en C.

Quittez l'interpréteur Python et assemblez l'assemblage au format roue. Comme vous le savez sans doute, wheel est un format de package relativement nouveau qui a été utilisé universellement récemment. Le format est décrit dans PEP 427, «The Wheel Binary Package Format 1.0», mais une description des caractéristiques du format de roue est un sujet digne d'un grand article séparé. Il est important pour nous que l'utilisateur puisse facilement installer le package au format roue à l'aide de pip.

Un package au format roue n'est pas plus compliqué qu'un package au format sdist. Pour ce faire, dans le dossier contenant le fichierpyproject.toml doit exécuter la commande

sip-wheel

AprÚs avoir exécuté cette commande, le processus de génération sera affiché et il peut y avoir des avertissements du compilateur:

$ sip-wheel

Ces liaisons seront construites: pyfoo.
Génération des liaisons pyfoo ...
Compilation du module 'foo' ...
sipfoocmodule.c: Dans la fonction
func_foo: sipfoocmodule.c: 29:22: avertissement: déclaration de fonction implicite de la fonction foo [-Wimplicit-function-declaration]
29 | sipRes = foo (a0);
| ^ ~~
siplib.c: Dans la fonction "slot_richcompare":
siplib.c: 9536: 16: avertissement: "st" peut ĂȘtre utilisĂ© sans initialisation dans cette fonction [-Wmaybe-uninitialized]
9536 | slot = findSlotInClass (ctd, st);
| ^ ~~~~~~~~~~~~~~~~~~~~~~~~
siplib.c: 10671: 19: remarque: "st" a été déclaré ici
10671 | sipPySlotType st;
| ^ ~
siplib.c: Dans la fonction "parsePass2":
siplib.c: 5625: 32: avertissement: "propriĂ©taire" peut ĂȘtre utilisĂ© sans initialisation dans cette fonction [-Wmaybe-uninitialized]
5625 | * propriétaire = arg;
| ~~~~~~~ ^ ~ ~
La roue a été construite.

Lorsque l'assemblage est terminé (notre petit projet se compile rapidement), un fichier avec le nom pyfoo-0.1-cp38-cp38-manylinux1_x86_64.whl ou similaire apparaßtra dans le dossier du projet . Le nom du fichier généré peut différer selon votre systÚme d'exploitation et la version de Python.

Maintenant, nous pouvons installer ce package en utilisant pip:

pip install --user --upgrade pyfoo-0.1-cp38-cp38-manylinux1_x86_64.whl

L'option --upgrade est utilisée ici afin que pip remplace le module pyfoo installé précédemment.

De plus, le module foo et le paquet pyfoo peuvent ĂȘtre utilisĂ©s, comme indiquĂ© ci-dessus.

Ajouter des rĂšgles de conversion Ă  char *


Dans la section précédente, nous avons rencontré un problÚme selon lequel la fonction foo ne peut accepter qu'un ensemble d'octets, mais pas de chaßnes. Nous allons maintenant corriger cette lacune. Pour ce faire, nous utiliserons un autre outil SIP - les annotations . Les annotations sont utilisées dans les fichiers .sip et sont appliquées à certains éléments de code: fonctions, classes, arguments de fonction, exceptions, variables, etc. Les annotations sont écrites entre des barres obliques: / annotation / .

Une annotation peut fonctionner comme un indicateur, qui peut ĂȘtre dans l'Ă©tat dĂ©fini ou non dĂ©fini, par exemple: / ReleaseGIL / , ou certaines annotations doivent ĂȘtre affectĂ©es Ă  certaines valeurs, par exemple: / Encoding = "UTF-8" /. Si plusieurs annotations doivent ĂȘtre appliquĂ©es Ă  un objet, elles sont sĂ©parĂ©es par des virgules dans des barres obliques: / annotation_1, annotation_2 /.

Dans l'exemple suivant, qui se trouve dans le dossier pyfoo_c_02 , ajoutez une fonction de paramĂštre d'annotation de fichier pyfoo.sip foo :

%Module(name=foo, language="C")

int foo(char* /Encoding="UTF-8"/);

L'annotation Encoding indique dans quel encodage la chaĂźne Ă  passer Ă  la fonction doit ĂȘtre encodĂ©e. Les valeurs de cette annotation peuvent ĂȘtre: ASCII, Latin-1, UTF-8 ou None. Si l'annotation d' encodage n'est pas spĂ©cifiĂ©e ou Ă©gale Ă  None , le paramĂštre d'une telle fonction n'est soumis Ă  aucun encodage et est transmis Ă  la fonction telle quelle, mais dans ce cas, le paramĂštre en code Python doit ĂȘtre de type octets , c'est-Ă -dire un tableau d'octets, comme nous l'avons vu dans l'exemple prĂ©cĂ©dent. Si le codage est spĂ©cifiĂ©, ce paramĂštre peut ĂȘtre une chaĂźne (tapez str en Python). L' annotation d' encodage ne peut ĂȘtre appliquĂ©e qu'aux paramĂštres de type char , const char ,char * ou const char * .

VĂ©rifions comment la foo fonction du foo le module fonctionne maintenant . Pour ce faire, comme prĂ©cĂ©demment, vous devez d'abord compiler la bibliothĂšque foo en appelant la commande make dans le dossier foo , puis appeler la commande, par exemple, sip-wheel, Ă  partir du dossier d'exemple pyfoo_c_02 . Le fichier pyfoo-0.2-cp38-cp38-manylinux1_x86_64.whl ou avec un nom similaire sera crĂ©Ă© , qui peut ĂȘtre dĂ©fini Ă  l'aide de la commande:

pip install --user --upgrade pyfoo-0.2-cp38-cp38-manylinux1_x86_64.whl

Si tout s'est bien passé, démarrez l'interpréteur Python et essayez d'appeler la fonction foo avec un argument de chaßne:

>>> from foo import foo

>>> foo(b'qwerty')
12

>>> foo('qwerty')
12

>>> foo('')
24

Tout d'abord, nous nous assurons que l'utilisation d' octets est toujours possible. AprĂšs cela, nous nous assurons que maintenant nous pouvons Ă©galement passer des arguments de chaĂźne Ă  la fonction foo . Notez que la fonction foo pour un argument de chaĂźne avec des lettres russes a renvoyĂ© une valeur deux fois plus grande que pour une chaĂźne contenant uniquement des lettres latines. Cela est arrivĂ© parce que la fonction foo ne compte pas la longueur de la chaĂźne en caractĂšres (et la double), mais la longueur du tableau char * , et comme en UTF-8, le codage des lettres russes occupe 2 octets, puis la taille du tableau char * aprĂšs la conversion de Les chaĂźnes Python se sont avĂ©rĂ©es ĂȘtre deux fois plus longues.

Bien! Nous avons rĂ©solu le problĂšme avec l'argument de fonctionfoo , mais que se passe-t-il si nous avons des dizaines ou des centaines de ces fonctions dans notre bibliothĂšque, devrez-vous spĂ©cifier l'encodage des paramĂštres pour chacune d'entre elles? Souvent, l'encodage utilisĂ© dans un programme est le mĂȘme, et il n'y a aucune raison pour que diffĂ©rentes fonctions indiquent des encodages diffĂ©rents. Dans ce cas, il est possible de spĂ©cifier un encodage par dĂ©faut dans SIP, et si pour une fonction un encodage est nĂ©cessaire pour un autre, alors il peut ĂȘtre redĂ©fini en utilisant l'annotation Encoding .

Pour définir l'encodage des paramÚtres de fonction par défaut, la directive % DefaultEncoding est utilisée . Son utilisation est illustrée dans l'exemple situé dans le dossier pyfoo_c_03 .

Afin de profiter de la directive % DefaultEncoding , changez le fichier pyfoo.sip, son contenu est maintenant le suivant:

%Module(name=foo, language="C")
%DefaultEncoding "UTF-8"

int foo(char*);

Maintenant, si l'argument est une fonction de type char , char * , etc. S'il n'y a pas d'annotation Encoding , alors l'encodage est tiré de la directive % DefaultEncoding , et si ce n'est pas le cas, alors la conversion n'est pas effectuée, et pour tous les paramÚtres char * , etc. il faut transférer non pas des lignes, mais des octets .

Un exemple du dossier pyfoo_c_03 est collectĂ© et vĂ©rifiĂ© de la mĂȘme maniĂšre qu'un exemple du dossier pyfoo_c_02 .

En bref sur project.py. Automatisez l'assemblage


Jusqu'Ă  prĂ©sent, nous avons utilisĂ© deux fichiers utilitaires pour crĂ©er une liaison Python - pyproject.toml et pyfoo.sip . Nous allons maintenant nous familiariser avec un autre de ces fichiers, qui devrait s'appeler project.py . Avec ce script, nous pouvons influencer le processus de construction de notre package. Faisons l'automatisation de la construction. Afin de collecter les exemples pyfoo_c_01 - pyfoo_c_03 des sections prĂ©cĂ©dentes, vous deviez d'abord aller dans le dossier foo / , y compiler Ă  l'aide de la commande make , retourner dans le dossier oĂč se trouve le fichier pyproject.toml, puis commencer Ă  construire le package Ă  l'aide de l'un des commandes sip- * .

Maintenant, notre objectif est de nous assurer que lors de l'exĂ©cution des commandes sip-build , sip-sdist et sip-wheel , l'assemblage de la bibliothĂšque C foo est dĂ©marrĂ© en premier , puis la commande elle-mĂȘme est dĂ©jĂ  lancĂ©e.

Un exemple créé dans cette section se trouve dans le dossier source pyfoo_c_04 .

Pour modifier le processus de gĂ©nĂ©ration, nous pouvons dĂ©clarer une classe dans le fichier project.py (le nom de fichier doit ĂȘtre juste celui) qui est dĂ©rivĂ©e de la classe sipbuild.Project . Cette classe possĂšde des mĂ©thodes que nous pouvons remplacer par nous-mĂȘmes. Actuellement, nous nous intĂ©ressons aux mĂ©thodes suivantes:

  • construire . AppelĂ© lors de l'appel Ă  la commande sip-build .
  • build_sdist . AppelĂ© lorsque la commande sip-sdist est appelĂ©e .
  • build_wheel . AppelĂ© lorsque la commande SIP-Wheel est appelĂ©e .
  • installer . AppelĂ© lorsque la commande sip-install est invoquĂ©e .

Autrement dit, nous pouvons redĂ©finir le comportement de ces commandes. À strictement parler, les mĂ©thodes rĂ©pertoriĂ©es sont dĂ©clarĂ©es dans la classe abstraite sipbuild.AbstractProject , Ă  partir de laquelle la classe dĂ©rivĂ©e sipbuild.Project est crĂ©Ă©e .

Créez un fichier project.py avec le contenu suivant:

import os
import subprocess

from sipbuild import Project

class FooProject(Project):
    def _build_foo(self):
        cwd = os.path.abspath('foo')
        subprocess.run(['make'], cwd=cwd, capture_output=True, check=True)

    def build(self):
        self._build_foo()
        super().build()

    def build_sdist(self, sdist_directory):
        self._build_foo()
        return super().build_sdist(sdist_directory)

    def build_wheel(self, wheel_directory):
        self._build_foo()
        return super().build_wheel(wheel_directory)

    def install(self):
        self._build_foo()
        super().install()

Nous avons dĂ©clarĂ© la classe FooProject , dĂ©rivĂ©e de la classe sipbuild.Project, et y avons dĂ©fini les mĂ©thodes build , build_sdist , build_wheel et install . Dans toutes ces mĂ©thodes, nous appelons les mĂ©thodes du mĂȘme nom Ă  partir de la classe de base, avant d'appeler la mĂ©thode _build_foo , qui dĂ©marre l'exĂ©cution de la commande make dans le dossier foo .

Notez que les méthodes build_sdist et build_wheel doivent renvoyer le nom du fichier qu'elles ont créé. Ceci n'est pas écrit dans la documentation, mais est indiqué dans les sources SIP.

Maintenant, nous n'avons pas besoin d'exécuter la commande makemanuellement pour construire la bibliothÚque foo , cela se fera automatiquement.

Si vous exĂ©cutez maintenant la commande sip-wheel dans le dossier pyfoo_c_04 , un fichier est crĂ©Ă© avec le nom pyfoo-0.4-cp38-cp38-manylinux1_x86_64.whl ou similaire selon votre systĂšme d'exploitation et la version de Python. Ce package peut ĂȘtre installĂ© Ă  l'aide de la commande:



pip install --user --upgrade pyfoo-0.4-cp38-cp38-manylinux1_x86_64.whl

AprĂšs cela, vous pouvez vous assurer que la foo fonction du foo le module fonctionne toujours.

Ajouter des options de ligne de commande Ă  construire


L'exemple suivant se trouve dans le dossier pyfoo_c_05 et le package a un numéro de version de 0,5 (voir les paramÚtres dans le fichier pyproject.toml ). Cet exemple est basé sur un exemple de la documentation avec quelques corrections. Dans cet exemple, nous allons refaire notre fichier project.py et ajouter de nouvelles options de ligne de commande pour la build.

Dans nos exemples, nous construisons une bibliothĂšque trĂšs simple foo, et dans les projets rĂ©els, la bibliothĂšque peut ĂȘtre assez volumineuse et il ne sera alors pas judicieux de l'inclure dans le code source du projet de liaison Python. Permettez-moi de vous rappeler que SIP a Ă©tĂ© crĂ©Ă© Ă  l'origine pour crĂ©er une liaison pour une bibliothĂšque aussi Ă©norme que Qt. Vous pouvez, bien sĂ»r, affirmer que les sous-modules de git peuvent aider Ă  organiser la source, mais ce n'est pas le but. Supposons que la bibliothĂšque ne se trouve pas dans le dossier avec la source de liaison. Dans ce cas, la question se pose: oĂč le collecteur SIP doit-il rechercher l'en-tĂȘte de bibliothĂšque et les fichiers objets? Dans ce cas, diffĂ©rents utilisateurs peuvent avoir leurs propres façons de placer la bibliothĂšque.

Pour résoudre ce problÚme, nous allons ajouter deux nouvelles options de ligne de commande au systÚme de construction, avec lesquelles vous pouvez spécifier le chemin d'accÚs au fichier foo.h (paramÚtre --foo-include-dir) et au fichier objet de la bibliothÚque (paramÚtre --foo-library-dir ). De plus, nous supposerons que si ces paramÚtres ne sont pas spécifiés, la bibliothÚque foo est toujours localisée avec les sources de liaison.

Nous devons recréer le fichier project.py et y déclarer une classe dérivée de sipbuild.Project . Examinons d'abord la nouvelle version du fichier project.py , puis voyons comment cela fonctionne.

import os

from sipbuild import Option, Project

class FooProject(Project):
    """        
       foo.
    """

    def get_options(self):
        """     . """

        tools = ['build', 'install', 'sdist', 'wheel']

        #   .
        options = super().get_options()

        #   
        inc_dir_option = Option('foo_include_dir',
                                help="the directory containing foo.h",
                                metavar="DIR",
                                default=os.path.abspath('foo'),
                                tools=tools)
        options.append(inc_dir_option)

        lib_dir_option = Option('foo_library_dir',
                                help="the directory containing the foo library",
                                metavar="DIR",
                                default=os.path.abspath('foo/bin'),
                                tools=tools)

        options.append(lib_dir_option)

        return options

    def apply_user_defaults(self, tool):
        """    . """

        #     
        super().apply_user_defaults(tool)

        #  ,         
        self.foo_include_dir = os.path.abspath(self.foo_include_dir)
        self.foo_library_dir = os.path.abspath(self.foo_library_dir)

    def update(self, tool):
        """   . """

        #   pyfoo
        # (  pyproject.toml  [tool.sip.bindings.pyfoo])
        foo_bindings = self.bindings['pyfoo']

        #   include_dirs  
        if self.foo_include_dir is not None:
            foo_bindings.include_dirs = [self.foo_include_dir]

        #   library_dirs  
        if self.foo_library_dir is not None:
            foo_bindings.library_dirs = [self.foo_library_dir]

        super().update(tool)

Nous avons Ă  nouveau crĂ©Ă© la classe FooProject , dĂ©rivĂ©e de sipbuild.Project . Dans cet exemple, l'assemblage automatique de la bibliothĂšque foo est dĂ©sactivĂ© , car il est dĂ©sormais supposĂ© qu'il peut se trouver Ă  un autre endroit et au moment de la crĂ©ation de la liaison, les fichiers d'en-tĂȘte et d'objet doivent ĂȘtre prĂȘts. Trois mĂ©thodes sont redĂ©finis

dans la FooProject classe : get_options , apply_user_defaults, et mise à jour . Considérez-les plus attentivement.

Commençons par la mĂ©thode get_options . Cette mĂ©thode doit renvoyer une liste d'instances de la classe sipbuild.Option. Chaque Ă©lĂ©ment de liste est une option de ligne de commande. Dans la mĂ©thode remplacĂ©e, nous obtenons la liste des options par dĂ©faut (la variable options ) en appelant la mĂ©thode de classe de base du mĂȘme nom, puis crĂ©ons deux nouvelles options ( --foo_include_dir et --foo_library_dir ) et les ajoutons Ă  la liste, puis renvoyons cette liste Ă  partir de la fonction.

Le constructeur de la classe Option accepte un paramÚtre obligatoire (nom de l'option) et un nombre suffisamment important de paramÚtres facultatifs qui décrivent le type de valeur pour ce paramÚtre, la valeur par défaut, la description du paramÚtre et quelques autres. Cet exemple utilise les options suivantes pour le constructeur Option :

  • help , , sip-wheel -h
  • metavar — , , . metavar «DIR», , — .
  • default — . , , foo , ( ).
  • tools — , . sip-build, sip-install, sip-sdist sip-wheel, tools = ['build', 'install', 'sdist', 'wheel'].

La mĂ©thode apply_user_defaults surchargĂ©e suivante est conçue pour dĂ©finir des valeurs de paramĂštre que l'utilisateur peut transmettre via la ligne de commande. La mĂ©thode apply_user_defaults de la classe de base crĂ©e une variable (membre de classe) pour chaque paramĂštre de ligne de commande crĂ©Ă© dans la mĂ©thode get_options , il est donc important d'appeler la mĂ©thode de classe de base du mĂȘme nom avant d'utiliser les variables crĂ©Ă©es afin que toutes les variables crĂ©Ă©es Ă  l'aide des paramĂštres de ligne de commande soient crĂ©Ă©es et initialisĂ©es avec des valeurs par dĂ©faut . AprĂšs cela, dans notre exemple, les variables self.foo_include_dir et self.foo_library_dir seront crĂ©Ă©es. Si l'utilisateur n'a pas spĂ©cifiĂ© les paramĂštres de ligne de commande correspondants, il prendra alors les valeurs par dĂ©faut en fonction des paramĂštres du constructeur de la classe Option (paramĂštre par dĂ©faut ). Si le paramĂštre par dĂ©faut n'est pas dĂ©fini, en fonction du type de la valeur de paramĂštre attendue, il sera initialisĂ© soit Aucun, soit une liste vide, ou 0.

Dans la méthode apply_user_defaults , nous nous assurons que les chemins dans les variables self.foo_include_dir et self.foo_library_dir sont toujours absolus. Cela est nécessaire pour qu'il ne dépende pas de ce que sera le dossier de travail au moment du démarrage de l'assemblage.

La derniĂšre mĂ©thode surchargĂ©e de cette classe est update.. Cette mĂ©thode est appelĂ©e lorsqu'il est nĂ©cessaire d'appliquer au projet les modifications apportĂ©es auparavant. Par exemple, modifiez ou ajoutez les paramĂštres spĂ©cifiĂ©s dans le fichier pyproject.toml . Dans les exemples prĂ©cĂ©dents, nous avons dĂ©fini les chemins d'accĂšs aux fichiers d'en-tĂȘte et d'objet Ă  l'aide des paramĂštres include-dirs et library-dirs , respectivement, dans la section [tool.sip.bindings.pyfoo] . Maintenant, nous allons dĂ©finir ces paramĂštres Ă  partir du script project.py , donc dans le fichier pyproject.toml , nous supprimerons ces paramĂštres:

[build-system]
requires = ["sip >=5, <6"]
build-backend = "sipbuild.api"

[tool.sip.metadata]
name = "pyfoo"
version = "0.3"
license = "MIT"

[tool.sip.bindings.pyfoo]
headers = ["foo.h"]
libraries = ["foo"]

Dans la méthode mise à jour nous du dictionnaire self.bindings claveté pyfoo gat exemple sipbuild.Bindings . Le nom de la clé correspond aux [tool.sip.bindings. pyfoo ] du fichier pyproject.toml , et l'instance de classe ainsi obtenue décrit les paramÚtres décrits dans cette section. Ensuite, les membres de cette classe include_dirs et library_dirs (les noms des membres correspondent aux paramÚtres include-dirs et library-dirs avec un trait d'union remplacé par des traits de soulignement) reçoivent des listes contenant les chemins stockés dans les membres self.foo_include_dir et self.foo_library_dir. Dans cet exemple, pour plus de précision, nous vérifions que les valeurs self.foo_include_dir et self.foo_library_dir ne sont pas égales à None , mais dans cet exemple, cette condition est toujours remplie car les paramÚtres de ligne de commande que nous avons créés ont des valeurs par défaut.

Ainsi, nous avons prĂ©parĂ© les fichiers de configuration afin que lors de l'assemblage, il soit possible d'indiquer les chemins d'accĂšs aux fichiers d'en-tĂȘte et d'objet. VĂ©rifions ce qui s'est passĂ©.

Tout d'abord, assurez-vous que les valeurs par défaut fonctionnent. Pour ce faire, accédez au dossier pyfoo_c_05 / foo et créez la bibliothÚque à l'aide de la commande make , car nous avons désactivé la génération automatique de bibliothÚque dans cet exemple.

AprĂšs cela, allez dans le dossierpyfoo_c_05 et exĂ©cutez la commande sip-wheel . À la suite de cette commande, le fichier pyfoo-0.5-cp38-cp38-manylinux1_x86_64.whl ou avec un nom similaire sera crĂ©Ă© . DĂ©placez

maintenant le dossier foo quelque part en dehors du dossier pyfoo_c_05 et exécutez à nouveau la commande sip-wheel . Par conséquent, nous obtenons l'erreur attendue informant que nous n'avons pas de fichier de bibliothÚque d'objets:

usr/bin/ld:   -lfoo
collect2: :  ld     1
sip-wheel: Unable to compile the 'foo' module: command 'g++' failed with exit status 1

AprÚs cela, exécutez sip-wheel en utilisant la nouvelle option de ligne de commande:

sip-wheel --foo-include-dir ".../foo" --foo-library-dir ".../foo/bin"

Au lieu des points de suspension, vous devez spĂ©cifier le chemin d'accĂšs au dossier dans lequel vous avez dĂ©placĂ© le dossier foo avec la bibliothĂšque assemblĂ©e. Par consĂ©quent, l'assembly doit rĂ©ussir Ă  crĂ©er le fichier .whl. Le module crĂ©Ă© peut ĂȘtre installĂ© et testĂ© de la mĂȘme maniĂšre que dans les sections prĂ©cĂ©dentes.

Vérifiez l'ordre des méthodes d'appel à partir de project.py


L'exemple suivant, que nous considĂ©rerons, sera trĂšs simple; il montrera l'ordre d'appeler les mĂ©thodes de la classe Project , que nous avons surchargĂ©es dans les sections prĂ©cĂ©dentes. Cela peut ĂȘtre utile pour comprendre quand les variables peuvent ĂȘtre initialisĂ©es. Cet exemple se trouve dans le dossier pyfoo_c_06 du rĂ©fĂ©rentiel source.

L'essence de cet exemple est de surcharger toutes les méthodes que nous avons utilisées auparavant dans la classe FooProject , qui se trouve dans le fichier project.py , et de leur ajouter des appels à la fonction d' impression , qui afficheraient le nom de la méthode dans laquelle il se trouve:

from sipbuild import Project

class FooProject(Project):
    def get_options(self):
        print('get_options()')
        options = super().get_options()
        return options

    def apply_user_defaults(self, tool):
        print('apply_user_defaults()')
        super().apply_user_defaults(tool)

    def apply_nonuser_defaults(self, tool):
        print('apply_nonuser_defaults()')
        super().apply_nonuser_defaults(tool)

    def update(self, tool):
        print('update()')
        super().update(tool)

    def build(self):
        print('build()')
        super().build()

    def build_sdist(self, sdist_directory):
        print('build_sdist()')
        return super().build_sdist(sdist_directory)

    def build_wheel(self, wheel_directory):
        print('build_wheel()')
        return super().build_wheel(wheel_directory)

    def install(self):
        print('install()')
        super().install()

Les lecteurs attentifs doivent noter qu'en plus des mĂ©thodes prĂ©cĂ©demment utilisĂ©es, la mĂ©thode apply_nonuser_defaults () , dont nous n'avons pas parlĂ© auparavant , est surchargĂ©e dans cet exemple . Cette mĂ©thode recommande de dĂ©finir des valeurs par dĂ©faut pour toutes les variables qui ne peuvent pas ĂȘtre modifiĂ©es via les paramĂštres de ligne de commande.

Dans le fichier pyproject.toml, renvoyez le chemin explicite vers la bibliothĂšque:

[build-system]
requires = ["sip >=5, <6"]
build-backend = "sipbuild.api"

[tool.sip.metadata]
name = "pyfoo"
version = "0.4"
license = "MIT"

[tool.sip.bindings.pyfoo]
headers = ["foo.h"]
libraries = ["foo"]
include-dirs = ["foo"]
library-dirs = ["foo/bin"]

Pour que le projet réussisse, vous devez aller dans le dossier foo et y construire la bibliothÚque à l'aide de la commande make . Ensuite, retournez dans le dossier pyfoo_c_06 et exécutez, par exemple, la commande sip-wheel . Par conséquent, si vous ignorez les avertissements du compilateur, le texte suivant s'affiche:

get_options ()
apply_nonuser_defaults ()
get_options ()
get_options ()
apply_user_defaults ()
get_options ()
update ()
Ces liaisons seront créées: pyfoo.
build_wheel ()
Génération des liaisons pyfoo ...
Compilation du module 'foo' ...
La roue a été construite.

Des lignes en gras sont affichĂ©es qui sont sorties de notre fichier project.py . Ainsi, nous voyons que la mĂ©thode get_options est appelĂ©e plusieurs fois, et cela doit ĂȘtre pris en compte si vous allez initialiser une variable membre de la classe dĂ©rivĂ©e de Project . La mĂ©thode get_options n'est pas le meilleur endroit pour cela.

Il est également utile de se rappeler que la méthode apply_nonuser_defaults est appelée avant la méthode apply_user_defaults , c'est-à-dire dans la méthode apply_user_defaults , il est déjà possible d'utiliser des variables dont les valeurs sont définies dans la méthode apply_nonuser_defaults .

AprÚs cela, la méthode de mise à jour est appelée, et à la toute fin, la méthode directement responsable de l'assemblage, dans notre cas, build_wheel .

Conclusion de la premiĂšre partie


Dans cet article, nous avons commencé à étudier l'outil SIP conçu pour créer des liaisons Python pour des bibliothÚques écrites en C ou C ++. Dans cette premiÚre partie de l'article, nous avons examiné les bases de l'utilisation de SIP en utilisant l'exemple de création d'une liaison Python pour une bibliothÚque trÚs simple écrite en C.

Nous avons dĂ©terminĂ© les fichiers que vous devez crĂ©er pour travailler avec SIP. Le fichier pyproject.toml contient des informations sur le package (nom, numĂ©ro de version, licence et chemins d'accĂšs aux fichiers d'en-tĂȘte et d'objet). En utilisant le fichier project.py , vous pouvez influencer le processus de construction du package Python, par exemple, commencer Ă  construire la bibliothĂšque C ou laisser l'utilisateur spĂ©cifier l'emplacement des fichiers d'en-tĂȘte et d'objet de la bibliothĂšque.

Dans le fichier * .sipdécrit l'interface du module Python listant les fonctions et classes qui seront contenues dans le module. Des directives et des annotations sont utilisées pour décrire l'interface dans le fichier * .sip .

Dans la deuxiĂšme partie de l'article, nous allons crĂ©er une liaison sur une bibliothĂšque orientĂ©e objet Ă©crite en C ++, et sur son exemple, nous Ă©tudierons des techniques qui seront utiles pour dĂ©crire l'interface des classes C ++, et en mĂȘme temps nous traiterons de nouvelles directives et annotations pour nous.

À suivre.

Références



All Articles