Gestion de projets avec Erlang/OTP

Magazine
Marque
GNU/Linux Magazine
Numéro
245
Mois de parution
février 2021
Spécialité(s)


Résumé

Un langage de programmation se doit d’être facile d’accès, que ce soit pour son apprentissage, la réalisation de concepts ou de produits finaux. La création de projets en Erlang se fait via les notions d’application et de release. Couplés à différents outils internes ou fournis par la communauté, ces principes permettent de créer un environnement de production flexible et maintenable sur le long terme, tout en facilitant la diffusion et le partage des modules conçus par les créateurs.


Body

Après avoir présenté la syntaxe et la logique du langage Erlang dans un premier article [1], puis après avoir fait une introduction de la suite logicielle et des concepts liés à Erlang/OTP dans un second [2], ce nouvel article s’attaque à la lourde tâche de présenter les concepts d’application et de release au sein d’Erlang. Toujours dans la continuité des précédents écrits, le projet d’exemple utilisant un système de cache sera une fois de plus utilisé. Ce projet s’adapte parfaitement au modèle acteur conçu par Erlang et offre ainsi l’opportunité d’être mis à jour étape par étape, comme un projet réalisé en entreprise ou dans le vaste monde de l’open-source.

Avant de rentrer dans le vif du sujet, un récapitulatif historique s’impose tout de même. Erlang [3], langage créé à la fin des années 90 par Joe Armstrong au sein de la société Ericsson, s’était donné pour objectif de créer une suite d’outils permettant de régler les nombreux problèmes rencontrés dans le monde de l’industrie et de la télécommunication. Ce langage, après de nombreuses années de développement et de test, a permis de révolutionner l’utilisation de la programmation fonctionnelle et distribuée. La mise en avant d’une philosophie innovante de gestion d’erreurs au travers de l’exploitation de microprocessus isolés a permis de redéfinir des concepts que l’on pensaient alors avoir été totalement acquis par la communauté des développeurs, comme la notion d’objet qui, par définition, aurait dû être totalement isolée, mais qui, en pratique, ne l’est que trop rarement.

Qui parle de révolution sous-entend des changements profonds dans les principes fondamentaux. Ce renversement de paradigme donna la possibilité de modeler un nouvel univers sur des principes novateurs ou encore trop peu utilisés dans le monde du logiciel. Des concepts tels que les processus, les superviseurs, les travailleurs, les arbres de supervision ou encore les behaviours sont autant de nouvelles notions qui ont émergé et qui ont généralement bouleversé les habitudes des personnes ayant pour tâche de concevoir, de créer ou encore de déployer les produits issus de cet environnement. C’est pour cela qu’il est nécessaire de mettre à disposition les outils ainsi que les ressources [4] facilitant la transition, tout en permettant de répondre aux besoins de cohérence et de flexibilité. Les modèles d’applications, de releases et de gestionnaires de projets présentés ici en font partie.

1. Introduction

Le système de cache, conçu lors des articles précédents, est normalement fonctionnel. Certes, il est loin d’être parfait et est extrêmement limité en fonctionnalités, mais répond au besoin originel : stocker des valeurs associées à des clés, dans une structure de données isolée au sein d’un processus nommé. Ce comportement est globalement similaire à celui d’une base de données Redis ou MongoDB. Bien que l’outil soit fonctionnel, le lecteur a dû gérer manuellement le projet sans aide et uniquement via la ligne de commandes. La compilation des différents fichiers s’est faite sans outils spécifiques. Pour les lecteurs habitués à concevoir des logiciels, leur premier réflexe fut sans doute celui de créer un Makefile, permettant ainsi d’automatiser les différentes tâches récurrentes et rébarbatives, telles que la compilation, les tests ou encore celle du lancement de la machine virtuelle, voire pour les plus téméraires, du déploiement.

Erlang étant un langage moderne, les projets conçus sur cette plateforme ont la possibilité d’être maintenus par un gestionnaire de projet. Effectivement, de tels outils existent d’ores et déjà pour d’autres langages, tels que Maven ou Ant avec Java, SBT avec Scala, Cargo avec Rust ou encore Leiningen pour Clojure. Contrairement aux deux derniers langages cités, qui fournissent nativement ces outils pour la gestion de projets, Erlang laisse le choix aux développeurs de prendre le ou les outils qui leur semble être le plus adapté. Deux grands noms reviennent fréquemment, à savoir Erlang.mk et Rebar3, qui deviendront prochainement vos points d’entrée pour la conception de logiciels écrits en Erlang.

Avant de parler de cesdits gestionnaires de projets qui feront la joie des développeurs pressés, il est nécessaire, voire primordial de comprendre ainsi que de connaître la structure d’un projet conçu avec et pour l’environnement Erlang/OTP.

2. Application

Le terme « application » peut parfois porter à confusion pour les nouveaux arrivants dans l’écosystème Erlang. La définition peut être trompeuse et regroupe plusieurs concepts. Tout d’abord, une application est un regroupement de code implémentant une ou plusieurs fonctionnalités, et agissant comme un composant unique au sein du système. Par extension, une application facilite la modularité ainsi que le partage avec d’autres systèmes ayant besoin de ces fonctionnalités pour différents contextes.

Concrètement, une application [5] est généralement constituée d’un arbre de supervision composé lui-même de processus superviseurs et de processus travailleurs. Une grande partie de ces éléments utilisent une ou plusieurs bibliothèques fournissant les implémentations nécessaires au bon fonctionnement de l’application. Pour lier toutes ces briques entre elles, Erlang/OTP fournit un behaviour appelé application, facilitant le regroupement des sous-processus entre eux et standardisant par la même occasion les méthodes de démarrage puis d’arrêt, au travers de fonctions exportées par ce même module.

