Thématiques principales

jeudi 1 mars 2018

Pourquoi le model checking ?


Dans cet article, je vous propose de revenir sur la problématique de modélisation des systèmes concurrents et communicants. Ayant traité le sujet des systèmes à événement discrets [1] dans un article précédent, nous avions vu qu’il était possible de modéliser ces systèmes à l’aide de systèmes à transitions.

En effet, la modélisation des composantes communicantes et concurrentes peut se traiter par une modélisation a base de multiple Sed [2]en utilisant des architectures spécifiques comme celle de la théorie du contrôle par supervision de Ramadge et Wonham [3].

Ces approches sont intéressantes car elles permettent l'élaboration d’une solution en ligne du principe de contrôle commande. C’est a dire que le modèle du système va être supervisé par un modèle de contrôleur produit a partir d’une spécification des comportement autorisé. Le résultat est un ensemble d’interactions orientées selon le champs des possibles des comportements tolérés et ce, au fil de l'exécution.

Cette approche est typiquement une approche classique du contrôle commande et de l’automatique (un article dédié a l’approche générale du contrôle des systèmes en automatique sera réalisé prochainement) cependant elle comporte des limites et reste complexe à mettre en oeuvre. Surtout lorsque le système est en fait la composition des comportements d’un ensemble de sous systèmes eux même en interaction entre eux. Dans ce genre de situation, la complexité est lié a la combinatoire des comportements possibles et la modélisation de cette combinatoire est hautement sujette à l’erreur surtout si l’on cherche un modèle unique.

Face à ces problématiques, Il est alors important d'élaborer des stratégies afin de prendre en compte cette complexité avant la phase de mise en production et ces stratégies se trouvent dans le model-checking.

Ainsi le model checking [4] a pour intérêt de permettre l’analyse d’un système et de ses comportements selon ses différentes composantes locales et/ou globale en amont de sa mise en production selon différents critères afin de garantir l’absence de dead lock (etat dans lequel le système ne peut plus évoluer et revenir dans son état initial) ou de live lock (état dans lequel le système peut encore évoluer mais ne peut plus revenir dans son état initial). Ceci n’est pas le seul intérêt du model checking car il permet également de caractériser des propriétés dans un système et de valider des comportements souhaités.

Cette approche est pratique car elle n’a pas pour but de contraindre le système qui est alors toujours libre de fonctionner selon tous ses degrés de libertés mais juste de s’assurer que celui ci est la solution a notre besoin auquel cas alors il sera toujours possible d'élaborer une loi de commande qui elle même pourra aussi être validée par une phase de model checking.

Prenons le temps de détailler tout cela au travers d’un exemple.

Exemple

Considérons donc un robot avec quatre pattes. Chacune des pattes est l’association de deux moteurs pas à pas. L’un a la base de la patte permet d’avancer ou reculer celle ci et un autre a l’articulation permettant de lever la patte ou la reposer. C’est très simpliste mais cela permet quelques mouvements qui peuvent déjà devenir très complexe.

La combinaison de ces deux mouvement permettant alors de réaliser des mouvement de plus haut niveau tels que la protraction (1) qui consiste a lever la patte et l'amener vers un point en avant en suivant une trajectoire curviligne et la rétraction (2) qui consiste a ramener la patte dans sa position initiale en suivant une droite



Nous utiliserons l’outil de model-checking LTSA [5] (qui s’appuie sur la notation FSP, Finite state Process [6]) pour effectuer la modélisation de notre système et effectuer le model checking

En FSP, la modélisation du comportement d’une pattes est donc l’association d’un moteur haut bas et d’un moteur avant arrière:
MoteurHB=(lever -> poser -> MoteurHB).


MoteurAR=(avancer -> reculer -> MoteurAR).


|| PATTE =(MoteurHB || MoteurAR).

A partir de ce système nous pouvons imaginer l’ensemble des comportements possibles de notre robot en déclinant ce modèle de patte en 4 exemplaires.
||PATTES = ({p1,p2,p3,p4}:PATTE).
Représenter ce modèle n'aurait pas beaucoup d'intérêt car déjà trop gros pour être seulement pertinent mais pour se rendre compte, ce modèle possède déjà 256 états et 512 transitions.

Nous sommes face à une complexité de type exponentielle O^n


Moteurs
1
2
4
6
8 (4 pattes)
Nombre Etats
2
2^2=4
2^4=32
2^6=64
2^8=256
Nombre Transitions
4
2*2^2=8
2*2^4=64
2*2^6=128
2*2^8=512
Pour juste nous rendre compte pour un système à 2 pattes le modèle correspondant serait celui ci:


