SBT Partie 1 : Découverte
SBT est l’outil de build de prédilection des développeurs Scala. Il est utilisé par le framework Play ou par le module activator de Lightbend. Au delà d’une guerre de tranchée dans le mode “c’est moi qui est l’outil de build le plus mieux !”, SBT est rapide à mettre en place. Il offre des fonctionnalités intéressantes et surtout il est simple d’étendre ses fonctionnalités. Un savant mélange de Maven et Ant, une alternative à Gradle, avec du Scala à la place de XML ou de Groovy.
Installation
Rien de bien compliqué, il suffit de suivre les instructions sur le site de SBT.
Structure des répertoires
Dès l’installation on peut tout de suite compiler et lancer un programme. Pour cela il suffit de :
- Créer une classe dans un répertoire vide
- sbt compile
- sbt run
Comme nous aimons ranger notre code, SBT suit la convention Maven :
main | |
java | Le code Java |
resources | Les fichiers à inclure dans le jar |
scala | Le code scala |
test | |
java | Le code Java des tests |
resources | Les fichiers à inclure pour les tests |
scala | Le code Scala pour les tests |
Pour lancer une action dans SBT, il existe le mode batch qui va lancer la tâche et sortir de SBT et il existe le mode interactif qui nous permet d’entrer dans un prompt spécifique à SBT.
Mode batch
Pour lancer une commande avec SBT, il suffit d’entrer la ligne suivante :
si une tâche prend des arguments, il suffit de tout mettre entre “
Attention, dans SBT, pour lancer des tâches dans des sous-projets, il faut rester à la racine du projet et taper la commande :
## Mode interactifPour accéder au mode interactif, il suffit de taper sbt sans aucun argument :
On peut ensuite taper une commande :
Ce mode est intéressant car il va nous permettre d’interagir plus simplement avec SBT, notamment avec de l’autocomplétion :
Un historique des commandes s’affiche en utilisant les commandes commençant par “!” :
Avoir la liste des tâches :
Une des fonctionnalités les plus intéressantes est le mode continu qui permet de lancer en permanence une tâche lorsqu’un changement se produit. C’est très intéressant pour lancer ses tests unitaires en tâche de fond. Pour faire ceci, il suffit de précéder une tâche par “~” :
Comment ça marche un build SBT ?
Un projet SBT est une Map décrivant le projet et le but d’un build va être de transformer cette Map grâce à des Setting[T] (où T est le type de la valeur dans la Map) qui sont associés à une entrée de la Map. Lorsque nous décrivons un build, nous ne faisons rien de plus que construire une liste de Setting[T].
Admettons que nous partons d’une Map vide et que nous appliquons les Setting suivants :
- Attribuer le nom hello à l’entrée name.
- Générer un jar portant le nom du projet et l’associé à l’entrée package.
Dans cet exemple, nous voyons que l’étape 2 dépend de l’étape 1, SBT est très malin et il va trier la liste dans le bon ordre pour que l’étape 1 soit exécutée avant l’étape 2
Un *Key* peut être de 3 types :- SettingKey[T] : Le code contenu est exécuté une seul fois au chargement du build
- TaskKey[T] : Le code contenu est exécuté lorsque l’on fait appelle à lui. Une tâche peut avoir des effets de bord, comme par exemple écrire un fichier sur le disque
- InputKey[T] : Une TaskKey avec des arguments
Attention, une TaskKey n’est exécuté qu’une seul fois par lancement. SBT va regarder toutes les dépendances et il va faire une phase de dé-duplication. Il n’est donc pas possible de faire par exemple une tâche avec l’algorithme suivant :
myTaskWhichDoesntDoWhatWeExcepted
Algorithme
- J’appelle la tâche clean
- Je génère des classes
- J’appelle une nouvelle fois la tâche clean
Résultat
- Les classes générés à l’étape 2 existe, car la tâche clean est lancée une seul fois avant myTaskWhichDoesntDoWhatWeExcepted
Le dernier point à connaître pour comprendre le fonctionnement de SBT est la notion de scope. Dans la Map générée par SBT, la clé n’est pas seulement le nom, mais le nom + le scope, ceci nous permet d’avoir plusieurs valeurs pour une clé.
Le scope peut être de 3 types :
- Project : la propriété name a une valeur différente selon le projet où l’on se trouve.
- Configuration : la propriété est différente si on est en configuration de test ou de compilation.
- Task : la propriété est différente selon la tâche où l’on se trouve.
Comment on décrit un build ?
Il y a plusieurs façon de décrire un build SBT :
- Un projet simple en .sbt.
- Un projet multiple en .sbt.
- Un projet Scala.
Les builds écrits en Scala se trouvent dans un sous-répertoire project. Ce répertoire est un autre projet SBT contenant le code Scala nécessaire au build, ce qui permet de bien séparer le code métier, du code spécifique au build. Si nous souhaitons réutiliser le code Scala du build pour en faire un plugin, nous avons déjà un projet SBT !
Dans ce post, nous ne décrirons pas de build en Scala, mais nous utiliserons le répertoire project pour insérer le code de nos tâches complexes. La description du build sera fait dans un fichier build.sbt à la racine du projet.
Le build.sbt peut avoir deux formes, selon si nous sommes dans un projet simple ou un projet multiple :
Simple
Multiple La définition d’un build SBT, consiste donc à enchaîner une succession de définition de *Setting*. Lorsqu’on travaille sur un projet simple, sans étendre les fonctionnalités les *Setting *seront de type *SettingKey[T]*, nous allons voir les plus importantes.Gestion des dépendances
Il y a deux modes de gestion des dépendances.
Le mode unmanaged
Il suffit de mettre des jars dans le répertoire lib pour qu’ils soient ajoutés dans le classpath. En jouant avec les scopes, il est possible de définir un répertoire pour les librairies de test, mais nous n’aborderons pas ce point, car qui utilise encore le unmanaged ?
Le mode managed
Dans ce mode, nous allons ajouter des entrées à libraryDependencies, qui est type SettingKey[Seq[ModuleID]]. Il faut donc ajouter des ModuleID, ce qui est simplifié grâce à l’ajout de sucres syntaxiques dans un build.sbt :
Entre l’organisation et le nom, il est possible d’utiliser “%%” à la la place de “%”, pour indiquer que la dépendance doit prendre en compte la version de Scala. Par exemple : Pour gérer les librairies, SBT utilise Ivy. Il est donc possible d’utiliser des numéros de version dynamiques, comme par exemple 2.2.+, pour avoir la version 2.2 la plus récente. Vous pouvez retrouver toutes les possibilités des versions dynamiques [ici](https://ant.apache.org/ivy/history/2.3.0/ivyfile/dependency.html#revision). Vous pouvez ajouter des *repositories* en utilisant la propriété *resolvers* : ## Options Scala / SBT # ConclusionSBT est un outil puissant pour décrire un build, qui est simple à prendre en main, une fois bien compris le système. SBT offre des avantages, comme :
- Le fait d’écrire son build en *Scala *ce qui permet d’avoir de l’auto-complémention dans les IDE
- Le mode interactif
- La gestion de dépendance avec ivy
Nous verrons dans le prochain article de la série comment créer un projet multiple avec SBT