如何在MSBuild中始终执行目标任务

5
我有一个MSBuild文件,在应用编译之前操作AssemblyInfo文件。在构建结束时,它会还原AssemblyInfo文件。它通过备份文件、操作文件,然后在构建完成后恢复文件来实现这一点。
这个方法运作得相当不错,但是当构建过程中发生错误时,它就无法还原原始文件了。我能否告诉MSBuild在构建结束时执行一个目标,无论是否成功?

MSBuild中没有内置此功能。您能否更好地解释您正在做什么,以便其他人可以提供一些替代方案? - Sayed Ibrahim Hashimi
当然... 我想要做的基本上是这样的: 1)将assemblyinfo.cs文件复制到备份文件(assemblyinfo.cs.original) 2)编辑assemblyinfo.cs文件以包含关于版本信息的信息。版本信息来自自定义的MSBuild任务。 3)构建应用程序和/或程序集 4)如果在构建过程中出现任何错误,则替换assemblyinfo.cs文件为原始文件(assemblyinfo.cs.original)我希望在构建过程中出现任何错误时执行第4步。 - Jason Thompson
3个回答

5
根据您对原问题的最新评论,我建议采用另一种方法,并忘记您当前使用的方法。您应该知道,您的版本信息不必在AssemblyInfo.cs文件中。它可以在任何代码文件中,只要每个文件中只定义一个AssemblyVersion和一个AssemblyFileVersion属性即可。基于此,我将采取以下步骤:
1. 从AssemblyInfo.cs中删除AssemblyVersion和AssemblyFileVersion。 2. 创建一个新文件,随意命名,在我的情况下,我将其放在Properties\ VersionInfo.cs中。不要将此文件添加到项目中。 3. 编辑项目文件以将该文件包含在要编译的文件列表中,只有当您需要时才会包含它。
让我们详细介绍一下第3步。当您构建.NET项目时,项目本身是一个MSBuild文件。在该文件中,您将找到一个已声明的Compile项。这是将发送到编译器进行编译的文件列表。您可以动态地从该列表中包括/排除文件。在您的情况下,如果您正在构建构建服务器(或您定义的其他条件)上的构建,则只需包括VersionInfo.cs文件。在本示例中,我定义了该条件为如果项目正在发布模式下构建,则VersionInfo.cs将发送到编译器进行编译,其他构建则不会。以下是VersionInfo.cs的内容:
[assembly: System.Reflection.AssemblyVersion("1.2.3.4")]
[assembly: System.Reflection.AssemblyFileVersion("1.2.3.4")]

为了将其与构建过程关联起来,您需要编辑项目文件。在该文件中,您会找到一个元素(根据项目类型可能会有多个)。您应该在其中添加类似于以下目标的目标。
<Target Name="BeforeCompile">
  <ItemGroup Condition=" '$(Configuration)'=='Release' ">
    <Compile Include="Properties\VersionInfo.cs" />
  </ItemGroup>
</Target>

我在这里所做的是定义了一个目标,BeforeCompile,这是一个可以覆盖的众所周知的目标。关于其他类似的目标,请参阅MSDN文章。基本上,这是一个总是在编译器被调用之前调用的目标。在这个目标中,如果Configuration属性设置为发布,则我仅将VersionInfo.cs添加到Compile项目中。您可以将该属性定义为任何您想要的内容。例如,如果您的构建服务器是TFS,则可能会是以下内容:

<Target Name="BeforeCompile">
  <ItemGroup Condition=" '$(TeamFoundationServerUrl)'!='' ">
    <Compile Include="Properties\VersionInfo.cs" />
  </ItemGroup>
</Target>

因为我们知道TeamFoundationServerUrl仅在通过TFS进行构建时才定义。

如果您是使用命令行构建,则可以像这样:

<Target Name="BeforeCompile">
  <ItemGroup Condition=" '$(IncludeVersionInfo)'=='true' ">
    <Compile Include="Properties\VersionInfo.cs" />
  </ItemGroup>
</Target>

当你构建项目时,只需执行 msbuild.exe YourProject.proj /p:IncludeVersion=true。注意:在构建解决方案时,此方法无效。


有趣,但我不确定这如何解决问题。让我详细说明一下我的做法。当我想要发送一个发布版本进行测试时,我会在Subversion中创建一个指定版本号的分支。然后,我的自定义MSBuild任务会读取工作副本所在的分支(以及其他信息,如修订号等)。然后,它会修改assemblyinfo.cs以嵌入此类信息。如果失败,它会恢复文件的原始版本。这样做的原因是为了让我的正则表达式“知道”在哪里修改信息。 - Jason Thompson
更重要的是,这样我就不会创建无休止的未提交更改循环。如果我提交版本1.0.0.0,我不会因为简单地构建项目而有一个更改的文件。相反,只有在需要时(在构建期间)才会更改该文件。我不希望我的开发人员必须手动记住更改此信息。这个想法是,我可以查看任何文件,并确切地知道它来自哪里以及如何构建它。因此,如果有人在生产服务器上发布垃圾代码,我知道该责备谁以及我需要修改哪个代码版本。 - Jason Thompson
+1 我喜欢这个解决方案。可惜CruiseControl不能像这样自动定义属性。 - Maslow

2
改变问题的方式如下:
  • 将一个"模板"AssemblyInfo.cs.template添加到版本控制中,它代表了你的"理想"AssemblyInfo.cs,并在其中添加正则表达式挂钩
  • 在构建之前,将模板复制到真实文件并应用你的正则表达式
  • 为AssemblyInfo.cs添加某种类型的svn忽略(我不是svn专家,但我相信有一种方法可以告诉它忽略某些文件)
  • 如果开发人员需要添加一些通常出现在AssemblyInfo.cs中的自定义内容(例如InternalsVisibleTo),则让他们将其添加到一个不同的.cs文件中,该文件已经检入。

进一步完善地,将Sayed的解决方案与我的结合起来,从实际的AssemblyInfo.cs中删除版本信息内容,并在BeforeBuild中添加一个被检入的VersionInfo.cs.template,用于创建VersionInfo.cs。


有趣的想法...我需要考虑一下,但我认为这可能更接近能够解决问题的解决方案。如果成功了,那么它会在“模板文件”中编译。如果失败了,模板文件仍然存在,但我想要保护的文件不会被更改。 - Jason Thompson
最终你想到的东西我很感兴趣,祝你好运! - Peter McEvoy

0

我从未使用过它,但根据文档,OnError Element 似乎对你试图实现的内容有用。

如果一个任务失败且 ContinueOnError 属性为 false,则会导致一个或多个目标执行。


我已经在我的自定义目标/任务中使用了OnError元素。这很棒。问题是当错误发生在我控制之外的目标时。因此,我希望有一个超级捕获机制,可以作为任何任务的OnError。 - Jason Thompson
@Jason,如果是那样的话,很抱歉我无法提供进一步的帮助。 - João Angelo

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