Il faut noter que cet ensemble de comportement n’est pas celui attendu, il est beaucoup plus grand que nécessaire car pour chaque patte, il existe des comportement ne respectant pas les règles de protraction et de retraction. On définit une propriété de marche pour les mettre en évidence.

property MARCHEG = ({p1.lever,p3.lever} -> {p1.avancer,p3.avancer} -> {p1.poser,p3.poser} -> {p1.reculer,p3.reculer} -> MARCHEG). 
property MARCHED = ({p2.lever,p4.lever} -> {p2.avancer,p4.avancer} -> {p2.poser,p4.poser} -> {p2.reculer,p4.reculer} -> MARCHED).
|| TEST=(PATTES || MARCHEG). 
Compiled: MoteurHB 
Compiled: MoteurAR 
Compiled: MARCHEG 
Composition: 
TEST = PATTES.p1:PATTE.MoteurHB || PATTES.p1:PATTE.MoteurAR || PATTES.p2:PATTE.MoteurHB || PATTES.p2:PATTE.MoteurAR || PATTES.p3:PATTE.MoteurHB || PATTES.p3:PATTE.MoteurAR || PATTES.p4:PATTE.MoteurHB || PATTES.p4:PATTE.MoteurAR || MARCHEG 
State Space: 
2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 15 = 2 ** 12 
Composing... 
property MARCHEG violation. 
-- States: 176 Transitions: 1408 Memory used: 48580K 
Composed in 48ms

|| TEST2=(PATTES || MARCHED).
Compiled: MoteurHB
Compiled: MoteurAR
Compiled: MARCHED
Composition:
TEST2 = PATTES.p1:PATTE.MoteurHB || PATTES.p1:PATTE.MoteurAR || PATTES.p2:PATTE.MoteurHB || PATTES.p2:PATTE.MoteurAR || PATTES.p3:PATTE.MoteurHB || PATTES.p3:PATTE.MoteurAR || PATTES.p4:PATTE.MoteurHB || PATTES.p4:PATTE.MoteurAR || MARCHED 
State Space: 
2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 15 = 2 ** 12 
Composing... 
property MARCHED violation. 
-- States: 176 Transitions: 1408 Memory used: 47479K 
Composed in 16ms 

On voit ici que le logiciel nous dit que les comportements de nos pattes peuvent ne pas respecter ces règles de marches et avoir des comportements autres.

On donc les utiliser comme contrôleur et les coupler avec les pattes du robot:
CONTROLLEUR_MARCHEG = ({p1.lever,p3.lever} -> {p1.avancer,p3.avancer} -> {p1.poser,p3.poser} -> {p1.reculer,p3.reculer} -> CONTROLLEUR_MARCHEG).

CONTROLLEUR_MARCHED = ({p2.lever,p4.lever} -> {p2.avancer,p4.avancer} -> {p2.poser,p4.poser} -> {p2.reculer,p4.reculer} -> CONTROLLEUR_MARCHED).

||SYSTEM=(PATTES || CONTROLLEUR_MARCHEG || CONTROLLEUR_MARCHED).
Notre nouveau système que l’on peut schématiser comme suit:



