English version available here : Azure DevOps & Desktop application

Introduction

La mouvance DevOps est en marche depuis quelques années déjà et cela transparait à travers les demandes grandissantes des clients, les formations dispensées et surtout sa démocratisation au sein de nombreuses équipes de développement. Pourtant derrière ce « Buzz Word » dont le marketing s’est emparé se cachent des concepts qui ne sont pas nouveaux. Le CI / CD, les méthodologies agiles, les outils / process / rôles liés au DevOps étaient déjà largement utilisés par les éditeurs de logiciel, garant de la qualité de leurs logiciels et donc de leur image de marque, en lien direct avec leurs ventes.

La démocratisation de ces méthodes a cependant permis à tout un chacun d’accéder et d’appréhender cette méthodologie dont l’objectif et de livrer mieux et plus souvent des versions d’un applicatif, qu’il s’agisse d’un logiciel in house ou d’une commande client.

Les solutions SaaS sont les cibles phares pour ces méthodologies puisque le client final n’a aucune action à faire pour profiter des mises à jour. Pourtant les applications de type Desktop, anciennement appelés « Client lourds » peuvent pleinement profiter des outils modernes pour faciliter la distribution régulière et c’est ce que nous allons voir tout de suite !

Le DevOps regroupe un ensemble complet de process et de bonnes pratiques, mais nous ferons le raccourci ici de parler principalement dans cet article du CI / CD.

Le CI / CD pour les application Desktop

A quoi fait-on référence lorsque l’on parle de CI / CD dans le contexte d’application Desktop ? Pour rester simple dans la définition, c’est l’ensemble des moyens et outils utilisés pour livrer au client une version finale de l’application en s’assurant de la qualité de la livraison

Nous allons donc automatiser l’intégralité du processus de mise à disposition de notre application à l’aide des pipelines Azure DevOps :

  1. Compilation d’une application WPF
  2. Lancer la batterie de tests unitaires pour s’assurer de la qualité du build
  3. Créer un installateur de type setup.exe
  4. Déployer cet installateur
  5. Informer nos utilisateurs de la disponibilité d’une nouvelle version

Comme souvent dans les articles, nous resterons sur un cas d’application WPF simple, mais il vous sera possible à partir de là d’ouvrir le champ des possibles en fonction de votre propre use case.

Mise en place de l’application de test

J’ai donc créé une application WPF en .NET 4.7.2 en intégrant l’excellent MVVM Light (réalisé par un français, un peu de chauvinisme n’a jamais fait de mal 😉). Il s’agira d’une simple calculatrice avec les opérations de base Additionner / Soustraire / Multiplier / Diviser et une gestion de la division par zéro. J’ai également ajouté le projet de tests unitaires associés à cette calculatrice.

Application Desktop - My DevOps calculator
Notre application de test DevOps calculator

J’ai ensuite créé un projet Azure DevOps dans lequel j’ai hébergé le code source (vous pourrez retrouver le lien à la fin de cet article) et qui nous permettra de créer les pipelines de build et de déploiement pout notre application WPF.

Création du pipeline de build

Le base de notre pipeline de build est assez classique, elle consiste en plusieurs tâches qui permettront de builder, tester puis d’héberger les artifacts du build dans l’espace du projet Azure DevOps.

Nous allons pour cela utiliser un template déjà existant dans l’environnement Azure DevOps. A noter que deux voies sont possibles concernant la création d’un pipeline de build : la voie de l’interface graphique ou la voie du fichier de configuration YAML. Nous allons utiliser le fichier de configuration YAML dans cet exemple qui est celle recommandée par Microsoft.

Il faudra donc créer un nouveau pipeline de Build, puis choisir l’endroit où est stocké le code source, ici Azure Repos Git. Ensuite il faudra choisir le repository Git à utiliser pour récupérer le code à builder. Viendra ensuite le choix du template de base à utiliser pour ce Pipeline, dans notre cas le « .NET Desktop » correspond à ce dont nous avons besoin.

