8
votes

I have a post-build target in MSBuild to copy some build outputs.

This is linked in as a dependency to the AfterBuild target (exposed by Microsoft.CSharp.targets):

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

Is there any way to avoid the files being copied if the build didn't actually re-build?

For example, when the MSBuild dependency analysis asserts that the project doesn't need to be built because none of its source files have been updated, it doesn't build, but still executes my copy target. Is there any way to prevent this?

7

7 Answers

9
votes

I just googled this and found this years old answer. I found a way, to do this a bit easier, by stealing an Idea from core targets. Override both BeforeBuild and AfterBuild and do something like:

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

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

Since you are overriding the AfterBuild target it will always execute after the build occurs. This is the case for a rebuild or a normal build. If you want to perform some actions after the rebuild (and not build) then you should extend the dependency property for the Rebuild target. So in your case to inject the targets after a rebuild occurs your project file should look something like:

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

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


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

This method extends the property which the Rebuild targets uses to declare what targets it depends on. I've detailed this in the article Inside MSBuild, see section Extending the build process.

Also what you are trying to accomplish may be acheived by the AfterBuild target if you can specify what what files would "trigger" an update to occur. In other words you can specify a set of "inputs" into the target and a set of "outputs" which are both files. If all outputs were created after all inputs then the target would be considerd up to date and skipped. this concept is known as Incremental Building and I've coverd it in the article MSBuild Best Practices Part 2. Also I have detailed this in my book, Inside the Microsoft Build Engine: Using MSBuild and Team Foundation Build.

EDIT: Adding BuildDependsOn example

If you want the target to only execute when the files are actually built and not just when the Rebuild target is executed. Then you should create your project to be like the following:

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

I just copied the inputs and outputs from the CoreCompile target inside of Microsoft.CSharp.targets (I'm assuming your are using C# here) and pasted it here. What this means is that the target will be skipped whenever the CoreCompile target is executed. Also since I extended the BuildDependsOn we know that MSBuild will try and execute it whenever the project is built.

6
votes

What about using Inputs and Outputs target's properties like it's done in compile target.

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

You could used that on CopyPostBuildFiles and InstallUtil or directly on AfterBuild.

See this page for more info on Targets Inputs Outputs

0
votes

Build target definition is:

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

I think that CoreBuild is not executed if your files have not changed, so you could try to use AfterCompile instead of AfterBuild.

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

What about this.

  • BuildBreak. Set to true when a compilation error occurs, otherwise false. Can be used in build break targets to determine whether they are executing after a compilation error or executing normally.

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

I haven't tried it, but it sounds pretty promising.

0
votes

This will invoke the AfterBuild target if and only if the project target file has been rebuilt:

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

This assumes that you have defined a property $(DeployPath) that identifies the folder where you copied the target output file.

-1
votes

I don't know much about MSBuild, but in the case of the MSBuild projects used by Visual Studio for C# there is a simple-minded alternative: Use a Post-build build event instead of an AfterBuild target.

You can set up a Post-build build event via the Visual Studio project properties dialog, on the third tab "Build Events". For some situations it may be an idea to enter some commands on this dialog, and then edit the .csproj file directly once you've determined how it works.

Remember to select "Run this post-build event: When the build updates the project output" on the dialog - that's the key to getting the functionality requested by the OP.

Like I say, I don't know much about MSBuild, and it may be that the Post-build build event is not applicable for some things that the AfterBuild target can do. But I've used it for copying files and running BAT scripts, and for that it works fine.

EDIT:

I'll add a few notes about how I usually use post-build events in my C# projects.

In order to separate different areas of functionality I usually create a BAT script called PostBuildEvent.bat, and place it in the same folder as the .csproj file. Then my post-build event contains only two lines:

cd $(ProjectDir)
PostBuildEvent.bat

Then I place the commands I want in the PostBuildEvent.bat file. Here's an example:

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

Remember that to call a BAT script from a BAT script you explicitly specify "call". Note also the use of "pause" - this makes it possible to test the script by double-clicking on the BAT file and then you can see any error messages in the cmd window. The "pause" is ignored when the script is run via MSBuild.