使用MSBuild进行批量重命名

3
我加入了一个团队,他们没有任何持续集成流程(甚至没有过夜构建),而且有一些可疑的开发实践。我们希望改变这种情况,因此我现在被指派创建一个过夜构建。我已经按照this series系列文章的步骤:创建一个包含所有项目(一些Web应用程序、Web服务、一些Windows服务和几个编译为命令行可执行文件的工具)的主解决方案;创建了一个MSBuild脚本来自动构建、打包和部署我们的产品;并创建了一个.cmd文件,以便一键完成所有操作。现在我正在尝试完成其中的一个任务:
团队目前的做法是将web.config和app.config文件放在源代码控制之外,并将名为web.template.config和app.template.config的文件放入源代码控制。这样开发人员就可以将.template.config文件复制到.config文件中,以获取所有标准配置值,然后能够编辑.config文件中的值以满足本地开发/测试需求。出于明显的原因,我希望自动化重命名.template.config文件为.config文件的过程。最好的方法是什么?
是否可能在构建脚本本身中完成此操作,而无需在脚本中规定每个需要重命名的单个文件(这将需要在向解决方案添加新项目时维护脚本)?还是我需要编写一些批处理文件,然后从脚本中运行?
此外,是否有更好的开发解决方案,可以建议使整个过程不必要?

为什么配置文件不会被检入?你能提供一些开发人员特定的配置设置的例子吗(不能共享)? - Arnold Zokas
据我所知,没有使用不能共享的设置。但是开发人员可能想要更改连接字符串,以便可以在本地数据库上进行所有测试。我可以告诉你,当前的流程很糟糕,因为项目文件仍然期望有一个web.config文件,这增加了我必须手动完成的工作量,以使一切都能够构建。 - Marc Chu
整个团队(只有3或4名开发人员,直到我加入)似乎没有对现状有什么问题,因为开发人员似乎基本上拥有特定项目的所有权,可能甚至不会构建其他项目。因此,他们只对单个项目文件进行了工作,而不是像我创建的主解决方案那样。 - Marc Chu
2个回答

6

经过大量关于项目组、目标和复制任务的阅读,我已经弄清楚了如何做我需要的事情。

<ItemGroup>
    <FilesToCopy Include="..\**\app.template.config">
        <NewFilename>app.config</NewFilename>
    </FilesToCopy>
    <FilesToCopy Include="..\**\web.template.config">
        <NewFilename>web.config</NewFilename>
    </FilesToCopy>
    <FilesToCopy Include"..\Hibernate\hibernate.cfg.template.xml">
        <NewFilename>hibernate.cfg.xml</NewFilename>
    </FilesToCopy>
</ItemGroup>

<Target Name="CopyFiles"
        Inputs="@(FilesToCopy)"
        Outputs="@(FilesToCopy->'%(RootDir)%(Directory)%(NewFilename)')">
    <Message Text="Copying *.template.config files to *.config"/>
<Copy SourceFiles="@(FilesToCopy)"
      DestinationFiles="@(FilesToCopy->'%(RootDir)%(Directory)%(NewFilename)')"/>

我创建了一个包含要复制的文件的项组。 **操作符告诉它递归遍历整个目录树,查找指定名称的每个文件。 然后,我向这些文件中添加了一个名为“NewFilename”的元数据。 这就是我将重命名每个文件的名称。
此代码片段添加了目录结构中名为app.template.config的每个文件,并指定我将命名新文件为app.config:
<FilesToCopy Include="..\**\app.template.config">
    <NewFilename>app.config</NewFilename>
</FilesToCopy>

我随后创建了一个目标来复制所有文件。这个目标最初非常简单,只调用了Copy任务以始终复制并覆盖文件。我将FilesToCopy项目组作为复制操作的源传递。我使用transforms指定输出文件名,以及我的NewFilename元数据和众所周知的项目元数据。

以下片段将例如将文件c:\Project\Subdir\app.template.config转换为c:\Project\Subdir\app.config,并将前者复制到后者:

<Target Name="CopyFiles">
    <Copy SourceFiles="@(FilesToCopy)"
          DestinationFiles="@(FilesToCopy->'%(RootDir)%(Directory)%(NewFileName)')"/>
</Target>