Azure DevOps - Création d'un pipeline de build
Création du pipeline de build avec l’interface Azure DevOps

Vous verrez alors le fichier YAML qui sera généré comme base pour notre pipeline de build. Celui-ci contient plusieurs entités :

  • trigger : Sur quel déclencheur le build doit-il s’exécuter. Par défaut, sur un push dans la branche master.
  • pool : Quel agent doit-être utilisé pour cette action de build. Par défaut, une machine Windows hébergée par Microsoft.
  • variables : Un ensemble de variables utilisées dans le pipeline
  • steps : Comporte réellement les étapes de notre compilation, au nombre de 4 dans cet exemple :
    • Installation des outils nuget
    • Installation des paquets nuget
    • Lancement du build de la solution
    • Lancement des tests

Il faudra ensuite lui rajouter les tâches nécessaires à la publication des artifacts de build afin de pouvoir les récupérer depuis le pipeline de release.
La création de ce fichier YAML entraine l’ajout du fichier azure-pipelines.yaml à la racine de votre repository.

# .NET Desktop
# Build and run tests for .NET Desktop or Windows classic desktop solutions.
# Add steps that publish symbols, save build artifacts, and more:
# https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net

trigger:
- master

pool:
  vmImage: 'windows-latest'

variables:
  solution: '**/*.sln'
  buildPlatform: 'Any CPU'
  buildConfiguration: 'Release'

steps:
- task: NuGetToolInstaller@0

- task: NuGetCommand@2
  inputs:
    restoreSolution: '$(solution)'

- task: VSBuild@1
  inputs:
    solution: '$(solution)'
    platform: '$(buildPlatform)'
    configuration: '$(buildConfiguration)'

- task: VSTest@2
  inputs:
    platform: '$(buildPlatform)'
    configuration: '$(buildConfiguration)'

- task: CopyFiles@2
  inputs:
    SourceFolder: 'MyCalculator\\bin\\Release'
    Contents: '**'
    TargetFolder: '$(Build.ArtifactStagingDirectory)'

- task: PublishBuildArtifacts@1
  inputs:
    PathtoPublish: '$(Build.ArtifactStagingDirectory)'
    ArtifactName: 'drop'
    publishLocation: 'Container'

A l’issue du premier run, tous les voyants devraient être au vert signifiant que toutes les étapes ont réussi (y compris que tous les tests unitaires sont passés).

Etapes de build Azure DevOps réussies
Toutes les étapes du build au vert

Nous sommes maintenant prêts à passer à l’étape suivante, plus spécifique à notre application Desktop : la création de l’installateur.

Création de l’installateur

Plusieurs choix s’offrent à nous pour créer un installateur que l’on pourra distribuer à nos clients. J’ai fait le choix d’Inno Setup pour cet exemple. La création de l’installateur peut se faire soit dans le pipeline de Build soit dans le pipeline de Release. Le faire dans le pipeline de Build permet de s’assurer que notre script Inno Setup fonctionne toujours avec la mise à jour de notre application. Cela permet aussi de refuser des Pull Request si le script d’installation ne fonctionne pas correctement.

J’ai donc ajouté un script .iss assez classique qui prend tous les fichiers DLL, EXE et conf pour être déployé par l’installateur.