Sur celui ci on va pouvoir vérifier maintenant nos précédents propriétés:
|| TEST3=(SYSTEM || MARCHEG).
Compiled: MoteurHB 
Compiled: MoteurAR 
Compiled: CONTROLLEUR_MARCHEG 
Compiled: CONTROLLEUR_MARCHED 
Compiled: MARCHEG
Composition: 
TEST3 = SYSTEM.PATTES.p1:PATTE.MoteurHB || SYSTEM.PATTES.p1:PATTE.MoteurAR || SYSTEM.PATTES.p2:PATTE.MoteurHB || SYSTEM.PATTES.p2:PATTE.MoteurAR || SYSTEM.PATTES.p3:PATTE.MoteurHB || SYSTEM.PATTES.p3:PATTE.MoteurAR || SYSTEM.PATTES.p4:PATTE.MoteurHB || SYSTEM.PATTES.p4:PATTE.MoteurAR || SYSTEM.CONTROLLEUR_MARCHEG || SYSTEM.CONTROLLEUR_MARCHED || MARCHEG
State Space: 
2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 15 * 15 * 15 = 2 ** 20
Composing... 
-- States: 121 Transitions: 308 Memory used: 37000K 
Composed in 61ms
|| TEST4=(SYSTEM || MARCHED).
Compiled: MoteurHB 
Compiled: MoteurAR 
Compiled: CONTROLLEUR_MARCHEG 
Compiled: CONTROLLEUR_MARCHED 
Compiled: MARCHED 
Composition: 
TEST4 = SYSTEM.PATTES.p1:PATTE.MoteurHB || SYSTEM.PATTES.p1:PATTE.MoteurAR || SYSTEM.PATTES.p2:PATTE.MoteurHB || SYSTEM.PATTES.p2:PATTE.MoteurAR || SYSTEM.PATTES.p3:PATTE.MoteurHB || SYSTEM.PATTES.p3:PATTE.MoteurAR || SYSTEM.PATTES.p4:PATTE.MoteurHB || SYSTEM.PATTES.p4:PATTE.MoteurAR || SYSTEM.CONTROLLEUR_MARCHEG || SYSTEM.CONTROLLEUR_MARCHED || MARCHED
State Space: 
2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 15 * 15 * 15 = 2 ** 20 
Composing... 
-- States: 121 Transitions: 308 Memory used: 36363K 
Composed in 26ms
Et conclure que nous n’avons plus de violations.

Bien sur cet exemple est très simpliste mais par l’explosion combinatoire de comportement, on se rend vite compte qu’il est difficile d’estimer même visuellement sur des structures “simple” que celles ci ont les comportement attendu et n’ont pas de comportements prohibés. A l’aide du model-checking, nous pouvons travailler de façon formelle sur le système en utilisant par exemple ici la notation FSP. A l’aide de la même notation, nous nous sommes restreint à la définition de propriétés très simple mais il reste possible d’aller plus loin en vérifiant des propriétés typique du model checking telles que
  • est ce qu’une propriété arrivera un jour ? 
  • est ce qu’une propriété est invariante ?
  • est ce qu’une propriété est récurrente ?
Pour répondre à ces questions, il faudra utiliser des logiques spécifiques comme LP [7], LTL[8], CTL [9] ou CTL* [10], des logiques permettant de spécifier plus précisément nos propriétés.

Cet article n'était qu’une introduction pour saisir les enjeux du model checking. Pour les prochains articles sur le sujet nous entrerons donc plus dans le détails des aspects formelles du model-checking.

Références

[1] http://un-est-tout-et-tout-est-un.blogspot.fr/2017/11/readysystemes-evenements-discrets.html
[2] http://un-est-tout-et-tout-est-un.blogspot.fr/2017/11/finite-state-machine.html
[3] http://un-est-tout-et-tout-est-un.blogspot.fr/2017/12/supervisory-control-theory.html
[4] https://ti.arc.nasa.gov/m/profile/dimitra/publications/thesis.pdf
[5] https://www.doc.ic.ac.uk/ltsa/
[6] https://www.doc.ic.ac.uk/~jnm/LTSdocumention/FSP-notation.html
[7] https://www.lri.fr/~paulin/Logique/html/cours004.html
[8] http://queinnec.perso.enseeiht.fr/Ens/ST/ltl.pdf
[9] http://queinnec.perso.enseeiht.fr/Ens/ST/ctl.pdf
[10] https://fr.wikipedia.org/wiki/CTL*

lundi 26 février 2018

Dépôt Debian signé

Dans un article précédent nous avions traité de la construction de dépôts debian. Basiquement nous nous étions contenté de l’initialisation du listing des packages du dépôt en effectuant un scanpackage [1].

Aujourd’hui de façon à rendre un peu plus fiable et sécurisé les paquets et fournir plus de garanties aux utilisateurs du dépôts, nous allons regarder comment signer les éléments du dépôts, mais nous allons aussi industrialiser un peu la méthode de production du dépôt.

Pour cela nous allons tout d’abord récupérer reprepro [2] et gpg2 [3-4]. On aurait pu faire sans reprepro mais bon, quand il existe un outil qui sait faire autant ne pas s’en priver [5-6].

Générer la clef pour signer le dépôt

Tout d’abord il faut construire une clef pour notre futur dépôt et on vérifie la présence de notre clef dans le trousseau

gpg2 --gen-key
gpg2 --list-keys

Puis on export cette clef dans répertoire accessible au travers de votre serveur apache (un chemin proche de l’url du futur repo par exemple)

gpg2 --armor --export votreNom@mail.com >> /var/www/pathInApache/key/keyName.gpg.key

