Используя 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 или около того предметов.
2 ответа
Я только что написал об этом в блоге ( http://sedodream.com/2011/12/29/UpdatingXMLFilesWithMSBuild.aspx), но здесь я также вставлю информацию для вас.
Сегодня я только что увидел вопрос, опубликованный в Stackru, спрашивающий, как обновить файл XML с помощью MSBuild во время сборки CI, выполненной из Team City.
Не существует правильного единственного ответа, есть несколько различных способов обновления файла XML во время сборки. Наиболее заметно:
- Используйте SlowCheetah, чтобы преобразовать файлы для вас
- Используйте задачу TransformXml напрямую
- Используйте встроенную (MSBuild 4.0) задачу XmlPoke
- Используйте стороннюю библиотеку задач
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']/value
- /Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutCopyDir']/value Я не собираюсь останавливаться на том, что вам нужно сделать, чтобы выяснить правильный XPath, потому что это не цель этого поста. Существует множество ресурсов, связанных с XPath. В разделе ресурсов я привел ссылку на онлайн-тестер XPath, который я всегда использую.
Теперь, когда у нас есть необходимые выражения XPath, нам нужно создать наши элементы MSBuild, чтобы все обновилось. Вот общая техника:
- Поместите всю информацию для всех обновлений XML в элемент
- Используйте 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 (ссылка в ресурсах).
Надеюсь, это поможет.
Ресурсы
- Пакет MSBuild: http://sedotech.com/Resources
- SlowCheetah - расширение XML-преобразований: http://visualstudiogallery.msdn.microsoft.com/69023d00-a4f9-4a34-a6cd-7e854ba318b5
- MSBuild Extension Pack (есть задача обновить XML-файлы): http://msbuildextensionpack.codeplex.com/
- Онлайн тестер XPath: http://www.whitebeam.org/library/guide/TechNotes/xpathtestbed.rhtm
Мы изменяем значения конфигурации для наших различных сред сборки (например, dev, staging, production), используя преобразования конфигурации. Я предполагаю, что преобразования config, вероятно, не будут работать для вас, но если это возможно, ознакомьтесь с этим ответом, в котором показано, как применить преобразования.Net config к любому XML-файлу.
Альтернативой может стать использование задачи сборки FileUpdate из проекта MSBuild Community Tasks. Эта задача позволяет использовать регулярные выражения для поиска и замены содержимого в файле. Вот пример:
<FileUpdate Files="version.txt" Regex="(\d+)\.(\d+)\.(\d+)\.(\d+)" ReplacementText="$1.$2.$3.123" />
Поскольку вы будете передавать системные свойства TeamCity в FileUpdate, если решите перейти ко второму варианту, взгляните на этот вопрос, чтобы увидеть, как на системные свойства можно ссылаться в сценарии MSBuild.