Ecrire des scripts de build avec PSAKE

MSBUILD propose un langage de script orienté XML dans la pure tradition des années 2000.

Aujourd’hui les choses changent et la tendance est aux outils beaucoup plus légers et facile à prendre en main.

PSake (on prononce SA-KI) est justement un tentative d’aller dans cette direction en remplaçant ce MSBUILD Script par PowerShell. Cela présente de nombreux avantages :

  • PowerShell apporte déjà une sémantique riche que PSake n’a pas besoin de reproduire
    Il est honnêtement possible de faire le tour de cet outil en 1 heure ou 2, il faut bien plus de temps avec MSBUILD
  • PowerShell est de toute façon un outil dans lequel on aura de plus en plus besoin d’investir en tant que développeur
    PSake est une raison de plus de démarrer.
  • MSBUILD n’est pas facilement évolutif, les extensions sont complexes à créer, PowerShell est un langage, pas un Framework. Il est donc taillé pour cela
  • Une fois laborieusement appris à manipuler des scripts MSBUILD on a quoi? Des compétences en script MSBUILD, rien d’autre.
    PowerShell ouvre un univers nettement plus vaste. Nous n’avons pas le temps de nous former à une multitude d’outils qui constituent leur propre îlot, il faut rationaliser l’effort de formation.
    En s’appuyant sur l’existant, PowerShell, PSake répond pleinement à cette contrainte

 

J’encourage donc l’usage de PSake.

Notez bien un point: PSake pousse vers la sortie le langage de script XML, pas MSBUILD. PSake n’apporte que la sémantique. L’intelligence c’est toujours MSBUILD qui va la fournir et qui travaillera dans les coulisses.

Ce point est important car il signifie que le risque technique de passer à PSake devient faible.

 

J’espère vous avoir convaincu au moins de lire la suite de ce tutoriel!

L’écosystème

PSake a été initialement proposé par James Kovacs en 2008 et à évolué depuis.

Son écosystème est constitué tout d’abord de son site Github

https://github.com/psake/psake

Que l’on peut compléter par diverses contributions:

https://github.com/psake/psake-contrib

Et bien entendu les innombrables blogs et forums de discussion qui parlent de l’outil.

Installer PSake

On peut l’installer via Nuget ou Chocolatey, mais je préfère en rester à la méthode standard PowerShell d’installation d’un module:

  • On récupère le package sur le site GitHub.
    C’est un fichier Zip qu’il faudra débloquer pour pouvoir l’installer via PowerShell

2016-03-09_18-09-50

  • On se rend dans le répertoire où se trouve le fichier psake.psm1
  • On lance:
    Import-Module .\psake.psm1
  • On lance PowerSell en tant qu’administrateur
  • On exécute:
    Set-ExecutionPolicy RemoteSigned

Si tout s’est bien passé la commande suivante affiche l’aide PSake:

Get-Help Invoke-psake -Full

2016-03-09_18-16-46

Le zip contient aussi des exemple que l’on trouvera dans le sous-répertoire examples:

2016-03-09_18-19-45

Essayons d’en lancer un pour voir:

Invoke-psake .\default.ps1 Clean

2016-03-09_18-22-30

Nous reviendrons sur la syntaxe plus loin, retenez que Invoke-Psake est la commande de base pour lancer un script PSake. Cette commande nous a été apportée par le module PSake.

En attendant examinez un peu le code et essayer de deviner comment il fonctionne. Mon sentiment est que les grandes lignes sont claires.

Les bases

Il est de tradition de commencer par un « Hello World ».


task hello {
   "Hello world!"
}

 

Note: l’accolade ouvrante DOIT être sur la même ligne que la déclaration de la task, autrement cela ne marche pas.

 

On le lance:

invoke-psake tests.ps1 hello

2016-03-09_18-33-41

Ca marche donc!

Les scripts PSake sont organisés autour des Task. On peut avoir autant de Tasks que l’on souhaite dans le script. Une Task est une unité d’exécution.

Les tasks doivent avoir un nom (hello) et un script (une action) (il y a une exception toutefois). Les tasks peuvent apparaître dans un ordre quelconque et nous verrons que l’on peut créer des dépendances pour ordonnancer leur ordre d’exécution.

Il est également possible d’imbriquer les tasks en leur faisant appeler invoke-task.

 

On peut compléter la définition de la task avec une description courte:


task hello -description "première task" {
   "Hello world!"
}

 

On affiche ensuite une documentation du script:

invoke-psake test.ps1 -docs

 

Continuons avec un script un peu plus sophistiqué:

 