但是,我注意到开发人员可能不希望每次运行脚本时都被覆盖他的定制化web.config文件。但是,如果存储库的web.template.config已被修改并且具有代码需要的新值,则开发人员可能应该覆盖他的本地文件。我尝试了多种不同的方法来实现这一点,如将Copy属性“SkipUnchangedFiles”设置为true,使用“Exist()”函数等,但都无济于事。
解决方案是增量构建。这确保只有在app.template.config更新时才会覆盖文件。我将文件名称作为目标输入传递,并将新文件名称指定为目标输出。
<Target Name="CopyFiles"
        Input="@(FilesToCopy)"
        Output="@(FilesToCopy->'%(RootDir)%(Directory)%(NewFileName)')">
      ...
</Target>

这里的目标是检查当前输出是否与输入保持最新状态。如果不是,即.template.config文件比其对应的.config文件有更改,则会将web.template.config复制到现有的web.config上。否则,它将保留开发人员的web.config文件不变。如果没有需要复制的指定文件,则跳过该目标。在干净的代码库克隆后,每个文件都将被复制。
以上结果非常令人满意,因为我刚开始使用MSBuild,并对其强大的功能感到惊讶。唯一让我不喜欢的是我必须在两个地方重复完全相同的转换。我讨厌复制任何类型的代码,但我无法想出如何避免这种情况。如果有人有提示,将不胜感激。此外,虽然我认为需要这样做的开发实践非常糟糕,但这确实有助于减轻这种糟糕因素。

目标名称为“CopyFiles”的目标会自动调用吗?还是我需要做些什么来包含该目标? - Vajda Endre
假设您正在运行不同的目标,例如“Build”,并且您希望此目标作为其一部分运行,则需要将CopyFiles作为目标的依赖项,如下所示:<Target Name="Build" DependsOnTargets="CopyFiles" />。 - Marc Chu

1

简短回答:
是的,您可以(也应该)自动化这个过程。您应该能够使用MSBuild Move task来重命名文件。

详细回答:
很高兴看到有人想从手动流程转变为自动化流程。通常情况下,很少有真正的理由不去自动化。您的构建脚本将作为构建和部署实际工作方式的活文档。在我看来,一个好的构建脚本比静态文档更有价值(虽然我并不是说您不应该有文档 - 它们毕竟不是互斥的)。让我们逐个回答您的问题。

最佳方法是什么?

我不完全了解您正在存储哪些文件中的配置,但我怀疑开发团队可以共享很多配置。

我建议提出以下问题:

  • 哪些设置是特定于开发人员的?
  • 是否有任何方法可以标准化本地开发人员机器,以便可以共享设置?

在构建脚本中是否有可能直接完成此操作,而无需在脚本中指定每个需要重命名的文件?

是的,请查看MSBuild Move task。您应该能够使用它来重命名文件。

...这将要求每次向解决方案添加新项目时都要维护脚本?

这是不可避免的 - 您的构建脚本必须随着解决方案一起发展。接受这一事实,并在您的估算中包括时间来更改您的构建脚本。

此外,是否有更好的开发解决方案可以建议,使整个过程变得不必要?

我不了解所有要求,因此很难推荐非常具体的东西。我可以建议这样做:

  • 为您的解决方案创建一个共享的构建脚本
  • 尽可能自动化手动任务(在合理范围内)
  • 如果您正在努力自动化某些内容,这可能是需要重新思考/重新设计的领域的指标
  • 确保您的团队成员了解构建的工作原理,并能够自行进行更改 - 不要“拥有”构建并成为瓶颈

请记住,从没有构建脚本到完全自动化不是一夜之间的过程。耐心点,首先专注于自动化造成最大痛苦的领域。

如果我对您的任何问题有误解,请告诉我,我会更新答案。


谢谢,Arnold。为了明确,配置设置确实被存储在源代码控制中,只是使用不同的文件名进行存储,因此需要上述过程。我想这样可以让开发人员从仓库中拉取而不必担心他的特定配置会被覆盖,迫使他重新编辑本地值。似乎应该有更好的方法来解决这个问题,例如,可能可以导入本地文件以覆盖web.config中的设置,就像MSBuild可以传递/p:TargetEnvPropsFile=xxx.proj来为特定环境进行配置一样。 - Marc Chu
此外,我希望能够像将解决方案文件传递给MSBuild一样(这不需要编辑脚本即可构建另一个项目,只需将其添加到解决方案中),我能够编写代码在脚本中查找任何具有.template.config扩展名的文件并将扩展名重命名为.config。如果我不能这样做,并且每次添加新项目时都需要编辑脚本以便我们可以列出另一个要重命名的文件,那么这几乎就打败了传递解决方案的目的,并可能会争论要单独列出脚本中的每个项目。 - Marc Chu
@MarcChu 是的,你可以像这样在构建脚本中添加逻辑。我以前也做过类似的事情(出于其他原因)。 - Arnold Zokas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接