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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8658972/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-06 15:05:18  来源:igfitidea点击:

Using msbuild I want to update a config file with values from teamcity

xmlmsbuildteamcityslowcheetah

提问by Loofer

I have some XML that looks something like this:

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

In TeamCity I have many system properties:

在 TeamCity 中,我有许多系统属性:

 system.HlrFtpPutDir     H:\ReleasePath1
 system.HlrFtpPutCopyDir H:\ReleasePath2

What sort of MsBuild magic can I use to push these values into my XML file? In all there are 20 or so items.

我可以使用什么样的 MsBuild 魔法将这些值推送到我的 XML 文件中?总共有20个左右的项目。

回答by Sayed Ibrahim Hashimi

I just blogged about this (http://sedodream.com/2011/12/29/UpdatingXMLFilesWithMSBuild.aspx) but I'll paste the info here for you as well.

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

Today I just saw a question posted on StackOverflow asking how to update an XML file using MSBuild during a CI build executed from Team City.

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

There is not correct single answer, there are several different ways that you can update an XML file during a build. Most notably:

没有正确的单一答案,您可以通过多种不同的方式在构建过程中更新 XML 文件。最为显着地:

  1. Use SlowCheetah to transform the files for you
  2. Use the TransformXml task directly
  3. Use the built in (MSBuild 4.0) XmlPoke task
  4. Use a third party task library
  1. 使用SlowCheetah为你转换文件
  2. 直接使用 TransformXml 任务
  3. 使用内置 (MSBuild 4.0) XmlPoke 任务
  4. 使用第三方任务库

1 Use SlowCheetah to transform the files for you

1 使用SlowCheetah 为您转换文件

Before you start reading too far into this post let me go over option #3 first because I think it's the easiest approach and the most easily maintained. You can download my SlowCheetah XML Transforms Visual Studio add in. Once you do this for your projects you will see a new menu command to transform a file on build (for web projects on package/publish). If you build from the command line or a CI server the transforms should run as well.

在你开始阅读这篇文章之前,让我先回顾一下选项 #3,因为我认为这是最简单的方法,也是最容易维护的。您可以下载我的 SlowCheetah XML Transforms Visual Studio 插件。一旦您为您的项目执行此操作,您将看到一个新的菜单命令,用于在构建时转换文件(用于打包/发布的 Web 项目)。如果您从命令行或 CI 服务器构建,转换也应该运行。

2 Use the TransformXml task directly

2 直接使用TransformXml任务

If you want a technique where you have a “main” XML file and you want to be able to contain transformations to that file inside of a separate XML file then you can use the TransformXml task directly. For more info see my previous blog post at http://sedodream.com/2010/11/18/XDTWebconfigTransformsInNonwebProjects.aspx

如果您想要一种技术,其中您有一个“主”XML 文件,并且您希望能够在单独的 XML 文件中包含对该文件的转换,那么您可以直接使用 TransformXml 任务。有关更多信息,请参阅我之前在http://sedodream.com/2010/11/18/XDTWebconfigTransformsInNonwebProjects.aspx上的博客文章

3 Use the built in XmlPoke task

3 使用内置的 XmlPoke 任务

Sometimes it doesn't make sense to create an XML file with transformations for each XML file. For example if you have an XML file and you want to modify a single value but to create 10 different files the XML transformation approach doesn't scale well. In this case it might be easier to use the XmlPoke task. Note this does require MSBuild 4.0.

有时,为每个 XML 文件创建一个带有转换的 XML 文件是没有意义的。例如,如果您有一个 XML 文件并且您想要修改单个值但要创建 10 个不同的文件,则 XML 转换方法不能很好地扩展。在这种情况下,使用 XmlPoke 任务可能更容易。请注意,这确实需要 MSBuild 4.0。

Below are the contents of sample.xml (came from the SO question).

以下是 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>

So in this case we want to update the values of the value element. So the first thing that we need to do is to come up with the correct XPath for all the elements which we want to update. In this case we can use the following XPath expressions for each value element.

所以在这种情况下,我们要更新 value 元素的值。所以我们需要做的第一件事就是为我们想要更新的所有元素提供正确的 XPath。在这种情况下,我们可以为每个值元素使用以下 XPath 表达式。

  • /Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutDir']/value
  • /Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutCopyDir']/value I'm not going to go over what you need to do to figure out the correct XPath because that's not the purpose of this post. There are a bunch of XPath related resources on the interwebs. In the resources section I have linked to the online XPath tester which I always use.
  • /Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutDir']/value
  • /Provisioning.Lib.Processing.XmlConfig/item[key='HlrFtpPutCopyDir']/value 我不打算讨论您需要做什么来找出正确的 XPath,因为这不是这篇文章的目的。互联网上有一堆 XPath 相关资源。在资源部分,我已链接到我一直使用的在线 XPath 测试器。

Now that we've got the required XPath expressions we need to construct our MSBuild elements to get everything updated. Here is the overall technique:

现在我们已经获得了所需的 XPath 表达式,我们需要构建我们的 MSBuild 元素来更新所有内容。这是整体技术:

  1. Place all info for all XML updates into an item
  2. Use XmlPoke along with MSBuild batching to perform all the updates
  1. 将所有 XML 更新的所有信息放入一个项目中
  2. 使用 XmlPoke 和 MSBuild 批处理来执行所有更新

For #2 if you are not that familiar with MSBuild batching then I would recommend buying my book or you can take a look at the resources I have online relating to batching (the link is below in resources section). Below you will find a simple MSBuild file that I created, UpdateXm01.proj.

对于#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>

The parts to pay close attention to is the XmlConfigUpdates item and the contents of the UpdateXml task itself. Regarding the XmlConfigUpdates, that name is arbitrary you can use whatever name you want, you can see that the Include value (which typically points to a file) is simply left at ConfigUpdates-SampleXml. The value for the Include attribute is not used here. I would place a unique value for the Include attribute for each file that you are updating. This just makes it easier for people to understand what that group of values is for, and you can use it later to batch updates. The XmlConfigUpdates item has these two metadata values:

需要密切关注的部分是 XmlConfigUpdates 项和 UpdateXml 任务本身的内容。关于 XmlConfigUpdates,该名称是任意的,您可以使用任何您想要的名称,您可以看到 Include 值(通常指向一个文件)只是留在 ConfigUpdates-SampleXml。此处不使用 Include 属性的值。我会为您要更新的每个文件的 Include 属性放置一个唯一值。这只是让人们更容易理解这组值的用途,您可以稍后使用它来批量更新。XmlConfigUpdates 项具有以下两个元数据值:

  • XPath -- This contains the XPath required to select the element which is going to be updated
  • NewValue -- This contains the new value for the element which is going to be updated Inside of the UpdateXml target you can see that we are using the XmlPoke task and passing the XPath as %(XmlConfigUpdate.XPath) and the value as %(XmlConfigUpdates.NewValue). Since we are using the %(…) syntax on an item this start MSBuild batching. Batching is where more than one operation is performed over a “batch” of values. In this case there are two unique batches (1 for each value in XmlConfigUpdates) so the XmlPoke task will be invoked two times. Batching can be confusing so make sure to read up on it if you are not familiar.
  • XPath -- 这包含选择要更新的元素所需的 XPath
  • NewValue -- 这包含将要更新的元素的新值 在 UpdateXml 目标内部,您可以看到我们正在使用 XmlPoke 任务并将 XPath 传递为 %(XmlConfigUpdate.XPath) 并将值传递为 %(XmlConfigUpdates .NewValue)。由于我们在项目上使用 %(...) 语法,因此将启动 MSBuild 批处理。批处理是对“一批”值执行多个操作的地方。在这种情况下,有两个唯一的批次(XmlConfigUpdates 中的每个值 1 个),因此 XmlPoke 任务将被调用两次。批处理可能会令人困惑,因此如果您不熟悉,请务必仔细阅读。

Now we can use msbuild.exe to start the process. The resulting XML file is:

现在我们可以使用 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>

So now we can see how easy it was to use the XmlPoke task. Let's now take a look at how we can extend this example to manage updates to the same file for an additional environment.

所以现在我们可以看到使用 XmlPoke 任务是多么容易。现在让我们看看如何扩展此示例以管理对其他环境的同一文件的更新。

How to manage updates to the same file for multiple different results

如何管理对同一文件的多个不同结果的更新

Since we've created an item which will keep all the needed XPath as well as the new values we have a bit more flexibility in managing multiple environments. In this scenario we have the same file that we want to write out, but we need to write out different values based on the target environment. Doing this is pretty easy. Take a look at the contents of UpdateXml02.proj below.

由于我们创建了一个项目,它将保留所有需要的 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>

The differences are pretty simple, I introduced a new property, TargetEnvName which lets us know what the target environment is. (note: I just made up that property name, use whatever name you like). Also you can see that there are two ItemGroup elements containing different XmlConfigUpdate items. Each ItemGroup has a condition based on the value of TargetEnvName so only one of the two ItemGroup values will be used. Now we have a single MSBuild file that has the values for both environments. When building just pass in the property TargetEnvName, for example msbuild .\UpdateXml02.proj /p:TargetEnvName=Env02. When I executed this the resulting file contains:

区别非常简单,我引入了一个新属性 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>

You can see that the file has been updated with different paths in the value element.

您可以看到文件已更新为 value 元素中的不同路径。

4 Use a third party task library

4 使用第三方任务库

If you are not using MSBuild 4 then you will need to use a third party task library like the MSBuild Extension Pack (link in resources).

如果您没有使用 MSBuild 4,那么您将需要使用第三方任务库,如 MSBuild 扩展包(资源中的链接)。

Hope that helps.

希望有帮助。

Resources

资源

回答by Jonathan McIntire

We change config values for our different build environments (e.g. dev, staging, production) using config transformations. I assume that config transforms probably won't work for you, but if it's a possibility, check out this answerwhich shows how to apply .Net config transforms to any XML file.

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

An alternative would be to use the FileUpdate build task from the MSBuild Community Tasksproject. This task allows you to use regular expressions to find and replace content in a file. Here's an example:

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

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

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

Because you'd be passing TeamCity system properties into FileUpdate if you decide to go with the second option, take a look at this questionto see how system properties can be referenced in an MSBuild script.

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