task Init -description "Initialisation" { 
   Write-Host "==> Initialisation"
}

task Compile -description "Compiler le code" {
   Write-Host "==> Compiler le code"
}

On a vu qu’on lance une task avec la commande PowerShell Invoke-psake, la syntaxe est la suivante:

invoke-psake [-buildFileName] <nom fichier> <nom task1, nom task2, …>

BuildFileName désigne le nom du fichier de script, il est facultatif. Si le fichier s’appelle default.ps1, le nom du fichier est lui aussi facultatif:

invoke-task  init, compile

On peut préciser une ou plusieurs tasks séparées par une virgule:

invoke-task test.ps1 init, compile

2016-03-09_23-22-13

 

Si la task s’appelle default, son nom est facultatif.

invoke-task test.ps1

 

On peut définir des dépendances entre task à l’aide de la propriété depends:

task Init -description "Initialisation" {
   Write-Host "==> Initialisation"
}

task Compile -description "Compiler le code" {
   Write-Host "==> Compiler le code"
}
 
task test -depends init, compile -description "lancer toutes les tasks" {
   Write-Host "==> Test"
}

L’exécution de la task test sera donc précédée de celle de init, puis compile.

On peut ensuite lancer juste test:

2016-03-09_23-30-19

Il existe une task par défaut appelée default et qui n’accepte pas d’actions (une exception est levée dans ce cas):

task Init -description "Initialisation" {
   Write-Host "==> Initialisation"
}

task Compile -description "Compiler le code" {
   Write-Host "==> Compiler le code"
}
 
task default -depends init, compile -description "lancer toutes les tasks"

 

L’exécution d’un script PSake alimente la variable $psake dans la console PowerShell. Elle donne accès à quelques paramètres du contexte d’exécution:

2016-03-09_23-47-00

On apprend par exemple que le Framework .Net actif est 4.0

 

On peut déclarer des propriétés dans un script en les ajoutant à properties:

 

properties {
   $initMessage = '==> Initialisation'
   $compileMessage = '==> Compiler le code'
}
 
task Init -description "Initialisation" {
   Write-Host $initMessage
}

task Compile -description "Compiler le code" {
   Write-Host $compileMessage
}
 
task default -depends init, compile -description "lancer toutes les tasks"

 

Je ne vais pas le développer ici, mais on peut également mettre en place des actions ou des conditions avant ou après l’exécution de la task à l’aide des propriétés suivantes de task:

  • preAction
  • postAction
  • preCondition
  • postCondition

 

On peut aussi définir des méthodes qui s’exécutent avant (TaskSetup) et après (TaskTearDown) chaque task.

task build   {
   Write-Host "==> $taskName"
}
 
task postbuildAction {
   Write-Host "==> $taskName"
}
 
TaskSetup {
   Write-Host "==> TaskSetup"
}

TaskTearDown {
   Write-Host "==> TaskTearDown"
}
 
task default -depends build , postbuildAction

 

2016-03-10_15-25-08

 

Notre premier script de build

Il est temps de se lancer dans le cœur du sujet. Nous allons faire un build d’un projet. Commencez par créer une solution VS quelconque si vous en avez pas une sous la main.

Commençons avec ce script:

properties {
   $projectName = consoleapplication6.sln'
}
 
task build {
   Write-Host "==> $taskName" 
   msbuild $projectName 
}
 
task default -depends build

La propriété $projectName contient le chemin complet vers un fichier solution ou projet qui sera passé en paramètre de MSBUILD. La task build est justement chargée de lancer le build.

On en profite pour découvrir une nouvelle variable intégrée: $taskName qui contient le nom de la task en cours.

Bien entendu le code suppose que MSBUILD soit déjà présent sur votre machine et le chemin d’accès dans PATH. Il s’agit bien de MSBUILD en ligne de commande, on peut donc y passer tous les paramètres nécessaires. Normalement il y a au moins le nom du projet ou de la solution.

Le résultat de l’exécution dépend bien entendu du projet à créer, mais on peut s’attendre à quelque chose de similaire à ceci:

2016-03-10_14-10-44

 

Que se passe t’il si la compilation échoue? Le plus simple est d’essayer:

2016-03-10_14-35-22

Pas terrible, le script PowerShell ne s’en rend absolument pas compte. C’est normal. MSBUILD n’est pas une commande PowerShell, mais une commande externe. Lorsqu’une exception est levée par MSBUILD elle ne peut donc être récupérée par le script.