La création d’une application en Erlang reste classique et se base sur un module créé par le développeur utilisant le behaviour application. Comme tout logiciel créé en Erlang, le code nécessite la création d’un en-tête correspondant aux différents paramètres utilisés par le compilateur pour lui faciliter son travail. Cet en-tête permet tout d’abord de définir le namespace, correspondant au nom du module, ainsi que les fonctions exportées par celui-ci et terminées par le type de behaviour utilisé.

-module(mon_application).
-export([start/2, stop/1]).
-behaviour(application).

Le premier callback à définir est start/2. Ce callback reçoit au travers du premier argument le type de démarrage de l’application. Effectivement, Erlang permet en grande partie de faciliter le déploiement de systèmes distribués, et une application peut naturellement démarrer dans un tel environnement. Le type de démarrage correspond alors à définir le comportement qu’une application pourra adopter si cette dernière est démarrée sur un nœud clusterisé.

Il existe globalement trois types de démarrages. Le type normal est celui par défaut, et se contentera de démarrer l’application localement, cloisonné sur son nœud. Le second type de démarrage se nomme takeover, et deviendra alors l’application principale sur le cluster Erlang, dans le cas où la même application fonctionnerait sur un des autres nœuds. Le dernier mode se nomme failover et fait office de comportement de roue de secours. Si la même application présente sur le cluster crashe ou s’arrête d’une façon non désirée, alors l’application en failover reprend automatiquement la main dessus.

Le deuxième argument de ce callback correspond aux options passées à l’application lors de son démarrage. Ces arguments sont libres et restent généralement définis par le développeur qui voudrait que son application réagisse différemment en fonction d’un contexte précis. Finalement, ce callback doit retourner un tuple semblable à celui retourné par un processus démarré avec les fonctions start/3 ou start_link/3, présentent dans les modules gen_server, gen_statem ou supervisor. Le premier élément de ce tuple contient généralement un tag sous forme d’atome (ok ou error) suivi de l’identifiant du processus faisant référence au superviseur ou au processus démarré.

start(_Type, _Arguments) ->
   mon_application_sup:start_link().

Le deuxième callback implémenté est stop/1. Son premier et seul argument correspond à l’état de l’application, généralement le contenu du retour du callback start/2. Cette fonction ne retourne pas de donnée particulière et agit comme un appel asynchrone.

stop(_Etat) ->
   ok.

Ces deux callbacks ne sont pas les seuls disponibles, mais sont les deux les plus utilisés et indispensables pour le behaviour application. Les autres callbacks correspondent à des fonctionnalités rencontrées généralement sur OTP, mais appliquées au monde de l’application. Le callback config_change/3 permet de faire une transition de configuration, correspondant à un changement d’état. Le callback prep_stop/1 permet d’effectuer des actions avant l’arrêt de l’application, comme arrêter un service externe ou sauvegarder un état. Finalement, le callback start_phase/3 permet de définir un ordre de démarrage, permettant de synchroniser le démarrage de plusieurs applications.

Mais ce n’est pas tout. Le module application, correspondant au behaviour du même nom, fournit un nombre important de fonctions pour configurer ce système durant sa période d’activité. Effectivement, une application doit pouvoir être paramétrable facilement, et ces différentes configurations doivent pouvoir se répercuter tout aussi aisément sur les sous-processus gérés par le superviseur applicatif. Un exemple est souvent plus compréhensible qu’un long discours. Lors du démarrage d’un nœud Erlang, plusieurs applications sont d'ores et déjà démarrées sur le système, telles que kernel ou stdlib, qui permettent de gérer l’état global de la machine virtuelle.

Ces deux applications ne dérogent pas à la règle précédente, et peuvent être paramétrées ou possèdent déjà des paramètres de configuration. Pour récupérer la liste de ces variables, la fonction application:get_all_env/1 peut-être utilisée. Son premier argument correspond au nom de l’application visée et retournera les éléments sous forme d’une liste de tuples, ou comme montrée dans l’exemple suivant, d’une liste vide.

1> application:get_all_env(stdlib).
[]

Évidemment, une liste vide ne saurait rester vide très longtemps. Pour rajouter une valeur dans les variables d’environnement de l’application, la fonction application:set_env/3 peut-être utilisée. Le premier argument reçoit le nom de l’application suivi de la clé, puis de la valeur associée.

2> application:set_env(stdlib, ma_variable, test).
ok

Comme le lecteur doit s’en douter, il est aussi possible de récupérer un élément de la liste, tout comme le gestionnaire de cache créé en exemple durant les deux derniers articles, les variables d’environnement d’une application fonctionnent sur le même principe. La fonction application:get_env/2 est alors utilisée. Son premier argument correspond au nom de l’application, et le second à la clé. Cette fonction retourne la valeur associée à la clé.

3> application:get_env(stdlib, ma_variable).
{ok,test}

En revenant sur la fonction application:get_all_env/1, son utilisation permet de retourner une liste de tuples, comme déclarée précédemment. Cette structure de données est appelée proplist et se retrouve à pratiquement tous les niveaux des applications et des releases en Erlang, même si cette structure de données est maintenant mise au second plan, au profit des maps.

4> application:get_all_env(stdlib).
[{ma_variable,test}]

Pour démarrer ou mettre fin à une application, les fonctions application:start/1 et application:stop/1 sont exportées. Par exemple, pour démarrer l’application crypto, permettant de gérer une grande partie des fonctionnalités de chiffrement sur un nœud Erlang, la commande suivante sera utilisée :

5> application:start(crypto).
ok

