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

У меня есть 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 штук или около того.

Ответы [ 2 ]

71 голосов
/ 29 декабря 2011

Я только что написал в блоге об этом (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. Как только вы сделаете это для своих проектов, вы увидите новую команду меню для преобразования файла в Bui.ld (для веб-проектов на упаковке / публикации).Если вы выполняете сборку из командной строки или с сервера 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>

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

  • / Provisioning.Lib.Processing.XmlConfig / item [key = 'HlrFtpPutDir'] / value
  • /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, это имя произвольно, вы можете использовать любое имя, какое хотите, вы можете видеть, что значение «Включить» (которое обычно указывает на файл) просто оставлено в ConfigUpdates-SampleXml.Значение атрибута «Включить» здесь не используется.Я бы поместил уникальное значение для атрибута «Включить» для каждого файла, который вы обновляете.Это просто облегчает людям понимание того, для чего предназначена эта группа значений, и вы можете использовать ее позже для пакетных обновлений.Элемент XmlConfigUpdates имеет следующие два значения метаданных:

  • XPath - содержитXPath требуется для выбора элемента, который будет обновлен
  • NewValue - Это содержит новое значение для элемента, который будет обновлен Внутри цели UpdateXml вы можете видеть, что мы используем задачу XmlPoke и передаем XPath как% (XmlConfigUpdate.XPath), а значение как% (XmlConfigUpdates.NewValue). Поскольку мы используем синтаксис% (…) для элемента, это запускает пакетирование 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>

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

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

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

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

Ресурсы

4 голосов
/ 29 декабря 2011

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

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

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

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

...