web-dev-qa-db-fra.com

En utilisant msbuild, je veux mettre à jour un fichier de configuration avec les valeurs de teamcity

J'ai du XML qui ressemble à ceci:

<?xml version="1.0" encoding="utf-8"?>
<XmlConfig instancetype="XmlConfig, Processing, Version=1.0.0.0, Culture=neutral">
  <item>
    <key>IsTestEnvironment</key>
    <value>True</value>
    <encrypted>False</encrypted>
  </item>
  <item>
    <key>HlrFtpPutDir</key>
    <value>C:\DevPath1</value>
    <encrypted>False</encrypted>
  </item>
  <item>
    <key>HlrFtpPutCopyDir</key>
    <value>C:\DevPath2</value>
    <encrypted>False</encrypted>
  </item>

   ....

</Provisioning.Lib.Processing.XmlConfig>

Dans TeamCity, j'ai de nombreuses propriétés système:

 system.HlrFtpPutDir     H:\ReleasePath1
 system.HlrFtpPutCopyDir H:\ReleasePath2

Quelle sorte de magie MsBuild puis-je utiliser pour pousser ces valeurs dans mon fichier XML? En tout, il y a une vingtaine d'articles.

40
Loofer

Je viens de bloguer à ce sujet ( http://sedodream.com/2011/12/29/UpdatingXMLFilesWithMSBuild.aspx ) mais je vais également coller les informations ici pour vous.

Aujourd'hui, je viens de voir une question publiée sur StackOverflow demandant comment mettre à jour un fichier XML à l'aide de MSBuild lors d'une génération de CI exécutée à partir de Team City.

Il n'y a pas de réponse unique correcte, il existe plusieurs façons de mettre à jour un fichier XML pendant une génération. Notamment:

  1. Utilisez SlowCheetah pour transformer les fichiers pour vous
  2. Utilisez directement la tâche TransformXml
  3. Utiliser la tâche XmlPoke intégrée (MSBuild 4.0)
  4. Utiliser une bibliothèque de tâches tierce

1 Utilisez SlowCheetah pour transformer les fichiers pour vous

Avant de commencer à lire trop loin dans ce post, permettez-moi d'abord de passer en revue l'option # 3, car je pense que c'est l'approche la plus simple et la plus facile à maintenir. Vous pouvez télécharger mon complément SlowCheetah XML Transforms Visual Studio. Une fois que vous faites cela pour vos projets, vous verrez une nouvelle commande de menu pour transformer un fichier lors de la construction (pour les projets Web sur le package/publication). Si vous créez à partir de la ligne de commande ou d'un serveur CI, les transformations doivent également s'exécuter.

2 Utilisez directement la tâche TransformXml

Si vous voulez une technique où vous avez un fichier XML "principal" et que vous souhaitez pouvoir contenir des transformations vers ce fichier à l'intérieur d'un fichier XML séparé, vous pouvez utiliser directement la tâche TransformXml. Pour plus d'informations, consultez mon précédent article de blog sur http://sedodream.com/2010/11/18/XDTWebconfigTransformsInNonwebProjects.aspx

3 Utilisez la tâche XmlPoke intégrée

Parfois, cela n'a pas de sens de créer un fichier XML avec des transformations pour chaque fichier XML. Par exemple, si vous avez un fichier XML et que vous souhaitez modifier une seule valeur, mais pour créer 10 fichiers différents, l'approche de transformation XML ne s'adapte pas bien. Dans ce cas, il pourrait être plus facile d'utiliser la tâche XmlPoke. Notez que cela nécessite MSBuild 4.0.

Voici le contenu de sample.xml (issu de la question SO).

<Provisioning.Lib.Processing.XmlConfig instancetype="XmlConfig, Processing, Version=1.0.0.0, Culture=neutral">
  <item>
    <key>IsTestEnvironment</key>
    <value>True</value>
    <encrypted>False</encrypted>
  </item>
  <item>
    <key>HlrFtpPutDir</key>
    <value>C:\DevPath1</value>
    <encrypted>False</encrypted>
  </item>
  <item
    <key>HlrFtpPutCopyDir</key>
    <value>C:\DevPath2</value>
    <encrypted>False</encrypted>
  </item>
</Provisioning.Lib.Processing.XmlConfig>

Donc, dans ce cas, nous voulons mettre à jour les valeurs de l'élément de valeur. Donc la première chose que nous devons faire est de trouver le XPath correct pour tous les éléments que nous voulons mettre à jour. Dans ce cas, nous pouvons utiliser les expressions XPath suivantes pour chaque élément de valeur.

  • /Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutDir'[/value
  • /Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutCopyDir'[/value Je ne vais pas passer en revue ce que vous devez faire pour déterminer le XPath correct car ce n'est pas le but de cet article. Il y a un tas de ressources liées à XPath sur les interwebs. Dans la section des ressources, j'ai lié au testeur XPath en ligne que j'utilise toujours.

Maintenant que nous avons les expressions XPath requises, nous devons construire nos éléments MSBuild pour tout mettre à jour. Voici la technique globale:

  1. Placer toutes les informations pour toutes les mises à jour XML dans un élément
  2. Utilisez XmlPoke avec le traitement par lots MSBuild pour effectuer toutes les mises à jour

Pour le n ° 2, si vous n'êtes pas familier avec le traitement par lots MSBuild, je vous recommande d'acheter mon livre ou vous pouvez consulter les ressources en ligne que j'ai sur le traitement par lots (le lien est ci-dessous dans la section ressources). Vous trouverez ci-dessous un simple fichier MSBuild que j'ai créé, UpdateXm01.proj.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="UpdateXml" xmlns="http://schemas.Microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <SourceXmlFile>$(MSBuildProjectDirectory)\sample.xml</SourceXmlFile>
    <DestXmlFiles>$(MSBuildProjectDirectory)\result.xml</DestXmlFiles>
  </PropertyGroup>

  <ItemGroup>
    <!-- Create an item which we can use to bundle all the transformations which are needed -->
    <XmlConfigUpdates Include="ConfigUpdates-SampleXml">
      <XPath>/Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutDir']/value</XPath>
      <NewValue>H:\ReleasePath1</NewValue>
    </XmlConfigUpdates>

    <XmlConfigUpdates Include="ConfigUpdates-SampleXml">
      <XPath>/Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutCopyDir']/value</XPath>
      <NewValue>H:\ReleasePath2</NewValue>
    </XmlConfigUpdates>
  </ItemGroup>

  <Target Name="UpdateXml">
    <Message Text="Updating XML file at $(DestXmlFiles)" />
    <Copy SourceFiles="$(SourceXmlFile)"
          DestinationFiles="$(DestXmlFiles)" />
    <!-- Now let's execute all the XML transformations -->
    <XmlPoke XmlInputPath="$(DestXmlFiles)"
             Query="%(XmlConfigUpdates.XPath)"
             Value="%(XmlConfigUpdates.NewValue)"/>
  </Target>
</Project>

Les éléments à surveiller sont l'élément XmlConfigUpdates et le contenu de la tâche UpdateXml elle-même. En ce qui concerne les XmlConfigUpdates, ce nom est arbitraire, vous pouvez utiliser le nom que vous voulez, vous pouvez voir que la valeur Include (qui pointe généralement vers un fichier) est simplement laissée dans ConfigUpdates-SampleXml. La valeur de l'attribut Include n'est pas utilisée ici. Je placerais une valeur unique pour l'attribut Inclure pour chaque fichier que vous mettez à jour. Cela permet simplement aux utilisateurs de comprendre à quoi sert ce groupe de valeurs et vous pouvez l'utiliser plus tard pour effectuer des mises à jour par lots. L'élément XmlConfigUpdates a ces deux valeurs de métadonnées:

  • XPath - Il contient le XPath requis pour sélectionner l'élément qui va être mis à jour
  • NewValue - Cela contient la nouvelle valeur de l'élément qui va être mis à jour À l'intérieur de la cible UpdateXml, vous pouvez voir que nous utilisons la tâche XmlPoke et transmettons XPath en% (XmlConfigUpdate.XPath) et la valeur en% (XmlConfigUpdates .Nouvelle valeur). Étant donné que nous utilisons la syntaxe% (…) sur un élément, cela démarre le traitement par lots MSBuild. Le traitement par lots est le lieu où plusieurs opérations sont effectuées sur un "lot" de valeurs. Dans ce cas, il existe deux lots uniques (1 pour chaque valeur dans XmlConfigUpdates), de sorte que la tâche XmlPoke sera invoquée deux fois. Le lot peut être déroutant, alors assurez-vous de le lire si vous n'êtes pas familier.

Nous pouvons maintenant utiliser msbuild.exe pour démarrer le processus. Le fichier XML résultant est:

<Provisioning.Lib.Processing.XmlConfig instancetype="XmlConfig, Processing, Version=1.0.0.0, Culture=neutral">
  <item>
    <key>IsTestEnvironment</key>
    <value>True</value>
    <encrypted>False</encrypted>
  </item>
  <item>
    <key>HlrFtpPutDir</key>
    <value>H:\ReleasePath1</value>
    <encrypted>False</encrypted>
  </item>
  <item>
    <key>HlrFtpPutCopyDir</key>
    <value>H:\ReleasePath2</value>
    <encrypted>False</encrypted>
  </item>
</Provisioning.Lib.Processing.XmlConfig>

Alors maintenant, nous pouvons voir à quel point il était facile d'utiliser la tâche XmlPoke. Voyons maintenant comment nous pouvons étendre cet exemple pour gérer les mises à jour du même fichier pour un environnement supplémentaire.

Comment gérer les mises à jour du même fichier pour plusieurs résultats différents

Puisque nous avons créé un élément qui conservera tous les XPath nécessaires ainsi que les nouvelles valeurs, nous avons un peu plus de flexibilité dans la gestion de plusieurs environnements. Dans ce scénario, nous avons le même fichier que nous voulons écrire, mais nous devons écrire des valeurs différentes en fonction de l'environnement cible. Faire cela est assez facile. Jetez un œil au contenu de UpdateXml02.proj ci-dessous.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="UpdateXml" xmlns="http://schemas.Microsoft.com/developer/msbuild/2003">

  <PropertyGroup>
    <SourceXmlFile>$(MSBuildProjectDirectory)\sample.xml</SourceXmlFile>
    <DestXmlFiles>$(MSBuildProjectDirectory)\result.xml</DestXmlFiles>
  </PropertyGroup>

  <PropertyGroup>
    <!-- We can set a default value for TargetEnvName -->
    <TargetEnvName>Env01</TargetEnvName>
  </PropertyGroup>

  <ItemGroup Condition=" '$(TargetEnvName)' == 'Env01' ">
    <!-- Create an item which we can use to bundle all the transformations which are needed -->
    <XmlConfigUpdates Include="ConfigUpdates">
      <XPath>/Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutDir']/value</XPath>
      <NewValue>H:\ReleasePath1</NewValue>
    </XmlConfigUpdates>

    <XmlConfigUpdates Include="ConfigUpdates">
      <XPath>/Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutCopyDir']/value</XPath>
      <NewValue>H:\ReleasePath2</NewValue>
    </XmlConfigUpdates>
  </ItemGroup>

  <ItemGroup Condition=" '$(TargetEnvName)' == 'Env02' ">
    <!-- Create an item which we can use to bundle all the transformations which are needed -->
    <XmlConfigUpdates Include="ConfigUpdates">
      <XPath>/Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutDir']/value</XPath>
      <NewValue>G:\SomeOtherPlace\ReleasePath1</NewValue>
    </XmlConfigUpdates>

    <XmlConfigUpdates Include="ConfigUpdates">
      <XPath>/Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutCopyDir']/value</XPath>
      <NewValue>G:\SomeOtherPlace\ReleasePath2</NewValue>
    </XmlConfigUpdates>
  </ItemGroup>

  <Target Name="UpdateXml">
    <Message Text="Updating XML file at $(DestXmlFiles)" />
    <Copy SourceFiles="$(SourceXmlFile)"
          DestinationFiles="$(DestXmlFiles)" />
    <!-- Now let's execute all the XML transformations -->
    <XmlPoke XmlInputPath="$(DestXmlFiles)"
             Query="%(XmlConfigUpdates.XPath)"
             Value="%(XmlConfigUpdates.NewValue)"/>
  </Target>
</Project>

Les différences sont assez simples, j'ai introduit une nouvelle propriété, TargetEnvName qui nous permet de savoir quel est l'environnement cible. (note: je viens de composer ce nom de propriété, utilisez le nom que vous voulez). Vous pouvez également voir qu'il existe deux éléments ItemGroup contenant différents éléments XmlConfigUpdate. Chaque ItemGroup a une condition basée sur la valeur de TargetEnvName donc seule une des deux valeurs ItemGroup sera utilisée. Nous avons maintenant un seul fichier MSBuild qui contient les valeurs des deux environnements. Lors de la construction, passez simplement la propriété TargetEnvName, par exemple msbuild.\UpdateXml02.proj/p: TargetEnvName = Env02. Lorsque j'ai exécuté cela, le fichier résultant contient:

<Provisioning.Lib.Processing.XmlConfig instancetype="XmlConfig, Processing, Version=1.0.0.0, Culture=neutral">
  <item>
    <key>IsTestEnvironment</key>
    <value>True</value>
    <encrypted>False</encrypted>
  </item>
  <item>
    <key>HlrFtpPutDir</key>
    <value>G:\SomeOtherPlace\ReleasePath1</value>
    <encrypted>False</encrypted>
  </item>
  <item>
    <key>HlrFtpPutCopyDir</key>
    <value>G:\SomeOtherPlace\ReleasePath2</value>
    <encrypted>False</encrypted>
  </item>
</Provisioning.Lib.Processing.XmlConfig>

Vous pouvez voir que le fichier a été mis à jour avec différents chemins d'accès dans l'élément value.

4 Utilisez une bibliothèque de tâches tierce

Si vous n'utilisez pas MSBuild 4, vous devrez utiliser une bibliothèque de tâches tierce comme le pack d'extension MSBuild (lien dans les ressources).

J'espère que cela pourra aider.

Ressources

72

Nous modifions les valeurs de configuration pour nos différents environnements de construction (par exemple, dev, staging, production) en utilisant des transformations de configuration. Je suppose que les transformations de configuration ne fonctionneront probablement pas pour vous, mais si c'est une possibilité, consultez cette réponse qui montre comment appliquer les transformations de configuration .Net à n'importe quel fichier XML.

Une alternative serait d'utiliser la tâche de génération FileUpdate du projet MSBuild Community Tasks . Cette tâche vous permet d'utiliser des expressions régulières pour rechercher et remplacer du contenu dans un fichier. Voici un exemple:

<FileUpdate Files="version.txt" Regex="(\d+)\.(\d+)\.(\d+)\.(\d+)" ReplacementText="$1.$2.$3.123" />

Étant donné que vous transmettriez les propriétés système TeamCity à FileUpdate si vous décidez d'utiliser la deuxième option, jetez un œil à cette question pour voir comment les propriétés système peuvent être référencées dans un script MSBuild.

5
Jonathan McIntire