Ici par exemple notre clef sera accessible à l'adresse http://localhost/pathInApache/key/keyName.gpg.key. Pour l’ajouter il faudra ajouter la clef publique en mode admin (en root ou en ajoutant des sudo

wget -O - http://hostserver/pathInApache/key/keyName.gpg.key | apt-key add -

Comme ça lorsque l’on aura a faire le apt-get update, apt sera capable de vérifier la signature, grâce à la clef.

Construction du dépôt

Une fois la clef configurée, il ne nous reste plus qu'à construire le dépôt pour mettre à disposition les paquets Debian. Pour ce faire, nous allons dans un premier temps utiliser reprepro qui va automatiser une très grosse partie du travail en partant d’une config et d’un répertoire de livraison dans lequel nous déposerons nos paquets à la suite de leur production.

Il nous faut tout d’abord définir un répertoire accessible via http donc un répertoire tel que /var/www/apt par exemple. Au passage dans ce répertoire on peut y placer notre répertoire key, qui contient la clef publique du dépôt.

Dans ce répertoire apt nous allons y créer un répertoire livraison dédié aux paquets Debian. A côté de celui ci nous allons également créer un répertoire conf dans lequel on va déposer un fichier que l’on nommera distributions. Ce fichier contiendra toutes les informations nécessaire à reprepro pour initialiser le dépôt et l’alimenter.

Ainsi ce fichier doit contenir les champs suivants [10]:

Origin: Votre nom ou un url 
Label: monLabel 
Suite: stable 
Codename: le nom code de votre projet comme wheeze ou xenial par exemple “monCode” 
Version: 1.0 
Architectures: i386 amd64 armhf 
Components: all non-free contrib 
Description: Un peu de blabla 
SignWith: yes

Le paramètre SignWith est essentiellement le plus important pour ce qui nous préoccupe, c’est a dire de faire un dépôt signé. A la place du yes, il est possible de mettre la clef GPG mais avec le yes, reprepro ira directement la chercher dans le trousseau.

Ainsi on va avoir l’arborescence suivante:
  • /var/www/apt/conf/distributions
  • /var/www/apt/key/keyName.gpg.key
  • /var/www/apt/livraison/
On met un Debian dans le répertoire livraison et on applique la commande suivante:


