10
votes

I have a custom .targets file which I import into my C# MVC web application's project file. I've added custom targets to this like so:

<Target Name="CopyFiles" BeforeTargets="Build"></Target>

This works fine when building under Visual Studio, but when I use TeamCity to build it, the target never gets run, and I can't work out why.

If I change my target to use BeforeTargets="Compile" then it runs. Alternatively, if I add an additional target with the name Build to the .targets file

<Target Name="Build" />

then it will run, but doing so overrides the existing Build target and thus my application doesn't build. I can't quite make out the logic to this - it doesn't make sense. I'm using the Compile target for now, but if someone could explain why trying to execute it before the Build task doesn't work I'd really appreciate it.

1
Is your custom targets file being picked up by TeamCity? Has it definitely been added to you VCS?Castrohenge
Yes, and yes. As mentioned, if I change it to use BeforeTargets="Compile" then it runs. Just not with the Build task.Chris Anderson

1 Answers

10
votes

'Build' is a special built-in target, so doesn't really work the same way as most other targets. It definitely can't be safely overridden.

The most relevant documentation is here: https://msdn.microsoft.com/en-us/library/ms366724.aspx

If you want something to run before build, the standard approach (as recommend by the comments in a newly-created .csproj file) is to override the BeforeBuild target (as documented above).

However, this isn't the most robust solution. As noted in the documentation above:

Overriding predefined targets is an easy way to extend the build process, but, because MSBuild evaluates the definition of targets sequentially, there is no way to prevent another project that imports your project from overriding the targets you already have overridden.

It's better (and only slightly more complex), to override the BuildDependsOn property and extend the default value of this property to include the target you want to run (this is also documented in the link above).

Another approach would be to leave BeforeBuild empty and use BeforeTargets="BeforeBuild", which feels a bit odd but is quite simple and will still work even if the BeforeBuild target gets overridden.

As to why BeforeTargets="Build" doesn't work, I can't find a reference for this in the documentation, but I think it's to do with its special nature. It doesn't work the same as ordinary targets and it's probably better not to think of it as a target at all.