Pour récupérer la liste des applications démarrées actuellement sur le système, la fonction application:which_applications/0 pourra alors être utilisée. Cette fonction retourne une liste contenant le nom des applications, la description de ces dernières, ainsi que la version associée en fonctionnement sur le nœud.

5> application:which_applications().
[{crypto,"CRYPTO","4.4"},
{stdlib,"ERTS CXC 138 10","3.7"},
{kernel,"ERTS CXC 138 10","6.2"}]

Évidemment, une application fonctionne rarement seule isolée dans son coin, et elle peut avoir besoin de dépendances indispensables pour lui permettre de se mouvoir correctement. Si ces différentes dépendances n’ont pas été démarrées manuellement par le développeur avant le lancement de l’application, cette dernière ne devrait pas être en capacité de démarrer et devrait retourner une erreur indiquant la raison de l’échec.

Pour éviter ce genre de situation pouvant être dramatique lors d’une mise en production, la fonction application:ensure_all_started/1 a été conçue. Cette fonction permet de démarrer automatiquement la liste des dépendances. Par exemple, l’application ssl permet d’utiliser l’implémentation SSL/TLS au sein d’un nœud Erlang et dépend des applications crypto, asn1, et public_key. Dans le cadre d’un démarrage manuel de l’application avec la fonction application:start/1, cette dernière retourne une erreur, car une ou plusieurs dépendances ne sont pas en cours de fonctionnement.

6> application:start(ssl).
{error,{not_started,crypto}}

Le démarrage de l’application ssl via la fonction application:ensure_all_started/1 se passe sans accrocs, car cette fonction va regarder la liste des dépendances et démarrer les démarrer une par une.

7> application:ensure_all_started(ssl).
{ok,[crypto,asn1,public_key,ssl]}

Le lecteur attentif se posera alors une question fondamentale : comment une application peut-elle connaître ses dépendances ? Pour le savoir, le système applicatif se tourne vers un fichier de ressources [6], étroitement lié au module utilisant le behaviour application. Un tel fichier est livré aux côtés de l’application elle-même et contient une structure de données en terme Erlang. Ce fichier a pour nom celui de l’application, suivi de l’extension .app. Son contenu est standardisé et correspond à un tuple ayant alors trois champs. Le premier contient l’atome application, le second contient le nom de l’application sous la forme d’un atome et le troisième contient les différents paramètres liés à l’application. Voici l’exemple d’un tel fichier, nommé mon_application.app, qui permet de spécifier une application nommée mon_application.

{ application, mon_application,
  [{description, "une application de test"},
   {id, "MON APPLICATION"},
   {vsn, "0.0.1"},
   {modules, [mon_application_sup]},
   {registered, [mon_application_sup]}
   {applications, []}]
}.

Ce fichier contient des informations essentielles, telles que le nom de l’application, sa description, son identifiant, sa version, la liste des modules chargés par l’application, la liste des applications qui seront exposées (visible des autres nœuds), la liste de dépendances des applications et encore bien d’autres champs disponibles qui sont définis dans la documentation officielle [7], permettant ainsi de garantir le bon fonctionnement de l’application. Après enregistrement du fichier, et si les sous-modules ont été correctement créés, l’application pourra alors être démarrée et utilisée sur le nœud Erlang.

8> application:start(mon_application).
ok

Toutes les étapes présentées ici correspondent à la création d’une application Erlang artisanalement conçue, et font généralement partie de la culture générale à avoir pour comprendre la logique derrière les gestionnaires de projets. Ces derniers permettent d’ailleurs la création et la génération automatique des fichiers et autres configurations qui ont été vues dans cette partie, mais laisse une fois de plus la possibilité au développeur de les optimiser ou customiser en fonction des besoins du projet en cours de conception.

3. Release

Une release [8] est ce qu’est une application aux modules. Une application unique n’est généralement pas suffisante pour de nombreux projets et particulièrement ceux complexes nécessitant l’utilisation de nombreuses fonctionnalités. Les releases permettent donc de créer un système ayant la possibilité de contrôler plusieurs sous-applications. Comme le lecteur peut s’en douter, la gestion d’une release n’est pas forcement des plus simples. Cette partie de l’article est uniquement ici présente pour survoler ce concept et ainsi voir les possibilités offertes par Erlang pour la livraison de produits finis.

Tout comme une application est livrée avec ses spécifications sous forme d’un fichier de ressources, la release suit le même principe, à l’exception du fait que ce fichier utilise une extension en .rel. Pour donner un exemple, les commandes suivantes permettent de créer un répertoire ma_release qui contiendra le fichier ma_release.rel.

$ mkdir ma_release
$ cd ma_release
$ touch ma_release.rel

Encore une fois, tout comme le modèle de l’application, le fichier de ressources concernant une release contient les informations nécessaires au bon fonctionnement de cette dernière. Sous la forme d’un tuple de dimension quatre, le premier élément est un atome release, le second élément contient le nom de la release suivi de sa version, le troisième élément contient la version d’erts (Erlang Run-Time System) correspondant grossièrement à la version de l’interpréteur Erlang, puis de la liste des différentes applications à livrer avec la release, telles que le kernel, stdlib et sasl.

{ release,
   {"ma_release","VersionRelease"},
   {erts, "VersionERTS"},
   [{kernel, "VersionKernel"},
     {stdlib, "VersionStdLib"},
      {sasl, "VersionSASL"}
   ]
}.

Le lecteur attentif remarquera que les versions ne sont pas configurées dans le fichier de la release. Ces versions dépendent de votre système, et sont à trouver au niveau du chemin où les bibliothèques Erlang sont stockées. Éventuellement, plusieurs fonctions peuvent retourner des numéros de version, comme application:which_applications/0 ou encore release_handler:which_releases/0.

