我有一些看起来像这样的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个左右的项目。
答案 0 :(得分:70)
我刚刚在博客上发表了这篇文章(http://sedodream.com/2011/12/29/UpdatingXMLFilesWithMSBuild.aspx),但我也会在这里粘贴这些信息。
今天我刚刚在StackOverflow上发布了一个问题,询问如何在从Team City执行的CI构建期间使用MSBuild更新XML文件。
没有正确的单一答案,有几种不同的方法可以在构建期间更新XML文件。最值得注意的是:
在你开始阅读这篇文章之前,我先讨论#3选项,因为我认为这是最简单的方法,也是最容易维护的方法。您可以下载我的SlowCheetah XML Transforms Visual Studio添加。一旦您为项目执行此操作,您将看到一个新的菜单命令来转换构建文件(对于包/发布上的Web项目)。如果从命令行或CI服务器构建,则转换也应该运行。
如果您想要一种技术,其中有一个“主”XML文件,并且您希望能够在单独的XML文件中包含对该文件的转换,那么您可以直接使用TransformXml任务。有关详情,请参阅我之前在http://sedodream.com/2010/11/18/XDTWebconfigTransformsInNonwebProjects.aspx
上发表的博客文章有时,为每个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表达式。
现在我们已经获得了所需的XPath表达式,我们需要构建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项具有以下两个元数据值:
现在我们可以使用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,让我们知道目标环境是什么。 (注意:我刚刚编写了该属性名称,使用您喜欢的任何名称)。您还可以看到有两个包含不同XmlConfigUpdate项的ItemGroup元素。每个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元素中的不同路径进行更新。
如果您不使用MSBuild 4,则需要使用第三方任务库,如MSBuild Extension Pack(资源中的链接)。
希望有所帮助。
资源
答案 1 :(得分:4)
我们使用配置转换更改不同构建环境(例如dev,staging,production)的配置值。我假设配置转换可能不适合你,但如果有可能,请查看this answer,其中显示如何将.Net配置转换为任何XML文件。
另一种方法是使用MSBuild Community Tasks项目中的FileUpdate构建任务。此任务允许您使用正则表达式来查找和替换文件中的内容。这是一个例子:
<FileUpdate Files="version.txt" Regex="(\d+)\.(\d+)\.(\d+)\.(\d+)" ReplacementText="$1.$2.$3.123" />
如果您决定使用第二个选项,那么您将把TeamCity系统属性传递给FileUpdate,请查看this question以了解如何在MSBuild脚本中引用系统属性。