APT_DIR=”/var/www/apt”reprepro --dbdir $APT_DIR/db --confdir $APT_DIR/conf -b $APT_DIR/deb include monCode $APT_DIR/livraison/*.deb

reprepro va procéder a l’analyse du répertoire livraison en récupérer la dernière version des Debian présents et les classer dans un répertoire deb/pool. Dans ce même répertoire, il va constituer un répertoire dists dans lequel il déposera l'équivalent du scanpackage avec sa version signée qui permettra au client d’identifier notre dépôt avec la clef publique qu’il aura obtenu. Le répertoire db est un répertoire de travail de reprepro, nous ne nous attarderons pas dessus.

Bien sur pour à chaque nouvelle production des paquets, il faudra rappeler ce script. Pour cela, il est possible d’utiliser le daemon mini-dinstall [11-13]. Cela sera peut être l’occasion d’un autre article.

Pour les plus avisés qui voudront aller vraiment plus loin, je vous invite lire l’article de Vincent qui reprend un cas d’utilisation complet [9]

Note (26/08/2018) : attention a utiliser les commandes gpg2 avec sudo de facon a ce que les clefs soient accessibles lors de l'appel de reprepro qui se fait assez logiquement aussi en sudo (puisque c'est pour initialiser le depot.... )

Note 2 (26/08/2018) Sinon pour convertir de gpg a gpg2 et reciproqement:

gpg2 --export-secret-keys | gpg --import -
gpg --export-secret-keys | gpg2 --import -

Références

[1] https://un-est-tout-et-tout-est-un.blogspot.fr/2017/12/faire-un-depot-debian.html
[2] https://doc.ubuntu-fr.org/tutoriel/comment_creer_depot
[3] https://www.gnupg.org/
[4] https://doc.ubuntu-fr.org/gnupg#utilisation_et_configuration
[5] https://wiki.debian-fr.xyz/Faire_un_d%C3%A9pot_sign%C3%A9_ou_non
[6] http://blog.glehmann.net/2015/01/27/Creating-a-debian-repository/
[7] https://www.francoz.net/doc/gpg/x218.html
[8] http://www.serveur-linux.info/2012/01/depot-personnalise-paquets-debian/
[9] https://vincent.bernat.im/fr/blog/2014-depots-apt-locaux
[10] https://blog.packagecloud.io/eng/2017/03/23/create-debian-repository-reprepro/
[11] https://github.com/shartge/mini-dinstall
[12] https://manpages.debian.org/stretch/mini-dinstall/mini-dinstall.1.en.html
[13] https://debian-handbook.info/browse/fr-FR/stable/sect.setup-apt-package-repository.html

samedi 24 février 2018

Agilité: Combinatoire d'activité

Dans un article précédent [1] nous avons vu un modèle de processus de développement dans lequel je faisais intervenir 5 acteurs clefs responsables des axes majeurs intervenant dans le développement logiciel:
  • axe fonctionnel : le PO
  • axe technique : l’architecte
  • axe process : le scrum master
  • axe IVQ : le responsable IVQ (Intégration Validation Qualification)
  • axe savoir faire : le développeur


Je suis bien sûr très intéressé par votre opinion concernant mon avis sur le sujet. Alors n’hésitez pas à mettre des commentaires.

Références:


[1] http://un-est-tout-et-tout-est-un.blogspot.fr/2018/02/integration-continue.html

mercredi 21 février 2018

Intégration continue

Petit aparté pour parler un peu de l'intégration continu. Nous avions évoqué un peu le sujet dans l’article précédent [1] sur Gradle qui en est tout comme Maven [2] un élément et outil central à la construction d’un composant logiciel. Cependant, si nous avions décrit son rôle dans le processus de développement, nous n'avons pas décrit qu’elles sont les autres outils qui peuvent nous aider a automatiser l’ensemble du processus, nous aider à mener à bien l’entreprise de production logiciel complet, de A a Z.

Contextualisation

Repartons d’un processus de développement de base. Nous en avions vu quelques un dans l’article [3]. En voici un, sûrement perfectible, dans lequel nous utilisons une approche agile au sein de laquelle vont intervenir nos outils d'intégration continue.




Sans forcément faire un long discours explicatif des différents éléments de ce schéma (qui n’est qu’un modèle parmi d’autres possibles), nous pouvons décrire ce processus comme suit:

A partir de l’idée du client, sont définies un certain nombre de spécifications fonctionnelles plus ou moins raffinées. Ces spécification sont injectées, une fois suffisamment mature et dont la valeur ajoutée est considérée comme suffisamment significative, dans un certain nombre de Sprint sous la forme de Stories fonctionnelles et ou techniques. Le but final de l’approche étant de produire un certain nombre d'artefacts évoluant au fil d’un certain nombre de versions

Différents acteurs cités ici ont des rôles spécialisés selon les différents axes majeurs du développement logiciels :

  • axe fonctionnel : le PO
  • axe technique : l’architecte
  • axe process : le scrum master
  • axe IVQ : le responsable IVQ (Intégration Validation Qualification)
  • axe savoir faire : le développeur
La collaboration de ces différents acteurs est primordiale (et a mon sens, l’absence de l’un d’eux ne peut que mener à un désastre mais nous y reviendrons lorsque nous traiterons de la combinatoire de leur activités).

Nous avons déjà parler de l’approche agile mais nous pouvons constater ici qu’une phase importante du processus concerne la sortie de la partie cyclique. Cette phase peut être appeler livraison car elle correspond à la finalisation du ou des développements, et correspond au final à la livraison d’un nouvel incrément fonctionnel par l’ajout de nouveaux morceaux de code. Il faut comprendre que cette phase n’est pas terminale mais itérative comme l’est le développement et consiste à soutenir le développement en permettant la validation/livraison a chaque itération.

L'integration Continue

Cette phase est en fait appeler la phase d’intégration continue. Elle provient du besoin de tester systématiquement le code source et les nouvelles fonctionnalités apportées tout en garantissant la non régression des fonctionnalités déjà développées précédemment.


Mot d'ordre : automatisation

Dans ce schéma, le processus d'intégration continu fait intervenir un ensemble de composants en interaction et répondant à différents besoins. En entrée de l'intégration continu nous avons l’outil de gestion des sources (tel que Git [4], SVN [5] ou Mercurial [6]) dans lequel les développeurs déposeront le code produit et tester localement unitairement.

Ceci n’est cependant clairement pas suffisant car si un ajout de code peut être correct sur le poste du développeur et même en considérant que ce poste est conforme à un standard commun à toute l'équipe, il persistera toujours des différences sans compter les différences évidentes entre le poste de dev et la configuration d’un serveur cible.


Feed-back

Pour résoudre ces problèmes, intervient alors la phase d'intégration continu. Elle va permettre de procéder à une construction complète, automatisé et régulière de l’ensemble du composant logiciel soit de façon journalière, soit a chaque modification du référentiel de code, voir les deux. En procédant ainsi, l'intégration continu permettra la validation dans des plateforme de référence, de l’ensemble de fonctionnalités en déroulant l’ensemble des tests prévus (à noter que ces tests sont définis selon des niveaux différents, N1: tests unitaires, N2 tests d'intégrations, N3 : tests fonctionnels; les niveaux N2 et N3 sont souvent fusionnés). Les tests une fois validés, les développement réalisés par les développeurs sont alors intégré ensemble au sein d’une même branche ou de nouvelles validations sont déroulées (voila le coté intégration en continu).

Dans les faits, les sources sont récupérées par un outil d'orchestration tel que Jenkins [7] ou Travis [8] qui par configuration va appliquer un certain nombre de processus via un outil de build comme maven ou gradle ou directement sur la branche contenant les sources.

Procéduralement l’outil d’orchestration va devoir:

  • construire le composant (via maven [9] ou gradle [10]) en s’appuyant sur un entrepôt de composant (artifactory [11] ou nexus [12])
  • utiliser les éléments produit pour effectuer des tests unitaires
  • utiliser les éléments produit pour effectuer des tests fonctionnels dans un plateforme cible (virtuelle VM/docker ou non) avec des outils tels que robotframework [13] , sikuli [14]
  • effectuer de l’analyse de code à l’aide d’un outil comme sonar [15]
  •  produire de la documentation à l’aide d’outils comme maven site [16] ou java doc [17]


Vers le continuous delivery et le DevOps

En parallèle de cette approche ou le but est de vérifier au plus tôt l’absence de défauts mais aussi de problème d'intégration, il est considéré aujourd’hui de nouvelles approches par l’utilisation de conteneur versionable et livrable directement au client.

Ces nouvelles approches permettent au développeur de disposer d’un environnement de test ( voir de développement) local beaucoup plus proche et conforme à ce que sera la cible chez le client. Ces approches portées par des logiciels comme Docker fournissent des moyens beaucoup plus simple et élémentaires de validation et d'intégration en rapprochant le développeur de la plateforme cible cependant, elle nécessite l’utilisation par le client d’architecture spécifique basé sur ce type de conteneur. 


Facilitant d’un coté la maintenance, ces solutions doivent malgré tout, “en solution de virtualisation”, fournir des moyens de sécurisation et d’optimisation des performances au moins équivalentes aux solutions conventionnelles.

Conclusion

Dans ce blog, nous avons déjà traité des sujets tels que Docker. Il ne fait aucun doute que ce type de technologies ont leur adepte, et à juste titre. Cependant, même si ces approches ont leur avantages, il me semble que les approches conventionnelles (parce que omniprésente) gardent elles aussi leur points forts et que les solutions de productions logicielles futurs devront surtout composer avec des approches mixtes.

Il n’en reste pas moins que l'intégration continu est un environnement fortement outillé. Certains de ces outils, comme Maven, Gradle ou même la production documentaire, ont été évoqué dans différents articles. Pour les prochains articles, afin d’affiner notre vision des possibilités de l'intégration continu et des architectures possible la guidant, nous tacherons de rentrer un peu plus dans le détails de ces autres outils

References

[1] http://un-est-tout-et-tout-est-un.blogspot.com/2018/01/gradle.html
[2] http://un-est-tout-et-tout-est-un.blogspot.com/2017/12/maven-introduction.html
[3] http://un-est-tout-et-tout-est-un.blogspot.com/2017/12/les-processus-de-developpement-ou.html
[4] https://git-scm.com/
[5] https://subversion.apache.org/
[6] https://www.mercurial-scm.org/
[7] https://jenkins.io/
[8] https://travis-ci.org/
[9] https://maven.apache.org/
[10] https://gradle.org/
[11] https://jfrog.com/artifactory/
[12] https://www.sonatype.com/nexus-repository-sonatype
[13] http://robotframework.org/
[14] http://www.sikuli.org/
[15] https://www.sonarqube.org/
[16] https://maven.apache.org/plugins/maven-site-plugin/
[17] https://maven.apache.org/plugins/maven-javadoc-plugin/