La méthode habituelle pour ce type d’application est de renvoyer des codes de retour. Il faut lire la documentation pour savoir quelles valeurs sont susceptibles d’être renvoyées, mais presque toujours le code de retour en cas de succès est 0 et une valeur >0 en cas d’échec. En tout cas c’est ce que fait MSBUILD.

Il est donc indispensable de surveiller ce code depuis notre script, surtout si on doit décider ou pas de lancer des actions postérieures comme déployer en fonction du résultat des tests unitaires.

Ce n’est pas bien difficile de mettre des IF, mais c’est laborieux. PSake propose donc la commande Exec pour faire le travail à notre place. Exec se charge donc de lancer une commande externe et de gérer pour nous le code de retour.

 

properties {
   $projectName = 'maSolution.sln'
}
 
task build {
   Write-Host "==> $taskName"
 
   Exec { msbuild $projectName}
}
 
task default -depends build

Et cette fois:

2016-03-10_14-31-19

Exec permet donc à PSake de savoir qu’un problème a eu lieu. Je vous laisserez regarder la doc, mais on peut paramétrer le message d’erreur ou une politique de réexécution en cas d’échec.

 

Si une task échoue, on a fondamentalement 2 façons de réagir:

  1. On arrête
  2. On continue quand même

On va transformer un peu notre script:

properties {
   $projectName = 'consoleapplication6.sln'
}
 
task build {
   Write-Host "==> $taskName"
   Exec { msbuild $projectName}
}
 
task postbuildAction
{
   Write-Host "==> $taskName"
}
 
task default -depends build , postbuildAction

Vous constaterez en lançant le script, que la première option est celle choisit par défaut. C’est en général ce que l’on souhaite d’ailleurs.

 

Supposons que l’on souhaite tout de même continuer, il suffirait de compléter la définition de build avec continueOnError:


task build -continueOnError {
   Write-Host "==> $taskName"
   Exec { msbuild $projectName}
}

 

Notez qu’il est toujours possible de simuler l’échec d’une task avec cette ligne de commande:


exec {cmd /c exit (1) }

 

Un repository de propriétés

On travaille sur un script de build, on doit donc s’attendre à devoir gérer plusieurs environnement comme: dev, recette, production. Rien ne dit que les propriétés soient les même.

On a vu qu’il est possible de paramétriser un script avec un block de script properties. Sauf qu’elle est en dur dans le script lui-même. Ce n’est pas très utile en l’état.

Il est possible de passer les paramètres à invoke-psake. Voici un scénario envisageable:

Tout d’abord le script de test:

properties {
   $titre = ''
}
 
task message   {
   Write-Host "==> $titre"
}
 
task default -depends message

On remarque la déclaration de propriétés. On peut surcharger les valeurs par défaut du script en passant en paramètre une hashtable (pas un bloc de script dans ce cas):

$properties = @{
   titre = 'Titre du script'
}
 
cls
invoke-psake test.ps1 -properties $properties

Et l’affichage:

2016-03-10_22-42-46

La documentation révèle également un paramètre parameters qui attend également une hashtable. Quelle est la différence?

Properties ne marche que pour les propriétés, c’est à dire les variables définies dans le bloc de script properties. Parameters est capable de passer de nouvelles définitions.

Pour le vérifier, supprimez la fonction properties du script et lancez cette fois:


invoke-psake test.ps1 -parameters $properties

 

Vous constaterez que l’affichage est le même bien que $titre ne soit pas définit explicitement dans le script.

On dispose ainsi de deux solutions pour paramétriser un script PSake.

Plus sur les dépendances

Notre modèle de script sera celui-ci:

properties {
   $initMessage = '==> Initialisation'
   $compileMessage = '==> Compiler le code'
   $traceMessage = '==> Trace'
}
 
task Init -description "Initialisation" {
   Write-Host $initMessage
}
 
task trace -description "Trace" {
   Write-Host $traceMessage
}
 
task Compile -depends trace, init -description "Compiler le code" {
   Write-Host $compileMessage
}
 
task default -depends init, compile -description "lancer toutes les tasks"

 

Remarquez bien la différence avec le script précédent: Compile a lui aussi une dépendance avec Init. J’ai également ajouté une nouvelle task trace on verra pourquoi.

invoke-psake test.ps1

2016-03-10_09-07-58

 

Init est donc lancé une seule fois. PSake a bien vu que la dépendance associée à Compile n’était pas utile si on lance la task default comme le montre la position de init par rapport à trace.

 

Bibliographie

Le seul autre tuto en français que j’ai pu trouver:

Doc MSBUILD:

 

 

 

Laisser un commentaire