[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{23F2EF2D-4F19-43FC-A598-16A78387ADA9}
AppName=My DevOps calculator
AppVersion=1.0
;AppVerName=My DevOps calculator 1.0
AppPublisher=Ensilog
AppPublisherURL=https://ensilog.visualstudio.com/DevOps calculator
AppSupportURL=https://ensilog.visualstudio.com/DevOps calculator
AppUpdatesURL=https://ensilog.visualstudio.com/DevOps calculator
DefaultDirName={pf}\My DevOps calculator
DefaultGroupName=My DevOps calculator
AllowNoIcons=yes
OutputBaseFilename=DevOpsCalc-setup
Compression=lzma
SolidCompression=yes

[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1

[Files]
Source: "bin/Release/*.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin/Release/*.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "bin/Release/*.exe.config"; DestDir: "{app}"; Flags: ignoreversion
; NOTE: Don't use "Flags: ignoreversion" on any shared system files

[Icons]
Name: "{group}\My DevOps calculator"; Filename: "{app}\MyCalcutator.exe"
Name: "{group}\{cm:UninstallProgram,My DevOps calculator}"; Filename: "{uninstallexe}"
Name: "{commondesktop}\My DevOps calculator"; Filename: "{app}\MyCalcutator.exe"; Tasks: desktopicon

[Run]
Filename: "{app}\MyCalcutator.exe"; Description: "{cm:LaunchProgram,My DevOps calculator}"; Flags: nowait postinstall skipifsilent

Pour ajouter la création de l’installateur dans notre pipeline de Build, nous allons ajouter 2 tâches :

  • Une en charge de télécharger et d’installer Inno Setup à l’aide de Chocolatey (installé de base sur les machines de build hostées par Microsoft)
  • Une en charge d’exécuter Inno Setup sur le script .iss (extension d’Inno Setup) se chargeant de créer l’installateur.

Il faudra ensuite modifier les éléments mis dans l’artifact de sortie, car nous n’aurons besoin plus que de l’installateur.

La dernière tâche qu’il restera à faire est tout simplement de créer un pipeline de Release pour pousser l’artifact de sortie du Build sur un serveur de fichier (ex : FTP) avec un lien à envoyer aux clients !

Voici donc le fichier YAML final de notre pipeline de Build :

trigger:
- master

pool:
  vmImage: 'windows-latest'

variables:
  solution: '**/*.sln'
  buildPlatform: 'Any CPU'
  buildConfiguration: 'Release'

steps:
- task: NuGetToolInstaller@0

# Install Inno Setup with chocolatey
- task: PowerShell@2
  displayName: 'Inno setup download'
  inputs:
    targetType: 'inline'
    script: 'choco install innosetup'

- task: NuGetCommand@2
  inputs:
    restoreSolution: '$(solution)'

- task: VSBuild@1
  inputs:
    solution: '$(solution)'
    platform: '$(buildPlatform)'
    configuration: '$(buildConfiguration)'

- task: VSTest@2
  inputs:
    platform: '$(buildPlatform)'
    configuration: '$(buildConfiguration)'

# Execute install script
- task: PowerShell@2
  displayName: 'Execute Inno Setup script'
  inputs:
    targetType: 'inline'
    script: 'iscc.exe MyCalculator\\setup.iss'

# Copy setup into ArtifactStaging folder
- task: CopyFiles@2
  displayName: 'Copy setup to artifact'
  inputs:
    SourceFolder: 'MyCalculator\\Output'
    Contents: '**'
    TargetFolder: '$(Build.ArtifactStagingDirectory)'

# Publish artifact
- task: PublishBuildArtifacts@1
  inputs:
    PathtoPublish: '$(Build.ArtifactStagingDirectory)'
    ArtifactName: 'drop'
    publishLocation: 'Container'

Conclusion

Il aurait bien sur été possible de couvrir beaucoup plus de choses dans cet article mais il vous donne déjà un bon aperçu et de solides pistes pour automatiser vos processus liés à la qualité et la livraison de vos applications Desktop.

Voici quelques autres pistes à explorer pour vous permettre d’aller plus loin :

  • Signature de l’assembly de l’application et / ou de l’installateur
  • Intégration d’Application Insights pour obtenir un tracing efficace des versions déployées et des erreurs rencontrées.
  • Utilisation de ClickOnce pour proposer une mise à jour automatique de l’application à son démarrage, ou tout autre système.
  • Gestion multi-stage pour tester nos installateurs en staging avant de les envoyer en production.

Annexes