La machine virtuelle Erlang, en plus de ce fichier décrivant les applications, a besoin d’un script pour permettre le démarrage des applications. Le module systools exporte la fonction systools:make_script/1 qui prend en argument le nom de la release et génère automatiquement le script de boot.

1> systools:make_script("ma_release").
ok

À la suite de cette action, deux fichiers sont normalement générés. Le premier se nomme ma_release.script, contenant une structure de données Erlang sous forme d’un tuple et décrivant textuellement les différentes étapes du boot. Le deuxième fichier se nomme ma_release.boot et est l’équivalent du précédent fichier, mais compilé en format binaire ETF (Erlang Term Format). Pour vérifier le bon fonctionnement de ce script, l’utilisation de la commande erl suivie du nom de la release suffit à démarrer le système et donne accès à un shell Erlang.

$ erl ma_release
Erlang/OTP 21 [erts-10.2] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1]
 
Eshell V10.2 (abort with ^G)
1> q().
ok

Maintenant que le script fonctionne et que le système semble utilisable, il doit alors pouvoir être livré. Le module systools offre alors la fonction systools:make_tar/1 qui permet de créer un tarball contenant tous les fichiers compilés et prêts à l’usage, donc prêts à être déployés dans un environnement de production ou livrés au client final.

2> systools:make_tar("ma_release").
ok

Le fichier généré se nomme ma_release.tar.gz et contient, comme convenu, la liste des applications, compilées et prêtes à l’usage. Il est à noter qu’une release ne contient pas que l’application conçue, mais aussi le système dans son intégralité, comme le kernel, les bibliothèques standard et tout ce qui est nécessaire au bon fonctionnement du futur nœud. Pour le déploiement, ce fichier peut-être décompressé sur le serveur qui contiendra le système. Tout comme les applications, il est aussi possible de gérer la mise à jour intelligemment via différents handlers et principes... qui seront probablement détaillés dans un article dédié à la mise en production.

Finalement, pour les personnes pour lesquelles la ligne de commande est répulsive ou pour les personnes ayant peur de faire des erreurs, Erlang est livré avec le module reltool qui permet de générer les fichiers de ressources, mais aussi de fournir un nombre important d’outils pour voir et gérer les dépendances entre les versions. Le bonus : tout est réalisé au travers d’une GUI (qui nécessite le module wx, généralement fourni séparément sous GNU/Linux / UNIX, pour ceux qui rencontreraient des problèmes pour le démarrer).

4. Rebar3

Rebar3 [9] est probablement à ce jour l’outil le plus utilisé dans la communauté Erlang pour la gestion des projets. Développé il y a maintenant plusieurs années, il permet de fonctionner comme un gestionnaire de projets classique, offrant toutes les fonctionnalités d’un Maven, SBT ou encore Leiningen. Il est à l’origine issu d’un fork de rebar2, basé sur une réécriture complète du code par Fred Hebert, Tristan Sloughter et Tuncer Ayaz. Ce projet fait d’ailleurs partie des outils officiellement maintenus par l’équipe Erlang. Par ailleurs, et comme indiqué au début de l’article, Rebar3 n’est pas disponible nativement avec les releases d’Erlang et nécessite donc son installation soit via un paquet, soit via les sources. Évidemment, dans le cadre de cet article, l’auteur choisit ici de vous montrer l’installation via les sources. Rebar3 est intégralement écrit en Erlang et nécessite d’avoir d'ores et déjà l’environnement installé sur votre système via les sources ou votre gestionnaire de paquets favoris.

$ git clone https://github.com/erlang/rebar3
$ cd rebar3
$ git checkout 3.9.1
   ...
$ ./bootstrap
   ...
$ PATH=${PATH}:$(pwd)
$ rebar3
Rebar3 is a tool for working with Erlang projects.
 
Usage: rebar3 [-h] [-v] [<task>]
   -h, --help     Print this help.
   -v, --version Show version information.
   <task>         Task to run.

Par souci de clarté, la sortie de l’écran a été tronquée, mais chacune des tâches les plus utilisées va être développée au cas par cas dans cette partie de l’article, ainsi qu’au travers d’un exemple concret. Maintenant que Rebar3 est disponible dans le PATH, son exécution pourra se faire n’importe où sur le système.

Il n’est généralement pas bon de produire ce qui a d'ores et déjà été produit dans le passé, mais pour pouvoir avancer sereinement, le lecteur est invité à recréer le module cache. Pour se faire, la sous-commande new est utilisable et permet de générer automatiquement une arborescence pour un type de projet défini. Cette arborescence est alors produite à partir d’un template, généralement fourni avec Rebar3, mais pouvant être aussi produit par le développeur pour ses besoins.

Ces templates permettent de créer facilement une application (app), des facilités pour la compilation de programme en C et C++ (cmake), un script Erlang (escript), une bibliothèque (lib), un plugin Rebar3 (plugin), une release (release) ou encore un projet basé sur Umbrella (généralement utilisé avec Elixir). Ces templates correspondent au premier argument de la sous-commande new et possèdent des attributs modifiables sous forme de variable à définir à la suite, permettant ainsi de modifier, par exemple, le nom du projet.

$ rebar3 new app name=cache
===> Writing cache/src/cache_app.erl
===> Writing cache/src/cache_sup.erl
===> Writing cache/src/cache.app.src
===> Writing cache/rebar.config
===> Writing cache/.gitignore
===> Writing cache/LICENSE
===> Writing cache/README.md

