How can you conditionally run an MSBuild task only when your project outputs have been built? - msbuild

I want to run an MSBuild Task (which signs an executable/dll) but only when the output exe/dll has changed. If none of the source files have changed causing a recompile of the exe/dll then I don't want the task to run.
Despite spending several hours trying different things out I cannot work out how to get my target task to only run if the project has been compiled where the output files have changed (in other words the CoreCompile target was not skipped I think).

You can just do this:
<PropertyGroup>
<TargetsTriggeredByCompilation>DoStuffWithNewlyCompiledAssembly</TargetsTriggeredByCompilation>
</PropertyGroup>
This works because someone smart at Microsoft added the following line at the end of the CoreCompile target in Microsoft.[CSharp|VisualBasic][.Core].targets (the file name depends on the language and MSBuild/Visual Studio version).
<CallTarget Targets="$(TargetsTriggeredByCompilation)" Condition="'$(TargetsTriggeredByCompilation)' != ''"/>
So if you specify a target name in the TargetsTriggeredByCompilation property, your target will run if CoreCompile runs-- and your target will not run if CoreCompile is skipped (e.g. because the output assembly is already up-to-date with respect to the code).

Should be the same as this answer, using the TargetOutputs parameter::
<MSBuild Projects="File.sln" >
<Output TaskParameter="TargetOutputs" ItemName="AssembliesBuiltByChildProjects" />
</MSBuild>
<Message Text="Assemblies built: #(AssembliesBuiltByChildProjects)" /> <!-- just for debug -->
<CallTarget Targets="SignExe" Condition="'#(AssembliesBuiltByChildProjects)'!=''" />

Related

How can I have an msbuild target run only if new compilation actually occurred?

I am trying to have MSBuild generate a text file with a version number representing when the build was created.
Right now in my MSBuild task I have the following:
<Target Name="WriteBuildDate" AfterTargets="Compile">
<WriteLinesToFile File="buildversion.txt" Lines="$([System.DateTime]::UtcNow.Year).$([System.DateTime]::UtcNow.Month).$([System.DateTime]::UtcNow.Day).$([System.Math]::Floor($([System.DateTime]::UtcNow.Subtract($([System.DateTime]::UtcNow.Date)).TotalSeconds)))" Overwrite="true" Encoding="Unicode" />
</Target>
This works, except that it executes even if the all assemblies were not rebuilt. This means if I use the VS publish capability for multiple servers each of them will have slightly different version numbers, even though the builds are all the same.
Is there any way (without custom msbuild tasks) to have this target only execute if at least one assembly was rebuilt?
Thanks to #stijn's comment/link, I was able to get this working via:
<PropertyGroup>
<RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
</PropertyGroup>
<Target Name="WriteBuildDate" AfterTargets="CoreBuild" Condition="'$(_AssemblyTimestampBeforeCompile)'!='$(_AssemblyTimestampAfterCompile)'">
<WriteLinesToFile File="buildversion.txt" Lines="$([System.DateTime]::UtcNow.Year).$([System.DateTime]::UtcNow.Month).$([System.DateTime]::UtcNow.Day).$([System.Math]::Floor($([System.DateTime]::UtcNow.Subtract($([System.DateTime]::UtcNow.Date)).TotalSeconds)))" Overwrite="true" Encoding="Unicode" />
</Target>

Trigger build when another finished successfully in TFS 2008

