【问题标题】:Determine if MSBuild CoreCompile will run and call custom target确定 MSBuild CoreCompile 是否将运行并调用自定义目标
【发布时间】:2012-07-26 10:38:33
【问题描述】:

这似乎是一件显而易见的事情,但我已经竭尽全力试图在网上找到任何示例或自己做。

我有一个包含 19 个项目的 c# 解决方案和一个运行构建脚本来驱动 MSBuild 的 Jenkins 构建服务器。 MSBuild 当然会根据输入与输出来确定需要编译和不需要编译的内容。

我正在尝试创建一个自定义目标,以有条件地更新 MSBuild 将要编译的那些项目的 AssemblyInfo.cs 以增加文件版本。当然,我不想让项目单独编译。

我知道如何在每次运行的 CoreBuild 之前注入一个目标,所以如果有一些变量,我可以测试看看是否会发生可以工作的编译。我也知道如何确定编译是否运行,因此有条件地进行一些可能但不理想的后处理。

如何调整我的构建过程来实现这一点?

由于这个问题似乎没有直接的答案,有谁知道如何执行与 MSBuild 相同的逻辑来确定哪些项目需要重建?

【问题讨论】:

    标签: c# msbuild


    【解决方案1】:

    最终解决方案是Sayed Ibrahim Hashimi's blog entry 和来自 MSDN 论坛条目'Execute target when (core)compile will execute' 的信息的组合。

    我基本上采用了 Sayed 的注入方法来让我的目标在所有项目上运行“extend-corecompile.proj”,而无需编辑每个 proj 文件,但将其内容替换为指向自定义目标的“CoreCompileDependsOn”覆盖采用与“CoreCompile”目标相同的输入和输出。最终结果是一个仅在“CoreCompile”运行时运行的目标,同时在构建脚本中进行集中管理。

    感谢大家的投入,这是我在“extend-corecompile.proj”中使用的框架代码:

    <!--The following property group adds our custom post-target to the post compile call list -->
    <PropertyGroup>
        <TargetsTriggeredByCompilation>
            $(TargetsTriggeredByCompilation);
            CustomPostTarget
        </TargetsTriggeredByCompilation>
    </PropertyGroup>
    
    <!--The following property group adds our custom pre-target to CoreCompileDependsOn to ensure it is called before CoreCompile -->
    <PropertyGroup>
        <CoreCompileDependsOn>
            $(CoreCompileDependsOn);
            CustomPreTarget
        </CoreCompileDependsOn>
    </PropertyGroup>
    
    <!-- The following custom pre-target has the same inputs and outputs as CoreCompile so that it will only run when CoreCompile runs.
        Because we have injected this file and Targets are resolved in sequence we know this Target will fire before CoreCompile.-->
    <Target Name="CustomPreTarget" 
        Inputs="$(MSBuildAllProjects);
                @(Compile);                               
                @(_CoreCompileResourceInputs);
                $(ApplicationIcon);
                $(AssemblyOriginatorKeyFile);
                @(ReferencePath);
                @(CompiledLicenseFile);
                @(EmbeddedDocumentation); 
                $(Win32Resource);
                $(Win32Manifest);
                @(CustomAdditionalCompileInputs)"
        Outputs="@(DocFileItem);
                 @(IntermediateAssembly);
                 @(_DebugSymbolsIntermediatePath);                 
                 $(NonExistentFile);
                 @(CustomAdditionalCompileOutputs)">
        <!--Do pre-compilation processing here-->
    </Target>
    
    <!--This target will be called by CoreCompile-->
    <Target Name="CustomPostTarget" >
        <!--Do post-compilation processing here-->
    </Target>
    

    不确定如果 CoreCompile 失败会发生什么,它仍然调用我们的目标吗?我想我们会及时发现:)

    【讨论】:

      【解决方案2】:

      我刚刚在http://sedodream.com/2012/07/28/MSBuildHowToExecuteATargetAfterCoreCompilePart2.aspx 上发布了这个问题的答案,但我已经在下面为您粘贴了解决方案。

      几个月前,我写了一篇博客文章MSBuild how to execute a target after CoreCompile,其中描述了如果执行 CoreCompile 目标,您如何执行目标,如果跳过 CoreCompile,那么您的其他目标也将如此。我在上一篇文章中概述的方法的缺点是它需要您编辑 .csproj/.vbproj/etc 文件本身。因此,如果您有构建多个项目的场景,那么您将不得不编辑所有项目文件。在这篇文章中,我将描述如何执行相同的自定义,而无需编辑项目文件本身。

      在我们为这个特殊案例找到解决方案之前,让我描述一下 C# 和 VB 项目所具有的可扩展性挂钩。用于构建 C# 和 VB 项目的大部分逻辑都包含在 C:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets 的 MSBuild 目标文件中。如果您查看该文件,您会注意到顶部有一个如下所示的导入。

      <Import Project="$(CustomBeforeMicrosoftCommonTargets)" Condition="'$(CustomBeforeMicrosoftCommonTargets)' != '' and Exists('$(CustomBeforeMicrosoftCommonTargets)')"/>
      

      如果属性不为空且文件存在,则此语句将导入文件(位于 CustomBeforeMicrosoftCommonTargets 的值处)。 CustomBeforeMicrosoftCommonTargets 的默认值为 C:\Program Files (x86)\MSBuild\v4.0\Custom.Before.Microsoft.Common.targets。因此,如果您将 MSBuild 文件放在该位置,它将修改在该计算机上构建的每个 C#/VB 项目的构建过程。或者,如果您不想要(或由于 ACL 而不能),则可以将文件放到其他位置,然后通过覆盖 CustomBeforeMicrosoftCommonTargets 属性来指定其位置。这是我将在这里采取的方法。我创建了一个示例解决方案,其中包含两个项目 ProjA 和 ProjB。我还有一个构建脚本 build.proj,可以为此自动构建。下面是 build.proj 的全部内容。

      build.proj

      <?xml version="1.0" encoding="utf-8"?>
      <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
        <PropertyGroup>
          <FileToInject Condition=" '$(FileToInject)'=='' ">$(MSBuildThisFileDirectory)extend-corecompile.proj</FileToInject>
        </PropertyGroup>
      
        <ItemGroup>
          <ProjectsToBuild Include="ProjA\ProjA.csproj"/>
          <ProjectsToBuild Include="ProjB\ProjB.csproj"/>
        </ItemGroup>
      
        <Target Name="Build">
          <MSBuild Projects="@(ProjectsToBuild)"
                   Properties="CustomBeforeMicrosoftCommonTargets=$(FileToInject)" />   
        </Target>
      
        <Target Name="Clean">
          <MSBuild Projects="@(ProjectsToBuild)" Targets="Clean"/>
        </Target>
      
        <Target Name="Rebuild" DependsOnTargets="Clean;Build"/>
      
      </Project>
      

      在上面的构建目标中,我使用 MSBuild 任务来构建 ProjA 和 ProjB。如您所见,我正在传递指向extend-corecompile.proj 的属性CustomBeforeMicrosoftCommonTargets=$(FileToInject)。通过在构建 ProjA 和 ProjB 时传递此属性,它将自动为构建过程导入 extend-corecompile.proj 文件。你可以在下面看到extend-corecompile.proj的内容。

      extend-corecompile.proj

      <?xml version="1.0" encoding="utf-8"?>
      <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
        <PropertyGroup>
          <TargetsTriggeredByCompilation>
            $(TargetsTriggeredByCompilation);
            MyCustomTarget
          </TargetsTriggeredByCompilation>
        </PropertyGroup>
      
        <Target Name="MyCustomTarget">
          <Message Text="MyCustomTarget called" Importance ="high"/>
        </Target>
      
      </Project>
      

      此项目文件使用my previous blog post 中概述的技术,仅在执行 CoreCompile 时执行 MyCustomTarget。

      注意:您可以在https://github.com/sayedihashimi/sayed-samples/tree/master/ExtBuildMultiple 获取此示例的最新版本。

      【讨论】:

      • 这是对您之前帖子的一个很好的扩展,我可能不得不承认并使用它,但它并不能解决我最初的问题。
      • 您对预先执行检查以确定编译输出有什么建议吗?
      • @Sayed - 如何在 MyCustomTarget 中使用 CoreCompile 生成的程序集?
      【解决方案3】:

      或者,您可以使用所有项目都引用的单个自动生成的 VersionInfo.cs 文件。要使用此技术,请从项目的 AssemblyInfo.cs 文件中删除版本、公司信息等属性(是的,这很痛苦,但您只需执行一次),并让批处理命令输出基于模板的 VersionInfo.cs 文件。要在 Visual Studio 中引用公用文件,请从项目上下文菜单中选择添加现有项,然后在文件浏览器中导航到 VersionInfo.cs 文件后,单击添加旁边的下拉箭头并选择添加为链接。

      下面是我使用的一个示例。该脚本被检入我们的 SCC 系统,并在构建开始时执行,向脚本提供 %BUILD_NUMBER%。

      SET BUILD=%1
      
      @echo using System.Reflection; > "%~p0Version.cs"
      @echo [assembly: AssemblyCompany("MyCompany, Inc.")] >> "%~p0Version.cs"
      @echo [assembly: AssemblyProduct("MyProduct")] >> "%~p0Version.cs"
      @echo [assembly: AssemblyCopyright("Copyright © 2012 MyCompany, Inc.")] >> "%~p0Version.cs"
      @echo [assembly: AssemblyTrademark("")]@echo [assembly: AssemblyVersion("1.0.%BUILD%.0")] >> "%~p0Version.cs"
      
      @echo [assembly: AssemblyFileVersion("1.0.%BUILD%.0")] >> "%~p0Version.cs"
      
      @echo ^<Include xmlns="http://schemas.microsoft.com/wix/2006/wi"^> > "%~p0Version.wxi"
      @echo   ^<?define VersionBuild="%BUILD%"?^> >> "%~p0Version.wxi"
      @echo ^</Include^> >> "%~p0\Version.wxi"
      

      【讨论】:

      • 很好的信息,但不能解决我们只想增加 MSBuild 决定重新编译的项目版本的问题。基本上我们有项目间的依赖关系,我想依靠 MSBuild 的可靠性来确定需要在依赖关系树中重建什么并相应地更新文件版本。其余不需要重新编译的项目就不用管了。最简单的选择当然是每次都重建所有内容并始终增加整个解决方案,但这是不可取的。
      【解决方案4】:

      即使您获得了需要编译的项目列表,如果您更新其中一个项目的 assemblyinfo.cs,也可能会引发更改,从而触发另一个项目的编译。

      所以,最简单的方法是根据源代码控制修订号生成所有 AssemblyInfo.cs 文件。您甚至可以获取每个项目目录的最新修订号,从而有效地了解该项目的“最后”修改时间。

      看到这个问题:How can I change AssemblyProduct, AssemblyTitle using MSBuild?

      根据您的评论,您是否查看了 BeforeBuild 和 AfterBuild 目标(在您的 csproj 文件末尾):

        <Target Name="BeforeBuild">
        </Target>
        <Target Name="AfterBuild">
        </Target>
      

      【讨论】:

      • 嗯,是的,我知道潜在的风险,但假设我们知道我们在依赖重建等方面正在做什么。我想找到这个特定问题的答案,我想有条件地做MSBuild 知道它会做一个 CoreCompile 但在它做之前。
      • 关于构建目标之前和之后,它们总是被解雇。我需要找到一种方法,仅在 MSBuild 确定需要重建时才执行此操作,否则我不妨每次都重建所有项目,或者运行两次完整的过程,以找出第一次发生的变化。
      猜你喜欢
      • 2011-07-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多