L’arborescence créée est compatible avec celle standardisée dans Erlang/OTP et offre les mêmes avantages. La compilation du projet se fait via la sous-commande compile, qui va alors compiler tous les fichiers Erlang présents dans le sous-répertoire src.

$ rebar3 compile
===> Verifying dependencies...
===> Compiling cache

Évidemment, dans le cadre d’un projet un peu plus complexe, un développeur aura probablement besoin d’utiliser des bibliothèques ou des applications externes. Pour ce faire, Rebar3 fournit une sous-commande nommée get-deps permettant de télécharger les différentes dépendances configurées dans le fichier rebar.config. À noter qu’Erlang a pour vocation de créer un « univers » cohérent, et générera alors un fichier rebar.lock, contenant les versions des dépendances téléchargées et garantissant ainsi le fonctionnement du système pour ces versions seulement.

$ rebar3 get-deps
===> Verifying dependencies...

Pour faciliter l’interaction avec le code créé, Rebar3 permet de démarrer, à la demande du développeur, un shell préchargé avec le code. Cette sous-commande est nommée shell et sera d’une grande utilité lors d’un développement. Outre le lancement d’un REPL classique, elle permet aussi d’offrir des outils pour la compilation du projet via les différents modules livrés avec Rebar3.

$ rebar3 shell
===> Verifying dependencies...
===> Compiling cache
===> The rebar3 shell is a development tool; to deploy applications in production, consider using releases (http://www.rebar3.org/docs/releases)
===> Booted cacheErlang/OTP 21 [erts-10.2] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1]
 
1> r3:do(compile).
===> This feature is experimental and may be modified or removed at any time.
Verifying dependencies...
Compiling cache
ok

Rebar3 offre aussi la possibilité de tester plus facilement le code en intégrant les tests unitaires et autres modules de tests fournis avec Erlang/OTP. Effectivement, le module eunit permet de gérer les tests unitaires et ct permet de gérer les « tests communs » ou « common test » en anglais. Pour les utiliser avec Rebar3, les sous-commandes eunit et ct ont été créées, exécutant alors le code présent dans le sous-répertoire test.

$ rebar3 eunit===> Verifying dependencies...
===> Compiling cache
===> Performing EUnit tests...
 
Finished in 0.092 seconds
0 tests
 
$ mkdir test
$ rebar3 ct
===> Verifying dependencies...
===> Compiling cache
===> Running Common Test suites...
All 0 tests passed.

Les différents supports de tests statiques et dynamiques sont aussi supportés via les sous-commandes cover et dialyzer. Des outils qui seront encore une fois développés en profondeur dans de futurs articles. Pour donner tout de même une indication, cover et dialyzer sont des outils permettant de faire une analyse de code basée sur les spécifications et les types, en s’assurant que, par exemple, les fonctions sont correctement appelées ou que les différentes variables contiennent les bons types de données lors d’une exécution.

$ rebar3 cover
===> Verifying dependencies...
===> Compiling cache
===> Performing cover analysis...
===> No coverdata found
$ rebar3 dialyzer
===> Verifying dependencies...
===> Compiling cache
===> Dialyzer starting, this may take a while...
===> Updating plt...
===> Resolving files...
===> Updating base plt...
===> Resolving files...
===> Building with 191 files in "/home/user/.cache/rebar3/rebar3_21.2_plt"…

Rebar3 permet aussi de créer des releases et facilite le déploiement du projet en cours de création via les sous-commandes release et relup.

$ rebar3 release
$ rebar3 relup

Finalement, le fichier rebar.config permet de maintenir la cohérence au niveau des besoins du projet. Ce fichier est écrit en purs termes Erlang et donne la possibilité de rajouter les différentes dépendances ou fonctionnalités nécessaires au bon fonctionnement du projet en cours de réalisation. Chaque section est définie par un tuple, dont le premier élément est un atome désignant la catégorie de configuration, suivi des arguments et paramètres de cette dernière.

{erl_opts, [debug_info]}.
{deps, []}.
 
{shell, [
  % {config, "config/sys.config"},
    {apps, [cache]}
]}.

Rebar3 est un outil à avoir près de soi. Il permet d’accomplir un nombre incroyable de tâches habituellement laborieuses. Largement supporté par la communauté Erlang, il a aussi la particularité d’être modulable et donc, d’être extensible pratiquement à l’infini.

5. Erlang.mk

Erlang.mk [10] est une solution alternative développée et maintenue par Loïc Hoguin. Ce gestionnaire de projets est intégralement écrit à l’aide de GNU Makefile, ce qui lui garantit une très grande portabilité. Effectivement, la commande make se retrouve globalement sur tous les systèmes. Erlang.mk s’appuie aussi sur les outils standard, tel que Git, pour pouvoir fonctionner correctement. Le Makefile en question, simple fichier texte, est récupérable sur le site officiel du projet.

$ mkdir my_application
$ cd my_application
$ curl -O https://erlang.mk/erlang.mk

Tout comme Rebar3, Erlang.mk utilise un système de template pour générer automatiquement une arborescence et ainsi permettre la création d’un projet en quelques secondes. La création d’une application OTP classique se fait en appelant la cible bootstrap. La création d’une bibliothèque OTP se fait en appelant la cible bootstrap-lib. Finalement, la création d’une release se fait en appelant la cible bootstrap-rel. Le rôle de ces cibles, en plus de générer les différents fichiers, est aussi de créer le fichier Makefile qui contiendra les informations relatives au projet.

$ make -f erlang.mk bootstrap
$ find ../erlang.mk
./Makefile
./src
./src/my_application_app.erl
./src/my_application_sup.erl
./.erlang.mk
./.erlang.mk/last-makefile-change

