【问题标题】:Suppressing AfterBuild targets when a csproj has not been built在尚未构建 csproj 时抑制 AfterBuild 目标
【发布时间】:2016-06-08 00:25:27
【问题描述】:

我在 MSBuild 中有一个构建后目标来复制一些构建输出。

这是作为AfterBuild目标的依赖项(由Microsoft.CSharp.targets公开):

<Target Name="AfterBuild" DependsOnTargets="InstallUtil;CopyPostBuildFiles" />

如果构建实际上没有重新构建,有什么方法可以避免文件被复制?

例如,当 MSBuild 依赖分析断言不需要构建项目,因为它的源文件都没有更新,它不会构建,但仍然执行我的复制目标。有什么办法可以预防吗?

【问题讨论】:

    标签: c# msbuild


    【解决方案1】:

    我刚刚搜索了这个并找到了这个多年的答案。我找到了一种方法,通过从核心目标窃取一个想法来更容易地做到这一点。覆盖 BeforeBuild 和 AfterBuild 并执行以下操作:

      <Target Name="BeforeBuild">
        <PropertyGroup>
          <MyBeforeCompileTimestamp>%(IntermediateAssembly.ModifiedTime)
                     </MyBeforeCompileTimestamp>
        </PropertyGroup>
      </Target>
    
      <Target Name="AfterBuild">
        <CallTarget Condition="$(MyBeforeCompileTimestamp) !=   
              %(IntermediateAssembly.ModifiedTime)" Targets="MyTarget" /> 
      </Target>
    

    【讨论】:

    【解决方案2】:

    由于您要覆盖 AfterBuild 目标,因此它将始终在构建发生后执行。这是重建或正常构建的情况。如果您想在重建(而不是构建)之后执行一些操作,那么您应该扩展 Rebuild 目标的依赖属性。因此,在您的情况下,在重建发生后注入目标,您的项目文件应如下所示:

    <Project ...>
       <!-- some content here -->
    
       <Import Project="... Microsoft.Csharp.targets" />
    
    
        <PropertyGroup>
            <RebuildDependsOn>
                $(RebuildDependsOn);
                InstallUtil;
                CopyPostBuildFiles
            </RebuildDependsOn>
        </PropertyGroup>
    </Project>
    

    此方法扩展了 Rebuild 目标用来声明它所依赖的目标的属性。我在文章Inside MSBuild 中对此进行了详细说明,请参阅扩展构建过程部分。

    如果您可以指定哪些文件将“触发”更新发生,那么 AfterBuild 目标也可以实现您尝试完成的任务。换句话说,您可以在目标中指定一组“输入”和一组“输出”,它们都是文件。如果所有输出都是在所有输入之后创建的,那么目标将被认为是最新的并被跳过。这个概念被称为增量构建,我已经在文章MSBuild Best Practices Part 2 中介绍了它。我也在我的书中详细介绍了这一点,Inside the Microsoft Build Engine: Using MSBuild and Team Foundation Build。

    编辑:添加 BuildDependsOn 示例

    如果您希望目标仅在文件实际构建时执行,而不仅仅是在执行 Rebuild 目标时。然后你应该创建你的项目,如下所示:

    <Project ...>
       <!-- some content here -->
    
       <Import Project="... Microsoft.Csharp.targets" />
    
    
        <PropertyGroup>
            <BuildDependsOn>
                $(BuildDependsOn);
                CustomAfterBuild;
            </BuildDependsOn>
        </PropertyGroup>
       <Target Name="CustomAfterBuild" Inputs="$(MSBuildAllProjects);
                @(Compile);                               
                @(_CoreCompileResourceInputs);
                $(ApplicationIcon);
                $(AssemblyOriginatorKeyFile);
                @(ReferencePath);
                @(CompiledLicenseFile);
                @(EmbeddedDocumentation); 
                $(Win32Resource);
                $(Win32Manifest);
                @(CustomAdditionalCompileInputs)"
        Outputs="@(DocFileItem);
                 @(IntermediateAssembly);
                 @(_DebugSymbolsIntermediatePath);                 
                 $(NonExistentFile);
                 @(CustomAdditionalCompileOutputs)">
    
       <!-- Content here -->
    
        </Target>
    
    </Project>
    

    我刚刚从 Microsoft.CSharp.targets 内的 CoreCompile 目标复制了输入和输出(我假设您在此处使用 C#)并将其粘贴到此处.这意味着只要执行 CoreCompile 目标,就会跳过目标。此外,由于我扩展了 BuildDependsOn,我们知道 MSBuild 会在项目构建时尝试并执行它。

    【讨论】:

    • 看来更新“RebuildDependsOn”目标只会在您明确调用“Rebuild”目标时触发额外的步骤。如果您调用“构建”,它们将不会触发。
    • 我刚刚对其进行了编辑以涵盖此案例。我对你想要什么感到困惑。这行得通吗?
    • 使用输入和输出对我有用。就我而言,我能够使用项目的主输出文件——即 $(TargetPath)——作为所有其他依赖项的代理。
    【解决方案3】:

    像在编译目标中那样使用 Inputs 和 Outputs 目标的属性怎么样?

    <PropertyGroup>
      <PostBuildDir>CopiedFilesDirectory</PostBuildDir>
    </PropertyGroup>
    
    <Target Name="AfterBuild" DependsOnTargets="InstallUtil;CopyPostBuildFiles" />
    
    <Target Name="CopyPostBuildFiles"
            Inputs="@(PostBuildFiles)"
            Outputs="@(PostBuildFiles -> '$(PostBuildDir)%(Filename)%(Extension)'">
        <Copy SourceFiles="@(PostBuildFiles)"
              DestinationFolder="PostBuildDir"/>
    </Target>
    

    您可以在 CopyPostBuildFiles 和 InstallUtil 或直接在 AfterBuild 上使用它。

    有关Targets Inputs Outputs的更多信息,请参阅此页面

    【讨论】:

      【解决方案4】:

      构建目标定义为:

      <Target Name = "Build" DependsOnTargets="BeforeBuild;CoreBuild;AfterBuild"/>
      

      我认为如果您的文件没有更改,CoreBuild 不会执行,因此您可以尝试使用 AfterCompile 而不是 AfterBuild。

      <Target Name="AfterCompile" DependsOnTargets="InstallUtil;CopyPostBuildFiles" />
      

      【讨论】:

      • 好主意,但似乎没有太大区别。即使在我将 AfterBuild 更改为 AfterCompile 之后,副本仍然每次都执行。
      • 哦抱歉,我的测试用例一定很烂^^。
      【解决方案5】:

      这个怎么样。

      • BuildBreak。设置为真时 出现编译错误,否则 错误的。可用于构建中断 目标来确定它们是否是 在编译错误后执行或 正常执行。

      http://blogs.msdn.com/aaronhallberg/archive/2008/02/12/team-build-2008-property-reference.aspx

      我没试过,但听起来很有希望。

      【讨论】:

      • 嗨,马克,这个属性是一个不错的发现,但是我正在寻找一些可以让我在“nop build”之后抑制复制任务的东西,因为所有输出都被认为是保持最新。
      【解决方案6】:

      当且仅当项目目标文件已重建时,这将调用 AfterBuild 目标:

      <Target Name="AfterBuild"
          DependsOnTargets="InstallUtil;CopyPostBuildFiles"
          Inputs="$(TargetPath)"
          Outputs="$(DeployPath)\$(TargetFileName)" />
      

      这假定您已经定义了一个属性$(DeployPath),该属性标识了您复制目标输出文件的文件夹。

      【讨论】:

        【解决方案7】:

        我对 MSBuild 了解不多,但对于 Visual Studio for C# 使用的 MSBuild 项目,有一个简单的替代方案:使用 Post-build 构建事件而不是 AfterBuild 目标。

        您可以通过 Visual Studio 项目属性对话框的第三个选项卡“构建事件”设置构建后构建事件。在某些情况下,最好在此对话框中输入一些命令,然后在确定其工作方式后直接编辑 .csproj 文件。

        记得在对话框中选择“运行此构建后事件:当构建更新项目输出时” - 这是获得 OP 请求的功能的关键。

        就像我说的,我对 MSBuild 了解不多,可能是 Post-build 构建事件不适用于 AfterBuild 目标可以做的某些事情。但是我已经用它来复制文件和运行 BAT 脚本,而且它工作得很好。

        编辑:

        我将添加一些关于我通常如何在我的 C# 项目中使用构建后事件的注释。

        为了区分不同的功能区域,我通常会创建一个名为 PostBuildEvent.bat 的 BAT 脚本,并将其放在与 .csproj 文件相同的文件夹中。然后我的构建后事件只包含两行:

        cd $(ProjectDir)
        PostBuildEvent.bat
        

        然后我将我想要的命令放在 PostBuildEvent.bat 文件中。这是一个例子:

        copy "..\..\..\..\..\Shared Bin\Merlinia.CommonClasses.NamedPipesNative.dll" bin
        copy "..\..\..\..\..\Shared Bin\Merlinia.CommonClasses.NamedPipesNative.pdb" bin
        
        cd Packed-NonObfuscated
        call PackForWindowsSystem32-NonObfuscated.bat
        cd ..\
        
        cd Packed-Obfuscated
        call PackForWindowsSystem32-Obfuscated.bat
        cd ..\
        
        pause
        

        请记住,要从 BAT 脚本调用 BAT 脚本,您明确指定“调用”。还要注意“暂停”的使用 - 这可以通过双击 BAT 文件来测试脚本,然后您可以在 cmd 窗口中看到任何错误消息。当脚本通过 MSBuild 运行时,“暂停”将被忽略。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-03-17
          • 1970-01-01
          • 1970-01-01
          • 2013-04-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多