Développer des applications utilisant les API des
bibliothèques Subversion est plutôt simple. Subversion est d'abord
un ensemble de bibliothèques en langage C, avec des fichiers
d'en-têtes (.h
) situés dans le répertoire
subversion/include
de l'arborescence des
sources. Ces en-têtes sont copiés dans votre arborescence système
(par exemple /usr/local/include
)
quand vous compilez et installez Subversion à partir des sources.
Ces en-têtes contiennent l'ensemble des fonctions et des types
censés être accessibles aux utilisateurs des bibliothèques
Subversion. La communauté des développeurs Subversion apporte
beaucoup d'attention à la disponibilité et la qualité de la
documentation des API publiques — reportez-vous directement
aux fichiers d'en-têtes pour cette documentation.
Quand vous examinez les fichiers d'en-tête publics, la
première chose que vous remarquez est que les types de données
et les fonctions ont un espace de nommage réservé. Cela veut dire
que tous les noms de symboles Subversion publics commencent par
svn_
, suivi d'un code indiquant la bibliothèque
dans laquelle le symbole est défini (par exemple
wc
, client
,
fs
, etc.), suivi d'un unique caractère
souligné (_
) puis du reste du nom du symbole. Les
fonctions semi-publiques (utilisées par plusieurs fichiers au sein
d'une bibliothèque mais pas par du code extérieur à cette
bibliothèque, on peut les trouver au sein des répertoires de la
bibliothèque) suivent une règle de nommage légèrement différente
dans le sens où, au lieu d'un unique caractère souligné
après le code indiquant la bibliothèque, elles utilisent deux
caractères souligné consécutifs (_ _
).
Les fonctions qui sont propres à un fichier source (c'est-à-dire
privées) n'ont pas de préfixe particulier et sont déclarées avec
le mot-clé static
. Bien sûr, un compilateur n'a
que faire de ces conventions de nommage, mais elles sont une aide
précieuse pour clarifier la portée d'une fonction ou d'un type de
données particuliers.
Une autre bonne source d'informations sur la programmation avec les API Subversion est constituée par les bonnes pratiques de programmation au sein du projet lui-même, que vous pouvez trouver à l'adresse suivante http://subversion.apache.org/docs/community-guide/ (pages en anglais). Ce document contient des informations particulièrement utiles qui, bien que destinées aux développeurs (ou aux personnes désireuses de le devenir) de Subversion lui-même, peuvent également servir à tous ceux qui développent des applications utilisant Subversion comme bibliothèque tierce [55].
À côté des types de données propres à Subversion, vous
trouverez de nombreuses références à des types de données qui
commencent par apr_
: ce sont les
symboles de la bibliothèque pour la portabilité d'Apache
(Apache Portable Runtime en
anglais, soit APR). APR est un jeu de bibliothèques Apache,
originellement extraites du code source du serveur
pour essayer de séparer ce qui dépendait du système
d'exploitation de ce qui n'en dépendait pas. Au final, on
obtient une bibliothèque qui fournit une API permettant
d'effectuer des opérations qui changent un peu (ou beaucoup) en
fonction du système d'exploitation. Alors que le serveur HTTP
Apache était le premier utilisateur (et pour cause) de la
bibliothèque APR, les développeurs Subversion ont immédiatement
perçu les avantages qu'il y a à utiliser APR. Cela signifie
qu'il n'y a pratiquement aucun code spécifique à un système
d'exploitation dans Subversion en tant que tel. Cela veut aussi
dire que le client Subversion peut être compilé et exécuté
partout où un serveur Apache peut l'être. Actuellement, cette
liste comprend toutes les variantes d'Unix, Win32, BeOS, OS/2 et
Mac OS X.
En plus de fournir des implémentations fiables des appels
systèmes qui diffèrent d'un système d'exploitation à l'autre
[56],
APR fournit à Subversion un accès direct à de nombreux types de
données personnalisés tels que les tableaux dynamiques et les
tables de hachage. Subversion utilise abondamment ces types de
données et le type de données APR le plus utilisé, que l'on
retrouve dans presque tous les prototypes de l'API Subversion,
est apr_pool_t
— le réservoir de
mémoire (memory pool en anglais)
APR. Subversion utilise les réservoirs de mémoire
en interne pour tous ses besoins d'allocation mémoire
(à moins qu'une bibliothèque externe ne requière
un autre mécanisme de gestion de la mémoire pour les
données transmises via son API)
[57]
et, bien qu'une personne qui utilise l'API Subversion ne soit
pas obligée d'en faire autant, elle doit fournir des réservoirs
aux fonctions de l'API qui en ont besoin. Cela implique que les
utilisateurs de l'API Subversion doivent
également inclure l'APR lors de l'édition de liens, doivent
appeler apr_initialize()
pour initialiser
le sous-système APR et doivent ensuite créer et gérer des
réservoirs de mémoire pour les appels à l'API Subversion,
généralement en utilisant svn_pool_create()
,
svn_pool_clear()
et
svn_pool_destroy()
.
Subversion a été conçu pour effectuer à distance des
opérations de gestion de versions. À ce titre, les possibilités
d'internationalisation (i18n) ont fait l'objet d'une attention
toute particulière. Après tout, « à distance » peut
vouloir dire depuis un ordinateur situé « dans le même
bureau », mais aussi « à l'autre bout de la
planète ». Pour faciliter cette prise en compte, toutes
les interfaces publiques de Subversion qui acceptent des chemins
comme argument s'attendent à ce que ces chemins soient rendus
canoniques — la façon la plus facile de le faire étant de
les passer en argument à la fonction
svn_path_canonicalize()
— et codés
dans le format UTF-8. Cela signifie, par exemple, que tout
nouveau programme client qui pilote l'interface
libsvn_client
doit d'abord convertir les
chemins depuis le codage local vers UTF-8 avant de fournir ces
chemins à la bibliothèque Subversion, puis doit reconvertir tout
chemin renvoyé par Subversion vers le codage local avant
d'utiliser ce chemin à des fins externes à Subversion.
Heureusement, Subversion fournit un ensemble de fonctions (voir
subversion/include/svn_utf.h
) que tout
programme peut utiliser pour réaliser ces conversions.
De plus, les API Subversion demandent que toutes les URL
passées en paramètres respectent le format URI. Ainsi, au lieu
de désigner par file:///home/utilisateur/Mon fichier.txt
l'URL d'un fichier nommé Mon fichier.txt
situé dans le répertoire home/utilisateur
,
vous devez utiliser
file:///home/utilisateur/Mon%20fichier.txt
. Là
encore, Subversion fournit des fonctions utiles à votre
application — svn_path_uri_encode()
et svn_path_uri_decode()
pour coder et
décoder, respectivement, des URI.
Si vous désirez utiliser les bibliothèques Subversion à
partir d'un autre langage que le C (par exemple un programme
Python ou Perl), Subversion offre cette possibilité via le
générateur simplifié d'interface et d'encapsulation
(Simplified Wrapper and Interface
Generator ou SWIG en anglais).
Les interfaces SWIG de Subversion sont situées
dans le répertoire subversion/bindings/swig
.
Elles sont toujours en cours d'évolution mais sont utilisables.
Elles vous permettent d'appeler les fonctions de l'API
Subversion indirectement, en utilisant des interfaces qui
traduisent les types de données natifs de votre langage de
programmation vers les types de données utilisés par les
bibliothèques C de Subversion.
Des efforts significatifs ont été fournis pour produire des interfaces SWIG pleinement fonctionnelles pour Python, Perl et Ruby. D'une certaine manière, le travail effectué pour réaliser les interfaces vers ces langages est réutilisable pour produire des interfaces vers d'autres langages supportés par SWIG (ce qui inclut, entre autres, des versions de C#, Guile, Java, MzScheme, OCaml, PHP et Tcl). Cependant, vous aurez besoin d'un peu de programmation supplémentaire pour aider SWIG à faire les traductions entre les langages pour les API complexes. Pour plus d'informations sur SWIG lui-même, visitez le site Web du projet à l'adresse suivante : http://www.swig.org/ (site en anglais).
Subversion fournit également une interface vers le langage
Java. L'interface javahl (située dans
subversion/bindings/java
dans
l'arborescence des sources Subversion) n'est pas basée sur SWIG
mais est un mélange de Java et de JNI codé à la main. Javahl
couvre le plus gros des API du client Subversion et se destine
principalement aux développeurs d'environnements de
développement intégrés (IDE) et de clients Subversion en
Java.
Les interfaces Subversion vers les langages de programmation ne sont pas suivies avec le même niveau d'exigence que les modules du cœur de Subversion, mais peuvent généralement être utilisées en production. De nombreuses applications, de nombreux scripts, des clients graphiques alternatifs et des outils tiers utilisent aujourd'hui sans problème les interfaces vers les langages de programmation afin d'intégrer les fonctionnalités de Subversion.
Veuillez tout de même noter qu'il existe d'autres options pour s'interfacer avec Subversion dans d'autres langages : les interfaces pour Subversion qui ne sont pas fournies par la communauté de développement Subversion. Vous pouvez trouver des liens vers ces interfaces alternatives sur la page de liens externes du projet Subversion (à l'adresse http://subversion.tigris.org/links.html) et, en particulier, nous accordons une mention spéciale à deux d'entre elles. D'abord, l'interface PySVN de Barry Scott (http://pysvn.tigris.org/) est une interface reconnue vers Python. PySVN se targue d'une interface plus « pythonique » que les API « orientées C » fournies par l'interface standard de Subversion vers Python. Et si vous recherchez une implémentation 100 % Java de Subversion, jetez un œil à SVNKit (http://svnkit.com/), qui est une ré-écriture complète de Subversion en Java.
L'Exemple 8.1, « Utilisation de la couche dépôt »
contient un bout de code (écrit en C) qui illustre plusieurs
concepts que nous venons d'aborder. Il utilise à la fois
l'interface du dépôt et celle du système de fichiers (comme
dénoté par les préfixes svn_repos_
et
svn_fs_
des noms de fonctions) pour créer une
nouvelle révision dans laquelle un répertoire est ajouté. Vous
pouvez y observer l'utilisation du réservoir de mémoire APR qui
est utilisé pour les besoins d'allocation mémoire. En outre, le
code révèle le côté obscur de la gestion des erreurs de
Subversion : toutes les erreurs Subversion doivent être
explicitement prises en compte pour éviter des fuites de mémoire
(et dans certains cas, le plantage de l'application).
Exemple 8.1. Utilisation de la couche dépôt
/* Convertit une erreur Subversion en un simple code d'erreur booléen * * NOTE: Les erreurs Subversion doivent être effacées (en utilisant * svn_error_clear()) parce qu'elles sont allouées depuis le * réservoir global, sinon cela produit une fuite de mémoire. */ #define INT_ERR(expr) \ do { \ svn_error_t *__temperr = (expr); \ if (__temperr) \ { \ svn_error_clear(__temperr); \ return 1; \ } \ return 0; \ } while (0) /* Crée un nouveau répertoire NOUVEAU_REP dans le dépôt Subversion * situé à CHEMIN_DEPOT. Effectue toutes les allocations mémoire dans * RESERVOIR. Cette fonction créera une nouvelle révision pour l'ajout * de NOUVEAU_REP. Elle retourne zéro si l'opération se termine * correctement, une valeur différente de zéro sinon. */ static int cree_nouveau_rep(const char *chemin_depot, const char *nouveau_rep, apr_pool_t *reservoir) { svn_error_t *err; svn_repos_t *depot; svn_fs_t *fs; svn_revnum_t derniere_rev; svn_fs_txn_t *transaction; svn_fs_root_t *racine_transaction; const char *chaine_conflit; /* Ouvre le dépôt situé à chemin_depot. */ INT_ERR(svn_repos_open(&depot, chemin_depot, reservoir)); /* Obtient un pointeur sur l'objet du système de fichiers qui est * stocké dans CHEMIN_DEPOT. */ fs = svn_repos_fs(depot); /* Demande au système de fichiers de nous fournir le numéro de la * révision la plus récente. */ INT_ERR(svn_fs_youngest_rev(&derniere_rev, fs, reservoir)); /* Commence une nouvelle transaction qui est basée sur DERNIERE_REV. * Nous aurons moins de chance de voir notre propagation rejetée pour * cause de conflit si nous effectuons toujours nos changements à partir du * dernier instantané de l'arborescence du système de fichiers. */ INT_ERR(svn_repos_fs_begin_txn_for_commit2(&transaction, depot, derniere_rev, apr_hash_make(reservoir), reservoir)); /* Maintenant qu'une nouvelle transaction Subversion est commencée, * obtient l'objet racine qui représente cette transaction. */ INT_ERR(svn_fs_txn_root(&racine_transaction, transaction, reservoir)); /* Crée un nouveau répertoire sous la racine de la transaction, au * chemin NOUVEAU_REP. */ INT_ERR(svn_fs_make_dir(racine_transaction, nouveau_rep, reservoir)); /* Propage la transaction, créant une nouvelle révision du système de * fichiers incluant le nouveau répertoire. */ err = svn_repos_fs_commit_txn(&chaine_conflit, depot, &derniere_rev, transaction, reservoir); if (! err) { /* Pas d'erreur ? Excellent ! Indique brièvement la réussite * de l'opération. */ printf("Le répertoire '%s' a été ajouté en tant que nouvelle " "révision '%ld'.\n", nouveau_rep, derniere_rev); } else if (err->apr_err == SVN_ERR_FS_CONFLICT) { /* Oh-oh. La propagation a échoué pour cause de conflit (il semble * que quelqu'un d'autre a effectué des changements dans la même * zone du système de fichiers que celle que nous avons essayé de * modifier). Affiche un message d'erreur. */ printf("Un conflit s'est produit pour le chemin '%s' lors de" " l'ajout du répertoire '%s' au dépôt '%s'.\n", chaine_conflit, nouveau_rep, chemin_depot); } else { /* Une autre erreur s'est produite. Affiche un message d'erreur. */ printf("Une erreur s'est produite lors de l'ajout du " "répertoire '%s' au dépôt '%s'.\n", nouveau_rep, chemin_depot); } INT_ERR(err); }
Notez que dans l'Exemple 8.1, « Utilisation de la couche dépôt », le code aurait
tout aussi bien pu propager la transaction en utilisant
svn_fs_commit_txn()
. Mais l'API du système
de fichiers ignore tout des mécanismes de procédures
automatiques de la bibliothèque du dépôt. Si vous voulez que
votre dépôt Subversion effectue automatiquement certaines tâches
externes à Subversion chaque fois qu'une transaction est
propagée (par exemple envoyer un mail qui décrit les changements
effectués dans la transaction à la liste de diffusion des
développeurs), vous devez utiliser la version de la fonction
encapsulée dans libsvn_repos
qui ajoute la
fonctionnalité d'activation des procédures automatiques :
svn_repos_fs_commit_txn()
(pour davantage
d'informations sur les procédures automatiques des dépôts
Subversion, consultez la section intitulée « Mise en place des procédures automatiques »).
Maintenant, changeons de langage. L'Exemple 8.2, « Utilisation de la couche dépôt en Python » est un programme de démonstration qui utilise l'interface SWIG vers Python pour parcourir récursivement la dernière révision du dépôt et afficher les différents chemins trouvés lors de ce parcours.
Exemple 8.2. Utilisation de la couche dépôt en Python
#!/usr/bin/python """Parcourir un dépôt en affichant les chemins des objets suivis en versions.""" import sys import os.path import svn.fs, svn.core, svn.repos def parcourir_rep_systemedefichiers(racine, repertoire): """Parcourt récursivement le REPERTOIRE situé sous RACINE dans le système de fichiers. Renvoie la liste de tous les chemins sous et de REPERTOIRE.""" # Affiche le nom de ce chemin. print repertoire + "/" # Obtient les entrées du répertoire REPERTOIRE. entrees = svn.fs.svn_fs_dir_entries(racine, repertoire) # Pour chaque entrée noms = entrees.keys() for nom in noms: # Calcule le chemin complet de l'entrée. chemin_complet = repertoire + '/' + nom # Si l'entrée est un répertoire, effectue une récursion. La # récursion retournera une liste comprenant l'entrée et tous ses # enfants, que l'on ajoutera à notre liste. if svn.fs.svn_fs_is_dir(racine, chemin_complet): parcourir_rep_systemedefichiers(racine, chemin_complet) else: # Sinon, c'est un fichier donc l'afficher maintenant. print chemin_complet def parcourir_la_plus_recente_revision(chemin_depot): """Ouvre le dépôt situé à CHEMIN_DEPOT et effectue un parcours récursif de la révision la plus récente.""" # Ouvre le dépôt situé à CHEMIN_DEPOT et obtient une référence de # son système de fichiers suivi en versions. objet_depot = svn.repos.svn_repos_open(chemin_depot) objet_fs = svn.repos.svn_repos_fs(objet_depot) # Obtient la révision la plus récente (HEAD). rev_la_plus_recente = svn.fs.svn_fs_youngest_rev(objet_fs) # Ouvre un objet racine représentant la révision la plus récente. objet_racine = svn.fs.svn_fs_revision_root(objet_fs, rev_la_plus_recente) # Effectue le parcours récursif. parcourir_rep_systemedefichiers(objet_racine, "") if __name__ == "__main__": # Vérifie que l'on est appelé correctement. if len(sys.argv) != 2: sys.stderr.write("Usage: %s CHEMIN_DEPOT\n" % (os.path.basename(sys.argv[0]))) sys.exit(1) # Transforme la chaîne en chemin canonique. chemin_depot = svn.core.svn_path_canonicalize(sys.argv[1]) # Et c'est parti ! parcourir_la_plus_recente_revision(chemin_depot)
Le même programme en C aurait besoin de faire appel aux réservoirs de mémoire d'APR. Mais Python gère l'utilisation de la mémoire automatiquement et l'interface Subversion vers Python se plie à cette convention. En C, vous auriez utilisé des types de données personnalisés (tels que ceux fournis par la bibliothèque APR) pour représenter la table de hachage des entrées et la liste des chemins, mais Python sait gérer nativement les tables de hachage (appelés « dictionnaires ») ainsi que les listes et possède une riche collection de fonctions pour travailler sur ces types de données. C'est pourquoi SWIG (avec l'aide de la couche d'interface vers les langages de programmation de Subversion, un peu modifiée) prend soin de faire correspondre ces types de données personnalisés aux types de données natifs du langage cible. On obtient ainsi une interface plus intuitive pour les utilisateurs de ce langage.
L'interface de Subversion vers Python peut également être
utilisée pour effectuer des opérations dans la copie de travail.
Dans la section précédente de ce chapitre, nous avons mentionné
l'interface libsvn_client
et le fait
qu'elle a été conçue dans le seul but de faciliter l'écriture
d'un client Subversion. L'Exemple 8.3, « Une version de status en Python » est un court
exemple d'utilisation de cette bibliothèque via l'interface
Python SWIG pour re-créer une version à petite échelle de la
commande svn status.
Exemple 8.3. Une version de status en Python
#!/usr/bin/env python """Parcourir un répertoire d'une copie de travail en affichant les informations d'état.""" import sys import os.path import getopt import svn.core, svn.client, svn.wc def generer_code_etat(etat): """Traduit la valeur d'état vers un code à un caractère en utilisant la même logique que le client Subversion en ligne de commande.""" association_etat = { svn.wc.svn_wc_status_none : ' ', svn.wc.svn_wc_status_normal : ' ', svn.wc.svn_wc_status_added : 'A', svn.wc.svn_wc_status_missing : '!', svn.wc.svn_wc_status_incomplete : '!', svn.wc.svn_wc_status_deleted : 'D', svn.wc.svn_wc_status_replaced : 'R', svn.wc.svn_wc_status_modified : 'M', svn.wc.svn_wc_status_merged : 'G', svn.wc.svn_wc_status_conflicted : 'C', svn.wc.svn_wc_status_obstructed : '~', svn.wc.svn_wc_status_ignored : 'I', svn.wc.svn_wc_status_external : 'X', svn.wc.svn_wc_status_unversioned : '?', } return association_etat.get(etat, '?') def trouver_etat(chemin_copie_travail, verbeux): # Construit le "bâton" de contexte client. ctx = svn.client.svn_client_ctx_t() def _status_callback(path, etat): """Une fonction de renvoi ("callback") pour svn_client_status.""" # Affiche le chemin, moins la partie déjà présente # dans la racine du parcours. text_status = generer_code_etat(etat.text_status) prop_status = generer_code_etat(etat.prop_status) print '%s%s %s' % (text_status, prop_status, path) # Effectue le parcours des états, en utilisant _status_callback() # comme fonction de renvoi ("callback"). revision = svn.core.svn_opt_revision_t() revision.type = svn.core.svn_opt_revision_head svn.client.svn_client_status2(chemin_copie_travail, revision, _status_callback, svn.core.svn_depth_infinity, verbeux, 0, 0, 1, ctx) def utilisation_et_sortie(code_erreur): """Affiche le message d'utilisation et sort avec CODE_ERREUR.""" stream = code_erreur and sys.stderr or sys.stdout stream.write("""Usage: %s OPTIONS CHEMIN_COPIE_TRAVAIL Options: --help, -h : Affiche ce message d'aide. --verbose, -v : Affiche l'état de tous les objets, sans exception. """ % (os.path.basename(sys.argv[0]))) sys.exit(code_erreur) if __name__ == '__main__': # Analyse les options de la ligne de commande. try: opts, args = getopt.getopt(sys.argv[1:], "hv", ["help", "verbose"]) except getopt.GetoptError: utilisation_et_sortie(1) verbeux = 0 for opt, arg in opts: if opt in ("-h", "--help"): utilisation_et_sortie(0) if opt in ("-v", "--verbeux"): verbeux = 1 if len(args) != 1: utilisation_et_sortie(2) # Transforme le chemin en chemin canonique. chemin_copie_travail = svn.core.svn_path_canonicalize(args[0]) # Et c'est parti ! try: trouver_etat(chemin_copie_travail, verbeux) except svn.core.SubversionException, e: sys.stderr.write("Erreur (%d): %s\n" % (e.apr_err, e.message)) sys.exit(1)
Comme dans le cas de l'Exemple 8.2, « Utilisation de la couche dépôt en Python », ce
programme voit sa mémoire gérée automatiquement et utilise en
grande partie les types de données classiques de Python. L'appel
de svn_client_ctx_t()
est un peu trompeur
parce que l'API publique de Subversion ne possède pas de telle
fonction — la génération automatique de fonctions de SWIG
( une sorte d'usine à fonctions pour transformer des structures
C complexes vers un équivalent en Python) est à la peine.
Notez également que le chemin passé au programme
(tout comme dans le programme précédent) est mouliné par
svn_path_canonicalize()
car,
dans le cas contraire, on s'expose à un
arrêt rapide et brutal du programme par la bibliothèque C
Subversion sous-jacente qui effectue des tests de
conformité.