Ce Makefile nouvellement créé contient alors le nom du projet, la description du projet ainsi que sa version. Le fichier erlang.mk devient alors une bibliothèque, directement incluse à la fin du Makefile, et permettant d’utiliser toutes les fonctionnalités sans avoir à l’appeler manuellement via le flag -f de la commande make.

PROJECT = my_application
PROJECT_DESCRIPTION = New project
PROJECT_VERSION = 0.1.0
 
include erlang.mk

La compilation du projet en exécutant la commande make dans le répertoire du projet ou en appelant explicitement les cibles all ou app. Les binaires qui en résultent sont alors stockés dans le répertoire ebin.

$ gmake all
DEPEND my_application.d
ERLC   my_application_app.erl my_application_sup.erl
APP    my_application$ find ebin
ebin/
ebin/my_application.app
ebin/my_application_app.beam
ebin/my_application_sup.beam

Erlang.mk, comme Rebar3, donne accès au REPL Erlang, incluant par défaut, lors de son exécution, les chemins nécessaires au bon fonctionnement de l’application en cours de développement. Pour y avoir accès, la commande make doit être exécutée avec la cible shell.

$ gmake shell
GEN    shell
Erlang/OTP 21 [erts-10.2] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1]
 
Eshell V10.2 (abort with ^G)
1>

Enfin, Erlang.mk supporte aussi les différentes bibliothèques et frameworks de tests fournis avec Erlang. Les tests unitaires sont exécutables via les cibles eunit et ct. L’analyse statique est exécutée via la cible dialyze, qui fait appel alors à dialyzer, générant automatiquement les fichiers nécessaires à cet outil. Finalement, la cible check permet de se simplifier la vie en exécutant tous les types de tests à la suite.

$ gmake check
GEN    test-build
GEN    eunit
  There were no tests to run.
GEN    clean-app
GEN    coverdata-clean
DEPEND my_application.d
ERLC   my_application_app.erl my_application_sup.erl
APP    my_application
   Creating PLT my_application/.my_application.plt ...
Unknown functions:
  compile:file/2
  compile:forms/2
  compile:noenv_forms/2
  compile:output_generated/1
  crypto:block_decrypt/4
  crypto:start/0
Unknown types:
  compile:option/0
done in 2m32.76s
done (passed successfully)
  Checking whether the PLT my_application/.my_application.plt is up-to-date... yes
  Proceeding with analysis... done in 0m0.22s
done (passed successfully)

Erlang.mk est un gestionnaire de projets agréable à utiliser, offrant les mêmes avantages que Rebar3. Sa flexibilité et sa grande portabilité en font un outil parfait pour les projets multiplateformes et/ou multilangages. Il appartient désormais au lecteur de choisir lequel de ces deux outils, Rebar3 ou Erlang.mk, deviendra son préféré. Il ne faudra pas oublier une chose : ces projets permettent de faire bien d’autres choses et la documentation reste une très bonne source d’information pour les personnes qui seraient tentées par ces solutions.

6. Hex

Un article parlant des applications, des releases et des gestionnaires de projets en Erlang ne pouvait pas se faire sans une petite note sur Hex.pm [11]. Comme de nombreux langages présents sur le marché, la communauté a mis en place il y a de ça quelques années un site permettant de recenser les différents outils disponibles. Ce site permet donc de diffuser les modules créés par la communauté. Il permet aussi de créer des dépôts publics ou privés pour les créateurs de contenu, ainsi qu’un système de recherche assez pratique. Pour les plus curieux, il est bon de savoir que Hex.pm n’est pas réservé exclusivement à Erlang, mais est aussi disponible pour les utilisateurs du langage Elixir.

7. Gestionnaire de cache et application

Maintenant que le lecteur est au courant des différentes suites d’outils offertes par la communauté, il est temps de migrer et d’adapter le projet de cache développé depuis le début des articles. Pour que l’exemple soit réellement probant, une arborescence complètement compatible avec Rebar3 sera créée manuellement et chacune des étapes sera expliquée pour étayer les différentes informations qui ont été vues précédemment.

La création d’une arborescence commence par la création des répertoires. Pour ce faire, trois répertoires sont essentiels, le répertoire « racine » qui sera nommé d'après le nom du projet. Dans le cadre de cet exemple, il aura pour nom cache. Le sous-répertoire src contiendra les sources de l’application en cours de développement. Pour finir, le sous-répertoire test contiendra les tests basés sur les frameworks de test eunit ou common_test.

$ mkdir -p cache cache/test cache/src

Les fichiers cache_server.erl ainsi que cache_sup.erl font référence aux fichiers créés dans le précédent article [2] et peuvent-être simplement copiés et/ou déplacés dans le répertoire cache/src. Ils ne sont pas à modifier et permettront d’être le « cœur » de l’application.

$ mv ${origine}/cache.erl cache/src/cache.erl
$ mv ${origine}/cache_sup.erl cache/src/cache_sup.erl

Le fichier cache_app.erl va utiliser le behaviour application et ainsi pouvoir créer l’application cache.

$ touch cache/src/cache_app.erl

Le contenu de cache_app.erl contiendra alors le nom de l’application, cache_app. Le callback start/2 démarre le module cache_sup en utilisant la fonction supervisor:start_link/2 et retourne alors l’identifiant de processus en cas de succès. Le callback stop/1 ne réalise aucune action particulière, et se contente simplement d’arrêter implicitement le service.

-module(cache_app).
-behaviour(application).
-export([start/2, stop/1]).
 
start(_Type, _Options) ->
   supervisor:start_link(cache_sup, []).
 
stop(_Etat) ->
ok.

