Используя msbuild, я хочу обновить файл конфигурации со значениями из teamcity

У меня есть XML, который выглядит примерно так:

<?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>

В TeamCity у меня много системных свойств:

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

Какую магию MsBuild я могу использовать, чтобы вставить эти значения в мой XML-файл? Всего около 20 предметов.


person Loofer    schedule 28.12.2011    source источник


Ответы (2)


Я только что написал об этом в блоге (http://sedodream.com/2011/12/29/UpdatingXMLFilesWithMSBuild.aspx), но я также вставлю сюда информацию для вас.

Сегодня я только что увидел вопрос, размещенный на StackOverflow, с вопросом, как обновить файл XML с помощью MSBuild во время сборки CI, выполняемой из Team City.

Не существует правильного единственного ответа, существует несколько различных способов обновления XML-файла во время сборки. В частности:

  1. Используйте SlowCheetah, чтобы преобразовать файлы для вас
  2. Используйте задачу TransformXml напрямую
  3. Используйте встроенную (MSBuild 4.0) задачу XmlPoke.
  4. Используйте стороннюю библиотеку задач

1 Используйте SlowCheetah, чтобы преобразовать файлы для вас

Прежде чем вы начнете слишком углубляться в этот пост, позвольте мне сначала рассмотреть вариант № 3, потому что я думаю, что это самый простой подход и его легче всего поддерживать. Вы можете загрузить мою надстройку SlowCheetah XML Transforms для Visual Studio. Как только вы сделаете это для своих проектов, вы увидите новую команду меню для преобразования файла при сборке (для веб-проектов при пакетировании/публикации). Если вы строите из командной строки или с сервера CI, преобразования также должны выполняться.

2 Используйте задачу TransformXml напрямую

Если вам нужен метод, в котором у вас есть «основной» XML-файл, и вы хотите иметь возможность содержать преобразования этого файла в отдельном XML-файле, вы можете напрямую использовать задачу TransformXml. Для получения дополнительной информации см. мой предыдущий пост в блоге по адресу http://sedodream.com/2010/11/18/XDTWebconfigTransformsInNonwebProjects.aspx

3 Используйте встроенную задачу XmlPoke

Иногда нет смысла создавать файл XML с преобразованиями для каждого файла XML. Например, если у вас есть файл XML и вы хотите изменить одно значение, но создать 10 разных файлов, подход преобразования XML плохо масштабируется. В этом случае может быть проще использовать задачу XmlPoke. Обратите внимание, что для этого требуется MSBuild 4.0.

Ниже приведено содержимое sample.xml (получено из вопроса 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>

Итак, в этом случае мы хотим обновить значения элемента value. Итак, первое, что нам нужно сделать, это придумать правильный XPath для всех элементов, которые мы хотим обновить. В этом случае мы можем использовать следующие выражения XPath для каждого элемента значения.

  • /Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutDir']/значение
  • /Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutCopyDir']/value Я не буду подробно рассказывать, что вам нужно сделать, чтобы определить правильный XPath, потому что это не является целью этого поста. В Интернете есть множество ресурсов, связанных с XPath. В разделе ресурсов я дал ссылку на онлайн-тестер XPath, которым всегда пользуюсь.

Теперь, когда у нас есть необходимые выражения XPath, нам нужно создать наши элементы MSBuild, чтобы все было обновлено. Вот общая техника:

  1. Поместите всю информацию обо всех обновлениях XML в элемент
  2. Используйте XmlPoke вместе с пакетной обработкой MSBuild для выполнения всех обновлений.

Для # 2, если вы не очень хорошо знакомы с пакетной обработкой MSBuild, я бы порекомендовал купить мою книгу или вы можете взглянуть на имеющиеся у меня в Интернете ресурсы, касающиеся пакетной обработки (ссылка находится ниже в разделе ресурсов). Ниже вы найдете созданный мной простой файл MSBuild, 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>

Особое внимание следует уделить элементу XmlConfigUpdates и содержимому самой задачи UpdateXml. Что касается XmlConfigUpdates, это имя является произвольным, вы можете использовать любое имя, которое хотите, вы можете видеть, что значение Include (которое обычно указывает на файл) просто оставлено в ConfigUpdates-SampleXml. Значение атрибута Include здесь не используется. Я бы поместил уникальное значение атрибута Include для каждого файла, который вы обновляете. Это просто облегчает людям понимание того, для чего предназначена эта группа значений, и вы можете использовать ее позже для пакетных обновлений. Элемент XmlConfigUpdates имеет следующие два значения метаданных:

  • XPath — содержит XPath, необходимый для выбора элемента, который будет обновлен.
  • NewValue — содержит новое значение для элемента, который будет обновлен. Внутри цели UpdateXml вы можете видеть, что мы используем задачу XmlPoke и передаем XPath как %(XmlConfigUpdate.XPath), а значение как %(XmlConfigUpdates .Новое Значение). Поскольку мы используем синтаксис %(…) для элемента, это запускает пакетную обработку MSBuild. Пакетная обработка — это когда над «партией» значений выполняется более одной операции. В этом случае есть два уникальных пакета (по 1 для каждого значения в XmlConfigUpdates), поэтому задача XmlPoke будет вызываться два раза. Пакетная обработка может сбивать с толку, поэтому обязательно ознакомьтесь с ней, если вы не знакомы.

Теперь мы можем использовать msbuild.exe для запуска процесса. Результирующий файл XML:

<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>

Итак, теперь мы видим, как легко было использовать задачу XmlPoke. Давайте теперь посмотрим, как мы можем расширить этот пример, чтобы управлять обновлениями того же файла для дополнительной среды.

Как управлять обновлениями одного и того же файла для нескольких разных результатов

Поскольку мы создали элемент, который сохранит все необходимые XPath, а также новые значения, у нас появилось немного больше гибкости в управлении несколькими средами. В этом сценарии у нас есть тот же файл, который мы хотим записать, но нам нужно записать разные значения в зависимости от целевой среды. Сделать это довольно легко. Взгляните на содержимое UpdateXml02.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>

  <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>

Различия довольно просты, я представил новое свойство TargetEnvName, которое позволяет нам узнать, что такое целевая среда. (примечание: я только что придумал это имя свойства, используйте любое имя, которое вам нравится). Также вы можете видеть, что есть два элемента ItemGroup, содержащие разные элементы XmlConfigUpdate. Каждая ItemGroup имеет условие, основанное на значении TargetEnvName, поэтому будет использоваться только одно из двух значений ItemGroup. Теперь у нас есть один файл MSBuild со значениями для обеих сред. При сборке просто передайте свойство TargetEnvName, например msbuild .\UpdateXml02.proj /p:TargetEnvName=Env02. Когда я выполнил это, результирующий файл содержит:

<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>

Вы можете видеть, что файл был обновлен с разными путями в элементе value.

4 Используйте стороннюю библиотеку задач

Если вы не используете MSBuild 4, вам потребуется использовать стороннюю библиотеку задач, например пакет расширений MSBuild (ссылка в ресурсах).

Надеюсь, это поможет.

Ресурсы

person Sayed Ibrahim Hashimi    schedule 29.12.2011
comment
Эпический ответ! Большое спасибо Саид. - person Loofer; 29.12.2011
comment
Посмотрите этот ответ, если вы хотите что-то более простое: stackoverflow.com/a/21733510/1979461 - person GarethD; 17.08.2015
comment
@Sayed Это отличный ответ! Примечание. Если вы используете XMLPoke и ваши значения содержат специальные символы msbuild, их необходимо закодировать. См. msdn.microsoft.com/en-us/library/ms228186.aspx - person Jason Slocomb; 27.04.2018

Мы изменяем значения конфигурации для различных сред сборки (например, разработки, подготовки, производства) с помощью преобразований конфигурации. Я предполагаю, что преобразования конфигурации, вероятно, не будут работать для вас, но если это возможно, проверьте этот ответ, в котором показано, как применять преобразования конфигурации .Net к любому файлу XML.

В качестве альтернативы можно использовать задачу сборки FileUpdate из проекта Задачи сообщества MSBuild. Эта задача позволяет использовать регулярные выражения для поиска и замены содержимого в файле. Вот пример:

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

Поскольку вы будете передавать системные свойства TeamCity в FileUpdate, если решите использовать второй вариант, взгляните на этот вопрос, чтобы увидеть, как можно ссылаться на системные свойства в сценарии MSBuild.

person Jonathan McIntire    schedule 28.12.2011