使用 msbuild 我想使用来自 teamcity 的值更新配置文件

Posted

技术标签:

【中文标题】使用 msbuild 我想使用来自 teamcity 的值更新配置文件【英文标题】:Using msbuild I want to update a config file with values from teamcity 【发布时间】:2012-01-29 08:19:25 【问题描述】:

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

【问题讨论】:

【参考方案1】:

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

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

&lt;FileUpdate Files="version.txt" Regex="(\d+)\.(\d+)\.(\d+)\.(\d+)" ReplacementText="$1.$2.$3.123" /&gt;

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

【讨论】:

【参考方案2】:

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

今天我刚刚在 *** 上看到一个问题,询问如何在 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 中的每个值对应一个),因此 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

【讨论】:

史诗般的答案!非常感谢赛义德。 如果您想要更简单的东西,请查看此答案:***.com/a/21733510/1979461 @Sayed 这是一个很好的答案!注意:如果您使用 XMLPoke 并且您的值包含任何 msbuild 特殊字符,则需要对它们进行编码。见msdn.microsoft.com/en-us/library/ms228186.aspx

以上是关于使用 msbuild 我想使用来自 teamcity 的值更新配置文件的主要内容,如果未能解决你的问题,请参考以下文章

MSBuild 内置变量列表

如何使用 MSBuild 构建具有 VS2010 配置的 VS2015 解决方案?

MSBUILD 发布 ERROR_USER_UNAUTHORIZED

如何使用 msbuild 发布网页?

我可以查看使用 MSBuild 实际执行的构建命令吗?

如何使用msbuild首先构建依赖项目