Pour que l’application soit complète et fonctionnelle, le fichier cache.app.src doit alors être créé et va contenir les besoins de l’application, tels que les dépendances applicatives, de bibliothèques ou encore définir l’ordre de démarrage de certains services.

$ touch cache/src/cache.app.src

Le fichier en question devrait contenir les informations essentielles permettant de démarrer l’application cache. Étant donné que l’application en question est rudimentaire, seuls les outils essentiels sont à rajouter. Le code en question devrait ressembler à ça :

{application, cache,
[{description, "A simple cache application"},
  {vsn, "0.1.0"},
  {registered, [cache_sup, cache]},
  {mod, {cache_app, []}},
  {applications,
   [kernel,
    stdlib
   ]},
  {env,[]},
  {modules, []},
  {licenses, ["Apache 2.0"]},
  {links, []}
]}.

Finalement, pour que toute cette arborescence soit compatible avec Rebar3, le fichier rebar.conf doit être créé et contiendra la configuration relative au gestionnaire de projets.

$ touch cache/rebar.config

Comme vu précédemment, voici un exemple du contenu :

{erl_opts, [debug_info]}.
{deps, []}.
{shell, [
    {apps, [cache]}
]}.

Pour les personnes qui souhaiteraient diffuser ce code ailleurs, comme sur GitHub ou Gitlab, un fichier de LICENCE ainsi qu’un fichier de README peuvent être créés.

$ touch cache/LICENCE$
touch cache/README.md

L’application est maintenant prête à être compilée avec Rebar3. Pour ce faire, la sous-commande compile est à utiliser :

$ cd cache
$ rebar3 compile
===> Verifying dependencies...
===> Compiling cache

Le résultat de la compilation se trouve alors dans le répertoire _build, dynamiquement créé lors de l’exécution de la commande précédente. Dans tous les cas, il est possible d’utiliser la sous-commande shell pour contrôler si tout est correctement fonctionnel.

$ rebar3 shell===> Verifying dependencies...
===> Compiling cache
===> The rebar3 shell is a development tool; to deploy applications in production, consider using releases (http://www.rebar3.org/docs/releases)
===> Booted cache
Erlang/OTP 21 [erts-10.2] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1]
 
Eshell V10.2 (abort with ^G)

Il semblerait que l’application soit correctement démarrée, pour le vérifier, la commande application:which_application/0 devrait offrir la réponse recherchée. Effectivement, l’application cache est présente sur le nœud, elle contient bien la description configurée dans le fichier de ressources, tout comme la version.

1> application:which_applications().
[{cache,"A simple cache application","0.1.0"},
{inets,"INETS CXC 138 49","7.0.3"},
{ssl,"Erlang/OTP SSL application","9.1"},
{public_key,"Public key infrastructure","1.6.4"},
{asn1,"The Erlang ASN1 compiler version 5.0.8","5.0.8"},
{crypto,"CRYPTO","4.4"},
{stdlib,"ERTS CXC 138 10","3.7"},
{kernel,"ERTS CXC 138 10","6.2"}]

Qu’en est-il des différents processus démarrés sur le nœud ? Est-ce que le gestionnaire de cache fonctionne ? Il suffit pour cela d’utiliser la fonction cache:add/3, puis cache:get/2 pour le savoir...

2> cache:add(cache, test, 1).
ok
3> cache:get(cache, test).
1

L’application cache est maintenant compatible avec Rebar3 et peut utiliser toutes les fonctionnalités de ce gestionnaire de projets. Il est désormais possible de déployer l’application, de l’exporter proprement vers un dépôt ou encore de l’envoyer sur Hex.pm.

Conclusion

Une étape supplémentaire vient d’être franchie. La gestion d’un projet est une épreuve, mais permet sur le long terme d’avoir une structure compréhensible par un grand nombre. Cet article n’a montré que peu de code en Erlang, mais était nécessaire pour les articles qui viendront par la suite. Effectivement, la création des tests, mais aussi la conception de produits plus complexes, passe obligatoirement par une façon de ranger les différentes données créées par le développeur.

Le lecteur a maintenant une vision plus précise du concept d’application, de release et de projet sous Erlang. Il est évident que ceci n’est qu’une introduction, et que la compréhension de l’ensemble nécessite de la pratique. Comme dans tous les autres langages, Erlang ne faisant pas exception, la création de contenu est un processus long et nécessitant de la patience ainsi que de la rigueur, condition sine qua non pour atteindre un niveau d’excellence.

Références

[1] M. KERJOUAN, « Erlang, programmation distribuée et modèle acteur », GNU/Linux Magazine no237, mai 2020 : https://connect.ed-diamond.com/GNU-Linux-Magazine/GLMF-237/Erlang-programmation-distribuee-et-modele-acteur

[2] M. KERJOUAN, « Système extensible et hautement disponible avec Erlang/OTP », GNU/Linux Magazine no241, octobre 2020 : https://connect.ed-diamond.com/GNU-Linux-Magazine/GLMF-241/Systeme-extensible-et-hautement-disponible-avec-Erlang-OTP

[3] Site officiel d’Erlang : https://erlang.org

[4] Documentation officielle d’Erlang : https://erlang.org/doc

[5] Documentation du behaviour Application : https://erlang.org/doc/man/application.html

[6] Documentation du fichier de ressources applicatif : https://erlang.org/doc/man/app.html

[7] Design des applications en Erlang : https://erlang.org/doc/design_principles/applications.html

[8] Design des releases en Erlang : https://erlang.org/doc/design_principles/release_structure.html

[9] Site officiel de Rebar3 : https://www.rebar3.org

[10] Site officiel d’Erlang.mk : https://erlang.mk

[11] Site officiel d’Hex.pm : https://hex.pm

