41

我有一些看起来像这样的 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个左右的项目。

4

2 回答 2

72

我刚刚在博客上写了这个(http://sedodream.com/2011/12/29/UpdatingXMLFilesWithMSBuild.aspx),但我也会在这里为您粘贴信息。

今天我刚刚在 StackOverflow 上看到一个问题,询问如何在 Team City 执行的 CI 构建期间使用 MSBuild 更新 XML 文件。

没有正确的单一答案,有几种不同的方法可以在构建期间更新 XML 文件。最为显着地:

  1. 使用 SlowCheetah 为您转换文件
  2. 直接使用 TransformXml 任务
  3. 使用内置 (MSBuild 4.0) XmlPoke 任务
  4. 使用第三方任务库

1 使用 SlowCheetah 为您转换文件

在开始深入阅读这篇文章之前,让我先回顾一下选项#3,因为我认为这是最简单的方法,也最容易维护。您可以下载我的 SlowCheetah XML Transforms Visual Studio 插件。为您的项目执行此操作后,您将看到一个新的菜单命令,用于在构建时转换文件(对于打包/发布的 Web 项目)。如果您从命令行或 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 question)。

<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 元素来更新所有内容。这是整体技术:

  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 批处理。批处理是对“一批”值执行多个操作的地方。在这种情况下,有两个唯一的批次(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 扩展包(资源中的链接)。

希望有帮助。

资源

于 2011-12-29T03:49:46.333 回答
5

我们使用配置转换为不同的构建环境(例如开发、登台、生产)更改配置值。我认为配置转换可能对您不起作用,但如果有可能,请查看此答案,该答案显示如何将 .Net 配置转换应用于任何 XML 文件。

另一种方法是使用MSBuild 社区任务项目中的 FileUpdate 构建任务。此任务允许您使用正则表达式来查找和替换文件中的内容。这是一个例子:

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

因为如果您决定使用第二个选项,您会将 TeamCity 系统属性传递到 FileUpdate,请查看此问题以了解如何在 MSBuild 脚本中引用系统属性。

于 2011-12-28T20:41:38.960 回答