xml - 使用 msbuild 我想用 teamcity 的值更新配置文件

标签 xml msbuild teamcity slowcheetah

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

最佳答案

我刚刚写了关于这个的博客( http://sedodream.com/2011/12/29/UpdatingXMLFilesWithMSBuild.aspx ),但我也会在这里为你粘贴信息。
今天我刚刚在 StackOverflow 上看到一个问题,询问如何在从 Team City 执行的 CI 构建期间使用 MSBuild 更新 XML 文件。
没有正确的单一答案,您可以通过多种不同的方式在构建过程中更新 XML 文件。最为显着地:

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

  • 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 元素来更新所有内容。这是整体技术:
  • 将所有 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,该名称是任意的,您可以使用任何您想要的名称,您可以看到 Include 值(通常指向一个文件)只是留在 ConfigUpdates-SampleXml 中。此处不使用 Include 属性的值。我会为您要更新的每个文件的 Include 属性放置一个唯一值。这只是让人们更容易理解这组值的用途,您可以稍后使用它来批量更新。 XmlConfigUpdates 项具有以下两个元数据值:
  • XPath
    -- 这包含选择要更新的元素所需的 XPath
  • 新值
    -- 这包含将要更新的元素的新值
    在 UpdateXml 目标内部,您可以看到我们正在使用 XmlPoke 任务并将 XPath 作为 %(XmlConfigUpdate.XPath) 传递,并将值作为 %(XmlConfigUpdates.NewValue) 传递。由于我们在项目上使用 %(...) 语法,因此将启动 MSBuild 批处理。批处理是对“一批”值执行多个操作的地方。在这种情况下,有两个唯一的批次(XmlConfigUpdates 中的每个值 1 个),因此 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 扩展包(资源中的链接)。
    希望有帮助。
    资源
  • MSBuild 批处理:http://sedotech.com/Resources#Batching
  • SlowCheetah – XML 转换扩展:http://visualstudiogallery.msdn.microsoft.com/69023d00-a4f9-4a34-a6cd-7e854ba318b5
  • MSBuild 扩展包(有更新 XML 文件的任务):http://msbuildextensionpack.codeplex.com/
  • 在线 XPath 测试员:http://www.whitebeam.org/library/guide/TechNotes/xpathtestbed.rhtm
  • 关于xml - 使用 msbuild 我想用 teamcity 的值更新配置文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8658972/

    相关文章:

    c++ - 在 C++ 程序中使用什么库来*编写* XML 文件?

    java - 如何从 java eclipse DOM 上的 XML 文件获取 href 值

    ruby - 使用 Ruby 将 SEC Edgar XML 文件解析为 Nokogiri

    java - 通过java代码记录用户选择并更改textview字符串

    visual-studio - ##[警告]未找到 Visual Studio 版本 '14.0'。回退到版本 '15.0'

    c# - 当我从 msbuild 构建解决方案时未构建 Visual Studio 项目

    msbuild - 如何在 MSBuild 中指定额外的依赖文件?

    git - 如何找到包含给定 git 提交的 TeamCity 构建?

    .net - TeamCity - 未满足的要求 (DotNetFramework4.0_x86)

    teamcity - 如何在TeamCity中正确处理共享项目