当一个csproj未被构建时,抑制AfterBuild目标

8
我有一个在MSBuild中的后构建目标,用于复制一些构建输出。
这与“AfterBuild”目标(由“Microsoft.CSharp.targets”公开)相关联作为依赖项:
<Target Name="AfterBuild" DependsOnTargets="InstallUtil;CopyPostBuildFiles" />

如果构建实际上没有重新构建,有没有办法避免文件被复制?

例如,当MSBuild依赖分析断言项目不需要构建,因为它的源文件没有更新时,它不会构建,但仍会执行我的复制目标。有没有办法防止这种情况发生?

7个回答

9

我刚刚谷歌搜索了这个问题,并找到了几年前的答案。我发现可以通过从核心目标中借鉴一个想法来更轻松地完成此操作。覆盖BeforeBuild和AfterBuild,然后执行以下操作:

  <Target Name="BeforeBuild">
    <PropertyGroup>
      <MyBeforeCompileTimestamp>%(IntermediateAssembly.ModifiedTime)
                 </MyBeforeCompileTimestamp>
    </PropertyGroup>
  </Target>

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

谢谢!那正是我需要的解决方案。 - fmuecke
很好的概念...我在一个Core项目中使用了这个概念,其中BeforeBuild和AfterBuild并不直接可访问。相同的效果可以通过BeforeTargets和AfterTargets实现。参见:https://gist.github.com/brinko99/2bb84da5351403ab076d262d9d8487a4 - Terrence

7

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

<Project ...>
   <!-- some content here -->

   <Import Project="... Microsoft.Csharp.targets" />


    <PropertyGroup>
        <RebuildDependsOn>
            $(RebuildDependsOn);
            InstallUtil;
            CopyPostBuildFiles
        </RebuildDependsOn>
    </PropertyGroup>
</Project>

这种方法扩展了Rebuild目标使用的属性,用于声明它依赖哪些目标。我在文章《深入了解MSBuild》中详细介绍了这一点,请参见“扩展构建过程”部分。
此外,如果您可以指定会“触发”更新的文件,那么您要完成的目标也可以通过AfterBuild目标来实现。换句话说,您可以将一组“输入”和一组“输出”(均为文件)指定为目标。如果所有输出都是在所有输入之后创建的,则该目标将被视为最新,并跳过。这个概念称为增量构建,我在文章《MSBuild最佳实践第二部分》中进行了介绍。此外,我还在我的书《深入了解Microsoft Build引擎:使用MSBuild和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 目标中复制了输入和输出,然后将其粘贴到这里。这意味着每当执行 CoreCompile 目标时,目标会被跳过。由于我扩展了 BuildDependsOn,所以我们知道 MSBuild 将在构建项目时尝试执行它。

看起来更新“RebuildDependsOn”目标只会在显式调用“Rebuild”目标时触发额外的步骤。如果你调用“Build”,它们不会触发。 - Daniel Fortunov
我刚刚编辑了它以涵盖这种情况。我对你想要的东西感到困惑。这个可以吗? - Sayed Ibrahim Hashimi
使用输入和输出对我很有帮助。在我的情况下,我能够使用项目的主输出文件 -- 即 $(TargetPath) -- 作为所有其他依赖项的代理。 - yoyo

6

在编译目标中使用输入和输出目标属性,那么使用它们有何不同呢?

<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>

你可以在CopyPostBuildFilesInstallUtil上使用它,或者直接在AfterBuild上使用。
有关Targets Inputs Outputs的更多信息,请参见此页面。

0

只有在项目目标文件已经重建的情况下,才会调用AfterBuild目标:

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

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


0

嗨Mark,这个属性是一个不错的发现,但我想要的是能够让我在“nop build”之后抑制复制任务的东西,因为在这种情况下没有实际构建任何内容,因为所有输出都被认为是最新的。 - Daniel Fortunov

0

构建目标定义是:

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

我认为如果你的文件没有改变,CoreBuild不会被执行,所以你可以尝试使用AfterCompile代替AfterBuild。
<Target Name="AfterCompile" DependsOnTargets="InstallUtil;CopyPostBuildFiles" />

好主意,但似乎没有太大的区别。即使我将AfterBuild更改为AfterCompile,复制仍然会每次执行。 - Daniel Fortunov
哦,抱歉,我的测试用例可能有点糟糕 ^^。 - Julien Hoarau

-1

关于MSBuild,我不是很了解,但在C#中,对于Visual Studio使用的MSBuild项目,有一个简单的替代方案:使用后生成事件(Post-build build event)而不是AfterBuild目标。

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

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

就像我说的那样,我不太了解MSBuild,后生成事件可能不适用于一些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脚本,您需要显式指定“call”。还要注意使用“pause” - 这使得通过双击BAT文件测试脚本成为可能,然后您可以在cmd窗口中查看任何错误消息。当通过MSBuild运行脚本时,“pause”将被忽略。


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