This is a feature I'm used to from TeamCity - I could specify that a certain build configuration will be triggered by the success of another build configuration.
I could even pass the results of one build to another - but perhaps this is asking too much.
I'm looking for a similar functionality in TFS2008, is there a way to set a trigger on a build configuration that it shall start after another finished successfully?
I use the following target in my TFSBuild.proj:
Inject the new targets into the build process. We only trigger dependent builds if a "drop" was successfully created:
<PropertyGroup>
<DropBuildDependsOn>
$(DropBuildDependsOn);
CreateDependentBuildItemGroup;
TriggerDependentBuilds;
</DropBuildDependsOn>
</PropertyGroup>
Create a itemgroup that contains a list of the dependent builds we want to trigger (the Include attribute will list the name of the dependent build as it appears in the build explorer - in my case below, the dependant build is called "Integration"). In our build process, we sometimes want to trigger more than one build, and we want to point the next build at the binaries that were produced by the current build (in this example, I want to run Integration tests against the binaries produced). Notice the hack to get around spaces in configuration names - eg "Any CPU" will cause a problem in the MsBuild args. Using this format, we can have custom MSBuild args per dependent build.
<Target Name="CreateDependentBuildItemGroup">
<ItemGroup>
<DependentBuild Include="Integration">
<!--Using 8dot3 format for "Mixed Platforms" as it's tricky (impossible?) to pass a space char within /msbuildarguments of tfsbuild-->
<MsBuildArgs>/p:CallingBuildDropFolder=$(DropLocation)\$(BuildNumber)\Mixedp~1\Ship;CiSmallBuildNumber=$(CiSmallBuildNumber);BuildNumberPostFix=$(BuildNumberPostFix)</MsBuildArgs>
<PriorityArg>/priority:AboveNormal</PriorityArg>
</DependentBuild>
</ItemGroup>
</Target>
Now, trigger the builds. Notice that we use a Custom GetOption: we want to make sure that dependent builds use the same changeset that the current build used - we can't use Latest, cos someone may have checked in in the meantime - so we want all dependent builds in our "chain" to all be based of the same changeset. The actual command is within the Exec, and the BuildStep stuff is to make sure we report the success (or failure) of the Exec.
<Target Name="TriggerDependentBuilds"
Condition=" '$(CompilationStatus)' == 'Succeeded' ">
<BuildStep TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Name="TriggerStep"
Message="Triggering Dependent Builds">
<Output TaskParameter="Id"
PropertyName="TriggerStepId" />
</BuildStep>
<PropertyGroup>
<TriggerBuildCommandBase>TfsBuild start $(TeamFoundationServerUrl) $(TeamProject)</TriggerBuildCommandBase>
</PropertyGroup>
<Exec
ContinueOnError="true"
WorkingDirectory="C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE"
Command="$(TriggerBuildCommandBase) %(DependentBuild.Identity) /queue /getOption:Custom /customGetVersion:$(GetVersion) %(DependentBuild.PriorityArg) /msbuildarguments:"%(DependentBuild.MsBuildArgs)"">
<Output TaskParameter="ExitCode"
ItemName="TfsBuildResult"/>
</Exec>
<BuildStep Condition="'#(TfsBuildResult)'=='0'"
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Id="$(TriggerStepId)"
Status="Succeeded" />
<BuildStep Condition="'#(TfsBuildResult)'!='0'"
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Id="$(TriggerStepId)"
Status="Failed" />
</Target>
I hope that helps...

How to call the a unit test target in a project from a 'solution' project

