Customiser son processus de build avec Msbuild

Lors de la phase d’industrialisation du processus de build d’un projet, on se retrouve souvent confronté à la problématique suivante : comment gérer ses différents environnements (développement, qualification, pré production et production par exemple) ? En effet, chaque environnement va faire appel à des bases de données différentes, des noms de serveurs web distincts ou bien encore des emplacements de publication différenciés si l’on utilise ClickOnce. C’est ce dernier point que je vais prendre pour exemple ici. Comment gérer les différentes urls de déploiement en fonction d’un environnement donné. Bien sûr, ce qui s’applique pour ClickOnce est tout à fait utilisable pour d’autres besoins.
Première étape : Ajouter MsBuildTasks avec NuGet
Lorsque l’on déploie un projet avec ClickOnce en utilisant Visual Studio, ce dernier va générer au cours du processus un fichier nommé publish.htm qui comportera les différentes caractéristiques du projet comme son nom et sa version. Mais ce fichier n’est pas généré si l’on effectue la publication avec msbuild, il faut donc le construire. C’est pour cette raison que l’on utilise les MsBuildsTasks : pouvoir modifier un template en lui donnant les informations nécessaires.
Deuxième étape : Création du fichier paramétrage
Pour cela ajoutez un nouveau fichier dans votre projet nommé par exemple Customized.targets. Ce fichier contiendra les éléments suivants :
<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <BuildEnvironment>Production</BuildEnvironment> <BuildEnvironment>PreProduction</BuildEnvironment> <BuildEnvironment>Qualification</BuildEnvironment> <BuildEnvironment>Developpement</BuildEnvironment> </PropertyGroup> <Choose> <When Condition=" '$(BuildEnvironment)' == 'Production' "> <PropertyGroup> <PublishDir>C:\ClickOnce\Production\</PublishDir> <PublishUrl>$(PublishDir)</PublishUrl> <InstallUrl>http://deployserver/Production/</InstallUrl> </PropertyGroup> </When> <When Condition=" '$(BuildEnvironment)' == 'PreProduction' "> <PropertyGroup> <PublishDir>C:\ClickOnce\PreProduction\</PublishDir> <PublishUrl>$(PublishDir)</PublishUrl> <InstallUrl>http://deployserver/PreProduction/</InstallUrl> </PropertyGroup> </When> <When Condition=" '$(BuildEnvironment)' == 'Qualification' "> <PropertyGroup> <PublishDir>C:\ClickOnce\Qualification\</PublishDir> <PublishUrl>$(PublishDir)</PublishUrl> <InstallUrl>http://deployserver/Qualification/</InstallUrl> </PropertyGroup> </When> <When Condition=" '$(BuildEnvironment)' == 'Developpement' "> <PropertyGroup> <PublishDir>C:\ClickOnce\Developpement\</PublishDir> <PublishUrl>$(PublishDir)</PublishUrl> <InstallUrl>http://deployserver/Developpement/</InstallUrl> </PropertyGroup> </When> </Choose> <PropertyGroup> <!-- Note this must be done AFTER the above Choose (so PublishDir is set)--> <PublishFilePath>$(PublishDir)Publish.htm</PublishFilePath> </PropertyGroup> <ItemGroup> <Tokens Include="PublisherName"> <ReplacementValue>$(PublisherName)</ReplacementValue> <Visible>false</Visible> </Tokens> <Tokens Include="ProductName"> <ReplacementValue>$(ProductName)</ReplacementValue> <Visible>false</Visible> </Tokens> <Tokens Include="ApplicationVersion"> <ReplacementValue>$(ApplicationVersion)</ReplacementValue> <Visible>false</Visible> </Tokens> <Tokens Include="Prerequsites"> <ReplacementValue>@(BootstrapperPackage->'<li>%(ProductName)</li>','%0D%0A')</ReplacementValue> <Visible>false</Visible> </Tokens> <Tokens Include="Username"> <ReplacementValue>$(Username)</ReplacementValue> <Visible>false</Visible> </Tokens> </ItemGroup> <Target Name="AfterPublish"> <Time Format="dd/MM/yyyy HH:mm"> <Output TaskParameter="FormattedTime" PropertyName="PublishTime" /> </Time> <TemplateFile Template="Publish\Publish.htm" Tokens="@(Tokens)" OutputFilename="$(PublishFilePath)" /> <FileUpdate Files="$(PublishFilePath)" Regex="\${PublishTime}" ReplacementText="$(PublishTime)" /> </Target> </Project>
Dans ce fichier on retrouve plusieurs sections. La première permet de définir l’ensemble des environnements. La deuxième permet de surcharger les valeurs contenues dans le fichier csproj. C’est cette partie qui permet de déployer dans différents répertoires en définissant pour le paramètre PublishDir des valeurs différentes. Quant à la troisième section, elle permet de définir les valeurs de remplacement pour le fichier publish.htm. Enfin, la dernière permet d’appliquer au fichier publish.htm la transformation décrite précédemment.
Troisième étape : création du template publish.htm
Cette étape consiste à créer le template qui générera le page publish.htm, pour cela il faut créer un nouveau fichier Publish.htm dans le répertoire Publish à la racine du projet. Voici un exemple de contenu :
<HTML> <HEAD> <TITLE>${ProductName}</TITLE> <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8" /> <STYLE TYPE="text/css"> <!-- BODY{margin-top:20px; margin-left:20px; margin-right:20px; color:#000000; font-family:Tahoma; background-color:white} A:link {font-weight:normal; color:#000066; text-decoration:none} A:visited {font-weight:normal; color:#000066; text-decoration:none} A:active {font-weight:normal; text-decoration:none} A:hover {font-weight:normal; color:#FF6600; text-decoration:none} P {margin-top:0px; margin-bottom:12px; color:#000000; font-family:Tahoma} PRE {border-right:#f0f0e0 1px solid; padding-right:5px; border-top:#f0f0e0 1px solid; margin-top:-5px; padding-left:5px; font-size:x-small; padding-bottom:5px; border-left:#f0f0e0 1px solid; padding-top:5px; border-bottom:#f0f0e0 1px solid; font-family:Courier New; background-color:#e5e5cc} TD {font-size:12px; color:#000000; font-family:Tahoma} H2 {border-top: #003366 1px solid; margin-top:25px; font-weight:bold; font-size:1.5em; margin-bottom:10px; margin-left:-15px; color:#003366} H3 {margin-top:10px; font-size: 1.1em; margin-bottom: 10px; margin-left: -15px; color: #000000} UL {margin-top:10px; margin-left:20px} OL {margin-top:10px; margin-left:20px} LI {margin-top:10px; color: #000000} FONT.value {font-weight:bold; color:darkblue} FONT.key {font-weight: bold; color: darkgreen} .divTag {border:1px; border-style:solid; background-color:#FFFFFF; text-decoration:none; height:auto; width:auto; background-color:#cecece} .BannerColumn {background-color:#000000} .Banner {border:0; padding:0; height:8px; margin-top:0px; color:#ffffff; filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=1,StartColorStr='#1c5280',EndColorStr='#FFFFFF');} .BannerTextCompany {font:bold; font-size:18pt; color:#cecece; font-family:Tahoma; height:0px; margin-top:0; margin-left:8px; margin-bottom:0; padding:0px; white-space:nowrap; filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=2,OffY=2,Color='black',Positive='true');} .BannerTextApplication {font:bold; font-size:18pt; font-family:Tahoma; height:0px; margin-top:0; margin-left:8px; margin-bottom:0; padding:0px; white-space:nowrap; filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=2,OffY=2,Color='black',Positive='true');} .BannerText {font:bold; font-size:18pt; font-family:Tahoma; height:0px; margin-top:0; margin-left:8px; margin-bottom:0; padding:0px; filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=2,OffY=2,Color='black',Positive='true');} .BannerSubhead {border:0; padding:0; height:16px; margin-top:0px; margin-left:10px; color:#ffffff; filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=1,StartColorStr='#4B3E1A',EndColorStr='#FFFFFF');} .BannerSubheadText {font:bold; height:11px; font-size:11px; font-family:Tahoma; margin-top:1; margin-left:10; filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=2,OffY=2,Color='black',Positive='true');} .FooterRule {border:0; padding:0; height:1px; margin:0px; color:#ffffff; filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=1,StartColorStr='#4B3E1A',EndColorStr='#FFFFFF');} .FooterText {font-size:11px; font-weight:normal; text-decoration:none; font-family:Tahoma; margin-top:10; margin-left:0px; margin-bottom:2; padding:0px; color:#999999; white-space:nowrap} .FooterText A:link {font-weight:normal; color:#999999; text-decoration:underline} .FooterText A:visited {font-weight:normal; color:#999999; text-decoration:underline} .FooterText A:active {font-weight:normal; color:#999999; text-decoration:underline} .FooterText A:hover {font-weight:normal; color:#FF6600; text-decoration:underline} .ClickOnceInfoText {font-size:11px; font-weight:normal; text-decoration:none; font-family:Tahoma; margin-top:0; margin-right:2px; margin-bottom:0; padding:0px; color:#000000} .InstallTextStyle {font:bold; font-size:14pt; font-family:Tahoma; a:#FF0000; text-decoration:None} .DetailsStyle {margin-left:30px} .ItemStyle {margin-left:-15px; font-weight:bold} .StartColorStr {background-color:#4B3E1A} .JustThisApp A:link {font-weight:normal; color:#000066; text-decoration:underline} .JustThisApp A:visited {font-weight:normal; color:#000066; text-decoration:underline} .JustThisApp A:active {font-weight:normal; text-decoration:underline} .JustThisApp A:hover {font-weight:normal; color:#FF6600; text-decoration:underline} --> </STYLE> </HEAD> <BODY> <TABLE WIDTH="100%" CELLPADDING="0" CELLSPACING="2" BORDER="0"> <!-- Begin Banner --> <TR><TD><TABLE CELLPADDING="2" CELLSPACING="0" BORDER="0" BGCOLOR="#cecece" WIDTH="100%"><TR><TD><TABLE BGCOLOR="#1c5280" WIDTH="100%" CELLPADDING="0" CELLSPACING="0" BORDER="0"><TR><TD CLASS="Banner" /></TR><TR><TD CLASS="Banner"><SPAN CLASS="BannerTextCompany">${PublisherName}</SPAN></TD></TR><TR><TD CLASS="Banner"><SPAN CLASS="BannerTextApplication">${ProductName}</SPAN></TD></TR><TR><TD CLASS="Banner" ALIGN="RIGHT" /></TR></TABLE></TD></TR></TABLE></TD></TR> <!-- End Banner --> <!-- Begin Dialog --> <TR><TD ALIGN="LEFT"><TABLE CELLPADDING="2" CELLSPACING="0" BORDER="0" WIDTH="540"><TR><TD WIDTH="496"> <!-- Begin AppInfo --> <TABLE><TR><TD COLSPAN="3"> </TD></TR><TR><TD><B>Name:</B></TD><TD WIDTH="5"><SPACER TYPE="block" WIDTH="10" /></TD><TD>${ProductName}</TD></TR><TR><TD COLSPAN="3"> </TD></TR><TR><TD><B>Version:</B></TD><TD WIDTH="5"><SPACER TYPE="block" WIDTH="10" /></TD><TD>${ApplicationVersion}</TD></TR><TR><TD COLSPAN="3"> </TD></TR><TR><TD><B>Publisher:</B></TD><TD WIDTH="5"><SPACER TYPE="block" WIDTH="10" /></TD><TD>${PublisherName}</TD></TR><tr><td colspan="3"> </td></tr></TABLE> <!-- End AppInfo --> <!-- Begin Prerequisites --> <TABLE ID="BootstrapperSection" BORDER="0"><TR><TD COLSPAN="2">The following prerequisites are required:</TD></TR><TR><TD WIDTH="10"> </TD><TD><UL> <LI>Windows Installer 4.5</LI> <LI>Microsoft .NET Framework 4 (x86 and x64)</LI> <LI>Microsoft .NET Framework 4 (x86 and x64) and Update for .NET Framework 4 (KB2468871)</LI> </UL></TD></TR><TR><TD COLSPAN="2"> If these components are already installed, you can <SPAN CLASS="JustThisApp"><A HREF="Unibail.Fid.Launcher.application">launch</A></SPAN> the application now. Otherwise, click the button below to install the prerequisites and run the application. </TD></TR><TR><TD COLSPAN="2"> </TD></TR></TABLE> <!-- End Prerequisites --> </TD></TR></TABLE> <!-- Begin Buttons --> <TR><TD ALIGN="LEFT"><TABLE CELLPADDING="2" CELLSPACING="0" BORDER="0" WIDTH="540" STYLE="cursor:hand" ONCLICK="window.navigate(InstallButton.href)"><TR><TD ALIGN="LEFT"><TABLE CELLPADDING="1" BGCOLOR="#333333" CELLSPACING="0" BORDER="0"><TR><TD><TABLE CELLPADDING="1" BGCOLOR="#cecece" CELLSPACING="0" BORDER="0"><TR><TD><TABLE CELLPADDING="1" BGCOLOR="#efefef" CELLSPACING="0" BORDER="0"><TR><TD WIDTH="20"><SPACER TYPE="block" WIDTH="20" HEIGHT="1" /></TD><TD><A ID="InstallButton" HREF="setup.exe">Install</A></TD><TD width="20"><SPACER TYPE="block" WIDTH="20" HEIGHT="1" /></TD></TR></TABLE></TD></TR></TABLE></TD></TR></TABLE></TD><TD WIDTH="15%" ALIGN="right" /></TR></TABLE></TD></TR> <!-- End Buttons --> </TD></TR> <!-- End Dialog --> <!-- Spacer Row --> <TR><TD> </TD></TR> <TR><TD> <!-- Begin Footer --> <TABLE WIDTH="100%" CELLPADDING="0" CELLSPACING="0" BORDER="0" BGCOLOR="#ffffff"><TR><TD HEIGHT="5"><SPACER TYPE="block" HEIGHT="5" /></TD></TR><TR><TD CLASS="FooterText" ALIGN="center"><A HREF="http://go.microsoft.com/fwlink/?LinkId=154571">ClickOnce and .NET Framework Resources</A> </TD></TR><TR><TD HEIGHT="5"><SPACER TYPE="block" HEIGHT="5" /></TD></TR><TR><TD HEIGHT="1" bgcolor="#cecece"><SPACER TYPE="block" HEIGHT="1" /></TD></TR></TABLE> <!-- End Footer --> </TD></TR> </TABLE> </BODY> </HTML>
Ce fichier comporte un certain nombre de balises comme par exemple celles servant à générer le titre : ${ProductName}. Ce sont ces balises qui seront remplacées par les valeurs prédéfinies précédemment. Il est ainsi possible de générer pour chaque publication un fichier différent qui comportera les bonnes informations.
Dernière étape : modification du csproj et lancement du build
Maintenant que tout est prêt, il reste uniquement à modifier le csproj du projet principal en ajoutant les deux lignes suivantes :
<Import Project="$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.Targets" /> <Import Project="Customized.targets" />
Ces deux lignes permettent d’ajouter la prise en charge des MsBuildTasks et de notre fichier. Maintenant avec la commande ci-dessous, on peut lancer une publication avec Msbuild en spécifiant l’environnement souhaité. Il ne reste plus qu’à intégrer cette commande dans votre server de build.
Msbuild Projet.sln /target:Publish /p:BuildEnvironment=Production /p:ApplicationVersion=1.1.0.0
Bonjour,
merci beaucoup pour la réponse. J’ai bien placé le template Publish.htm. Msbuild est censé créer une nouvelle page dans le dossier de déploiement avec les valeurs remplacées ? Pourriez-vous m’envoyer un projet fonctionnel ? C’est beaucoup demandé mais cela me serait très utile et peut-être à d’autres, vu qu’il est difficile de se débrouiller avec tout ça pour un débutant…
Sinon, je vous transmets mon application de test, si vous pouvez regarder ce qui ne va pas… : https://dl.dropboxusercontent.com/u/9197067/Test.ClickOnce.zip
Merci d’avance.
Bonjour, je viens de publier un exemple complet à l’adresse suivante : https://github.com/melcom/ClickOnce. J’espère que cela vous aidera.
Merci pour ce tutoriel. Difficile de trouver quelque chose de récent et de complet pour le déploiement. Malheureusement, je n’arrive pas à générer le fichier Publish.htm, j’ai pourtant créer un nouveau projet et suivi le tutoriel à la lettre… Une idée ?
Bonjour, avez vous placé le fichier Publish.htm dans un répertoire Publish dans votre projet ? Est-ce que le fichier est bien déplacé ? Les balises de type ${PublisherName} ne sont pas correctement remplacées ? Merci pour vos réponses.