Nov/090
Simplification des appels à erlang depuis ruby
Dans les articles précédents, les appels au processus erlang
via la ligne de commande étaient écrits ainsi :
system "erl -pa #{EBIN_DIR} #{ROOT}/deps/*/ebin -boot start_sasl -s reloader -s inets -s xxx -mnesia dir 'db'"
On améliore l’écriture de cette commande afin de la rendre plus lisible et maintenable. Pour cela, on crée une fonction wrapper
comme ci dessous :
def erl(args, options) dirs = "-pa #{options[:dirs].join(' ')}" if options[:dirs] run = "-s init stop" if options[:init] noshell = "-noshell -noinput" if options[:noshell] func = "-s #{options[:func][0]} #{options[:func][1]}" if options[:func] mods = options[:mods].map { |e| "-s #{e}" }.join(' ') if options[:mods] boot = "-boot #{options[:boot]}" if options[:boot] puts "erl #{dirs} #{boot} #{noshell} #{mods} #{args} #{func} #{run}" system "erl #{dirs} #{boot} #{noshell} #{mods} #{args} #{func} #{run}" end
La commande peut être ré-écrite ainsi :
erl "-mnesia dir 'db'", :boot => "start_sasl", :mods => ["reloader", "inets", "xxx"], :dirs => [EBIN_DIR, "#{ROOT}/deps/*/ebin"]
Ce qui, me semble, est plus compréhensible que précédemment. On peut remarquer que le cas de la base mnesia
n’a pas encore été pris en compte.
Nov/090
rush, un peu plus d’OOP dans les scripts
Problématique
Le lancement de l’application se fait par une tâche Rake, comme décrit précédemment dans le premier article concernant les builds.
task :start => :build do ...
La tâche de lancement est dépendante d’un build
complet.
Cependant, on ne prend pas en compte l’environnement, et différents processus doivent être lancés avant de démarrer l’application. Il s’agit d’une dépendance sur des processus externes, et cela est généralement réalisé par des appels système.
Rush
Cependant, les commandes à créer sont un peu complexes et lourdes à maintenir. Pour cela, nous utiliserons donc une application Ruby
qui s’inscrit dans la direction des shell
basés sur de la programmation objet, comme le powershell
de Microsoft.
Bienvenue à Rush. Rush a été créé pour les tâches d’administration liées au projet Heroku comme expliqué dans un article de l’auteur.
Vous pouvez également consulter une présentation de rush, qui expliquera bien mieux que moi les intérêts d’une telle technologie.
On l’installe comme tout autre gem
:
gem install rush
A noter que je suis passé à gemcutter pour la gestion des modules (nouveau site officiel de gestion des gems
).
ATTENTION ! Rake
n’est pas vraiment compatible avec les systèmes de shell
(et ceci m’a coûté beauuucoup de temps). En effet Rake
redéfini les entrées et sorties standard, ce qui fait qu’il n’est plus possible de lire sur l’entrée standard, et donc qu’il est impossible de rediriger des commandes par pipe
. C’est pourquoi les commandes shell
seront écrites dans un fichier séparé.
Si vous insistez, vous vous retrouverez avec cette erreur peu compréhensible :
rake aborted!
Broken pipe
Start
On peut étendre la tâche de démarrage pour ajouter une dépendance à l’environnement :
task :start => [ :build, 'init:env' ] do end
et créer une tâche dans l’espace init
:
require 'rush' namespace 'init' do desc "Init servers" task :env do app = Rush[ROOT] app["*.dump"].each{ |f| f.destroy } app["*.swp"].each{ |f| f.destroy } system "#{ROOT}/process.sh" end end
Ici, on efface des fichiers temporaires à chaque nouveau démarrage en utilisant la syntaxe rush
(ROOT
est une constante qui représente le chemin racine du projet).
Un point important, comme indiqué précédemment, c’est que la gestion des processus est délélguée à un fichier externe, et lancée dans un shell
externe par la commande ruby
directement. Ceci car rake
et la gem
session
ne sont pas compatibles.
En tout cas, il n’est pas possible de démarrer une session bash
dans une tâche rake
, l’inverse étant sûrement possible.
Gestion des processus
La tâche d’initialisation a pour but de vérifier que les processus serveurs sont lancés, et si non de les lancer. Les serveurs sont actuellement Nginx et CouchDB.
On peut écrire le code ainsi (dans process.sh
) :
#!/usr/bin/ruby -rubygems require "rush" if Rush::box.processes.filter(:cmdline => /nginx/).empty? puts "Starting nginx" Rush.box.bash "nginx", :user => "root" end if Rush::box.processes.filter(:cmdline => /couchdb/).empty? puts "Starting couchdb" Rush.box.bash "couchdb -b", :user => "root" end
Et les processus ne seront démarrés que s’ils n’existent pas.
La commande indiquée dans le site est la suivante :
if Rush::box.processes.filter(:cmdline => /nginx/).alive?
Malheureusement, cela ne fonctionne pas du fait que les processus sont lancés en tant que root
, et je n’ai pas trouvé comment contourner ce problème.
Oct/090
Internationalisation des vues
Moteur de transformation
L’application est bien évidemment internationalisée et donc disponible en plusieurs langues.
Le principe est le suivant :
- On ne travaille que sur un seul modèle de vue
- Tous les textes sont traduits dans un fichier de propriétés
- Les vues sont uniquement statiques, pas de traduction à la volée
Le premier point est d’utiliser un mécanisme permettant de traduire les clés des fichiers modèles en traductions. Il est possible de définir son propre mécanisme de traduction assez simplement… cependant, ici, il semble plus intéressant de se reposer sur l’API
fournie par la dernière version de Rails, qui est disponible en tant que gem
et utilisable de façon autonome.
Oct/090
Vim et Rake
La structure de notre projet implique des phases de pré-processing permettant soit de compiler des sources en binaires, soit de transformer des modèles de vues en fichiers cibles.
Ces actions sont réalisées via le gestionnaire de tâches rake
. Ces actions sont effectuées pour l’instant en ligne de commandes. Par exemple :
$ rake -T rake build # Build the sources rake clean # Clean workspace rake compile # Compile the sources rake db:create # Initialise a database rake edoc # Create documentation rake front # Preprocess views and styles rake release # Packaging rake start # Start application rake test # Unit tests $ rake front Creating style /d/apps/gns/priv/www/assets/styles/application.css $
Nous allons intégrer ces commandes au sein de Vim
afin de simplifier le processus (ne pas avoir à jongler entre les fenêtres) et l’automatiser (ne rien oublier afin de ne pas s’énerver parce que ça ne marche pas !).
Sep/090
Automatisation des microtests Erlang via Rake
Les fonctions de tests unitaires commencent à apparaitre dans plusieurs fichiers, et il devient nécessaire de pouvoir régulièrement rejouer l’ensemble de ces tests. Ceci ne pouvant se faire que de manière automatique pour être efficace.
Attention on rappelle qu’il ne s’agit ici que de vérifier simplement et rapidement des comportements écrits directement dans les fichiers sources. Il est évident que pour des opérations plus complexes, un réel outil de tests unitaires tel que eUnit devra être mis en place.
Pour cela, on va réutiliser ce que l’on a déjà mis en place précédemment concernant l’exécution de la machine virtuelle Erlang via Rake (voir posts précédents).
Le but est de parcourir l’ensemble des fichiers sources .erl
et déterminer pour chacun d’eux si une fonction tes/0
a été définie, auquel cas on exécute la méthode du module en question.
L’analyse du fichier sera une bête expression régulière, et pour cela on patch la classe File
de Ruby afin de conserver une certaine harmonie dans l’écriture :
class File def self.contains?(filename, regexp) text = File.read filename return text.match(regexp) != nil end end
Pas très efficace comme programmation mais nous ne sommes concernés que par des fichiers sources qui sont, par définition, des fichiers textes de petite taille.
On crée alors une tâche Rake
:
desc "Unit tests" task :test => :compile do sources.each do |source| if File.contains? source, /^test\(\) ->$/ mod = File.basename(source, ".erl") puts "Unit testing #{mod}" system "erl -pa #{EBIN_DIR} #{ROOT}/deps/*/ebin -noshell -noinput -s inets -s crypto -s #{mod} test -s init stop" end end end
Cette tâche dépend de la compilation afin de pouvoir jouer les fonctions.
On a repris la commande système utilisée pour lancer l’application. Certains paramètres ont été ajoutés :
-noshell -noinput # Exécution en mode console non interactive -s inets -s crypto # Démarrage de modules nécessaires, ici crypto est lié aux cookies -s #{mod} test # Exécution de la fonction test() du module mod -s init stop # Arrêt de la machine virtuelle
Jul/090
Le retour de Ruby : Haml
On ne change pas les bonnes habitudes, et une des premières chose qui me manque sur ce projet est l’utilisation des outils tels que Haml et Sass1.
Heureusement, il est très simple de les rajouter !
Jul/090
Build des applications Erlang
Mochiweb crée un squelette applicatif en copiant les sources vers le répertoire destination. Ces sources doivent être compilés pour obtenir un byte code exécuté par le shell Erlang.
Dans le cas d’un fichier simple, la commande erlc
peut être appelée directement. Dans le cadre d’une application plus évoluée (et comme pour tous les autres langages compilés), il est nécessaire d’avoir des processus de build un peu plus complexes.
Pour Mochiweb, et comme vu précédemment, un makefile est fourni. Cependant, l’outil make
est assez peu intuitif à utiliser, et étant un grand utilisateur de Ruby je me dirige naturellement vers rake.
La première étape sera de recréer les commandes de makefile en Rake.