I'm trying to get Team City to build my .NET solution and run my nUnit tests.
I know I can modify the individual projects and tell them always run the unit tests. I don't want the unit tests to run when I click "build" in visual studio, but I do want the unit tests to run when Team City kicks off a msbuild task.
I tried "msbuild solutionname.sln" and gave team city the targets of "BUILD" and my custom build tag of "TEST". However, msbuild can't find any specified target when invoked against a sln solution. So, I ran msbuild to convert my solution into a project which has a target like this:
<Target Name="Build">
<MSBuild Projects="#(BuildLevel0)" >
</Target>
I naively thought I could write a new task like this:
<Target Name="BuildAndTest">
<CallTarget Targets="Build"/> <!-- This builds everything in solution -->
<CallTarget Targets="Test"/> <!-- DOES NOT WORK. This target exists in project that gets built by this solution -->
</Target>
The nunit target looks like this:
<Target Name="Test" DependsOnTargets="Build" Condition=" '$(Configuration)' == 'Release'">
<NUnit Assemblies="$(OutputPath)\Tsa.BaseTest.dll" ContinueOnError="false" ToolPath="C:\Program Files\NUnit 2.5.2\bin\net-2.0\" DisableShadowCopy="true" OutputXmlFile="$(OutputPath)\nunit-results.xml" />
</Target>
As you can see, it references OutputPath, which only the project knows--the solution doesn't have reference to $OutputPath, else I'd just put all the test targets into the "solution project".
Any suggestions on how I can get this to work?
I think you're making this a lot harder than it needs to be. TeamCity has built-in support for running NUnit unit tests after the build - you don't need to modify the MSBuild file at all. Just set up your Build Configuration (I think it's under Runner) to specify the version of NUnit and which assemblies are test assemblies.
NOTE: I checked and we have this under Runner: sln2008 (section NUnit Test Settings) in TeamCity Enterprise Version 4.5.4, but I don't see anything on the JetBrains site that states that it's specific to Enterprise. It may require a version upgrade, though. See TeamCity Testing Frameworks.
This is what finally worked. It is ignored by visual studio, msbuild will run this section correctly, and team city will as well, although it replaces the Target with its own an runtime (according to the build log).
TeamCity will "automatically" run nunit tests and display the results, only in the sense that it will do so after manually editing the msbuild file, doing numerous manual teaks and telling TeamCity where each assembly is and where each output file is.
<Project (snip) DefaultTargets="BuildAndTest" (snip)>
<Target Name="BuildAndTest">
<CallTarget Targets="Build" />
<CallTarget Targets="TestBase" />
</Target>
<Target Name="TestBase" DependsOnTargets="Build">
<NUnit Assemblies="Tsa.BaseTest\bin\RELEASE\Tsa.BaseTest.dll" ContinueOnError="false" ToolPath="C:\Program Files\NUnit 2.5.2\bin\net-2.0\" DisableShadowCopy="true" OutputXmlFile="$(SolutionDir)\Tsa.BaseTest\bin\RELEASE\nunit-results.xml" />
</Target>
</Target>
</Project>

MSBuild ITaskItem array is out of date

I'm creating a custom ITask for MSBuild which uploads the output files of my build. I'm using a web deployment project to publish my app and hooking in to the AfterBuild target to do my custom work.
If I add a file to my web application, the first time I do a build, my custom task is not recognizing the recently added file. In order for that file to show up in my array of ITaskItems, I have to first do a build with my 'AfterBuild' target removed and then start the build again with my 'AfterBuild' target in place.
Here is what my build file looks like:
<ItemGroup>
<PublishContent Include="$(OutputPath)\**" />
</ItemGroup>
<Target Name="AfterBuild">
<UploadTask FilesToPublish="#(PublishContent)" />
</Target>
The list in #(PublishContent) seems to be initialized at the beginning of the build instead of reflecting any changes that might have taken place by the build process itself.
Any ideas?
Thanks
Your ItemGroup is going to get evaluated when the project file first loads (when you first open Visual Studio or you 'unload' and 'reload' the project in Solution Explorer).
What you probably need is to create an ItemGroup as a task in your 'AfterBuild' target. Example:
<CreateItem Include="$(OutputPath)\**">
<Output TaskParameter="Include" ItemName="OutputFiles"/>
</CreateItem>
followed by:
<Target Name="AfterBuild">
<UploadTask FilesToPublish="#(OutputFiles)" />
</Target>

Conditional compilation with automated builds in Visual Studio

Here's what I'm trying to do:
A single build script
That script builds two executables from the same Visual Studio project.
The first compiled .exe has a small amount of code disabled.
The other compiled .exe has everything enabled.
I've been reading up on conditional compilation and that takes care of my needs as far as enabling/disabling blocks of code.
I just can't figure out how to control conditional compilation from a build script using msbuild.
Is there a way to manipulate conditional compilation variables from a build script or some other way to accomplish what I'm trying to do?
Use build configurations in your project file. Set the parameters in a PropertyGroup that is optionally included based on the configuration. The configuration can then also define the output path for the two different versions of the assembly.
For the version that needs to remove some code use a configuration that includes the PropertyGroup.
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'CompiledOutDebug|AnyCPU' ">
<DefineConstants>$(DefineConstants);MY_CONDITIONAL_COMPILATION_CONSTANT</DefineConstants>
</PropertyGroup>
Then use an MSBuild script that calls the project MSBuild script twice and uses the Properties attribute of the MSBuild task to specify the configuration to build:
<Target Name="Build">
<MSBuild Projects="MyProject.csproj;"
Targets="Build"
Properties="Configuration=Release" />
<MSBuild Projects="MyProject.csproj"
Targets="Build"
Properties="Configuration=CompiledOutDebug" />
</Target>
Hamish beat me to it.
Here's an alternate solution using the same concepts:
At the command line:
msbuild -t:Clean
msbuild
CopyOutputDirForWithoutDefine.cmd
msbuild -t:Clean
msbuild -property:DefineConstants=MY_CONDITIONAL_COMPILE_CONSTANT
CopyOutputDirForWithDefine.cmd
The 1st and 3rd 'msbuild -t:Clean' ensures that you don't have left over turds from previous builds. The 2nd 'msbuild' builds without the conditional define, while the 4rth builds with the conditional define.
If the above are just a couple on shot items, then a batch file maybe enough. I recommend learning a bit of MSBuild and actually scripting everything in a MSBuild file as Hamish has done.
If you don't want to create a separate target for the two compilations, you can do it by specifying the conditional define in the DefineConstants property when you call the build the second time:
<Target Name="Build">
<MSBuild Projects="MyProject.csproj;"
Targets="Build"
Properties="Configuration=Debug" />
<MSBuild Projects="MyProject.csproj"
Targets="Build"
Properties="Configuration=Debug;
AssemblyName=$(AssemblyName)_Conditional;
DefineConstants=$(DefineConstants);CONDITIONAL_DEFINE" />
</Target>
Note that if you do it this way, you need to also overwrite the AssemblyName, otherwise your second build might pick intermediate files from the first build.
You should also look at the MSBuild task docs on MSDN, there are some interesting tidbits there.