[12] Code source des exemples : https://github.com/niamtokik/linux-mag

Pour aller plus loin

Les personnes désirant en savoir plus sur la gestion de projets en Erlang peuvent se tourner vers les différents liens se trouvant dans la partie Références. La documentation officielle est une source d’exemples précieux qui permettront de donner une plus grande profondeur à cet article. Le livre « Designing for Scalability with Erlang/OTP » écrit par Francesco Cesarini et Steve Vinoski fait partie de la littérature à posséder pour avoir une vue d’ensemble de la situation, tout en profitant des meilleures pratiques. Un dernier mot pour la fin, le code source est mis à disposition du lecteur via le compte GitHub [12] de l’auteur.



Article rédigé par

Par le(s) même(s) auteur(s)

Introduction à l’écriture de tests avec Erlang/OTP

Magazine
Marque
GNU/Linux Magazine
HS n°
Numéro
113
Mois de parution
mars 2021
Spécialité(s)
Résumé

Les tests ont toujours été un facteur non négligeable de réussite lors de la conception d’un projet de développement. Ces différentes procédures, accompagnant généralement le code source, permettent d’augmenter grandement la qualité d’une application, qu’elle soit libre ou fermée. Même s’il est vrai qu’un programme ne peut-être testé dans son intégralité, les tests mis en place permettront de livrer à l’utilisateur final un système stable ainsi qu’une garantie de fonctionnement. Bien entendu, Erlang/OTP n’est pas en reste, et fournit aux développeurs tout un arsenal d’outils agissant sur l’excellence du produit.

Netcat, l’histoire d’un couteau suisse pour le réseau

Magazine
Marque
Linux Pratique
Numéro
123
Mois de parution
janvier 2021
Spécialité(s)
Résumé

Lier le monde de l’administration système et celui du réseau n’est pas chose aisée, ni donné à tout le monde. De nombreux outils présents issus du monde de l’open source essaient désespérément d’y trouver une place. L’un d’entre eux a par ailleurs une longueur d’avance. Permettant de jouer avec la création de socket sur la couche transport du modèle OSI, Netcat rayonne dans le monde underground depuis déjà de nombreuses années. Rien de tel qu’une petite histoire pour parler de ce programme légendaire...

Système extensible et hautement disponible avec Erlang/OTP

Magazine
Marque
GNU/Linux Magazine
Numéro
241
Mois de parution
octobre 2020
Spécialité(s)
Résumé

Erlang est un langage de programmation fonctionnel et distribué, créé à la fin des années 80 pour régler de nombreux problèmes issus du monde des télécoms, et plus généralement de l’industrie. Outre le fait qu’il soit l’une des seules implémentations réussies du modèle acteur disponible sur le marché, son autre grande particularité est d’être livré avec une suite d’outils, de modèles et de principes conçus pour offrir un environnement cohérent. Ce framework, nommé OTP, fait partie intégrante de la vie des développeurs utilisant Erlang au jour le jour...

Les derniers articles Premiums

Les derniers articles Premium

Cryptographie : débuter par la pratique grâce à picoCTF

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

L’apprentissage de la cryptographie n’est pas toujours évident lorsqu’on souhaite le faire par la pratique. Lorsque l’on débute, il existe cependant des challenges accessibles qui permettent de découvrir ce monde passionnant sans avoir de connaissances mathématiques approfondies en la matière. C’est le cas de picoCTF, qui propose une série d’épreuves en cryptographie avec une difficulté progressive et à destination des débutants !

Game & Watch : utilisons judicieusement la mémoire

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Au terme de l'article précédent [1] concernant la transformation de la console Nintendo Game & Watch en plateforme de développement, nous nous sommes heurtés à un problème : les 128 Ko de flash intégrés au microcontrôleur STM32 sont une ressource précieuse, car en quantité réduite. Mais heureusement pour nous, le STM32H7B0 dispose d'une mémoire vive de taille conséquente (~ 1,2 Mo) et se trouve être connecté à une flash externe QSPI offrant autant d'espace. Pour pouvoir développer des codes plus étoffés, nous devons apprendre à utiliser ces deux ressources.

Raspberry Pi Pico : PIO, DMA et mémoire flash

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Le microcontrôleur RP2040 équipant la Pico est une petite merveille et malgré l'absence de connectivité wifi ou Bluetooth, l'étendue des fonctionnalités intégrées reste très impressionnante. Nous avons abordé le sujet du sous-système PIO dans un précédent article [1], mais celui-ci n'était qu'une découverte de la fonctionnalité. Il est temps à présent de pousser plus loin nos expérimentations en mêlant plusieurs ressources à notre disposition : PIO, DMA et accès à la flash QSPI.

Les listes de lecture

9 article(s) - ajoutée le 01/07/2020
Vous désirez apprendre le langage Python, mais ne savez pas trop par où commencer ? Cette liste de lecture vous permettra de faire vos premiers pas en découvrant l'écosystème de Python et en écrivant de petits scripts.
11 article(s) - ajoutée le 01/07/2020
La base de tout programme effectuant une tâche un tant soit peu complexe est un algorithme, une méthode permettant de manipuler des données pour obtenir un résultat attendu. Dans cette liste, vous pourrez découvrir quelques spécimens d'algorithmes.
10 article(s) - ajoutée le 01/07/2020
À quoi bon se targuer de posséder des pétaoctets de données si l'on est incapable d'analyser ces dernières ? Cette liste vous aidera à "faire parler" vos données.
Voir les 53 listes de lecture

Abonnez-vous maintenant

et profitez de tous les contenus en illimité

Je découvre les offres

Déjà abonné ? Connectez-vous