Msbuild: transforming one itemgroup into another itemgroup with a different structure - msbuild

I may be asking the wrong question here, and I'm open to that, so I'll give a bit of background of what I'm trying to do. I invoke mstest via an msbuild project, after dynamically finding all test assemblies. I invoke mstest separately for every test assembly, so that the results can be imported into teamcity (my CI server) as soon as they're available, rather than waiting for all of them to be complete before showing any progress in TC.
The problem is that this runs a single test at a time, and combined with the slow overhead (even on an i7 quad, mstest takes 3-5 seconds overhead to open for each project) and many tests, the tests take a few minutes to run.
Using the msbuild task with BuildInParallel=true (and invoking with the /m parameter), it's possible to build several projects at once.
So what I'm trying to do is
get a list of all *.Tests.dll
Invoke the ExecMsTest target in the same project, in parallel, for each .dll
<PropertyGroup>
<MsTestExePath Condition="'$(MsTestExePath)'==''">C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\MSTest.exe</MsTestExePath>
<MsTestSettingsPath Condition="'$(MsTestSettingsPath)'==''">Project.testsettings</MsTestSettingsPath>
</PropertyGroup>
<ItemGroup>
<TestAssemblies Include="**\bin\**\*.Tests.dll" />
</ItemGroup>
<Target Name="RunTests">
<Message Text="Found test assemblies: #(TestAssemblies)" />
<MakeDir Directories="TestResults" />
<MsBuild Projects="#(ProjectsToBuild)" Targets="ExecMsTest" BuildInParallel="True" />
</Target>
<Target Name="ExecMsTest">
<Message Text="Running tests in $(TestAssembly)" />
<!-- show TC progress -->
<Message Text="##teamcity[progressMessage 'Running tests in $(TestAssembly).dll']" Importance="High" />
<PropertyGroup>
<MsTestCommand>"$(MsTestExePath)" /testcontainer:"$(TestAssembly)" /resultsfile:"TestResults\$(TestAssembly).trx" /testsettings:"$(MsTestSettingsPath)"</MsTestCommand>
</PropertyGroup>
<!-- Message Text="Exec: $(MsTestCommand)" / -->
<Exec Command="$(MsTestCommand)" ContinueOnError="true" />
<!-- import data to teamcity test results -->
<Message Text="##teamcity[importData type='mstest' path='TestResults\$(TestAssembly).trx']" />
<Message Text="Tests complete from $(TestAssembly)" />
However, this isn't quite right. You can see my itemgroup is called TestAssemblies, but I'm passing #(ProjectsToBuild) to mstest. This is because the msbuild task requires a differently-formatted item group, something like:
<ItemGroup>
<ProjectsToBuild Include="Project.mstest.proj">
<Properties>TestAssembly=Project.UI.Tests</Properties>
</ProjectsToBuild>
<ProjectsToBuild Include="Project.mstest.proj">
<Properties>TestAssembly=Project.Model.Tests</Properties>
</ProjectsToBuild>
</ItemGroup>
So this is the crux of my question, assuming I'm even asking the right thing: how do I transform the TestAssemblies itemgroup into something resembling the ProjectsToBuild itemgroup?
In case it's not obvious, the name of the items in TestAssemblies are the *.tests.Dll filenames, while I need that name to be a inside the item, and the name of the ProjectsToBuild item to all be the Project.mstest.proj file (since they're all invoking the same file).
Thanks to #Spider M9, this works:
<ItemGroup>
<TestAssemblies Include="**\bin\**\*.Tests.dll" />
</ItemGroup>
<Target Name="RunTests">
<Message Text="Found test assemblies: #(TestAssemblies)" />
<ItemGroup>
<TestAssembliesToBuild Include="Project.mstest.proj">
<Properties>TestAssembly=%(TestAssemblies.FileName);FullPath=%(TestAssemblies.FullPath)</Properties>
</TestAssembliesToBuild>
</ItemGroup>
<MakeDir Directories="TestResults" />
<MsBuild Projects="#(TestAssembliesToBuild)" Targets="ExecMsTest" BuildInParallel="True" />
</Target>
Running msbuild single-threaded, my entire build (which includes compiling, building application and database snapshots, deploying schema for a couple databases that get used in some of the unit tests, and then finally running mstest) took about 9m30s. After this change, it was taking ~7m.
However, prior to getting an answer to this question, I just tried running a single mstest instance, to see how much it would improve, and that takes about 4m50s (of which mstest takes slightly over 1 minute to run). The downside is I have to wait until all tests are complete before getting results, but considering the staggering improvement from 6m to 1m that's a perfectly acceptable trade-off.
To be clear, the only difference is that mstest is starting once, vs starting a dozen times, and presumably there is also some benefit from multitasking. I'm running this on a Core i7-860 (4 physical cores, 8 logical cores) and I suspect number of cores will highly influence the level of improvement this change makes.
Here is my new RunTests:
<Target Name="RunTests">
<Message Text="Found test assemblies: #(TestAssemblies)" />
<MakeDir Directories="TestResults" />
<!-- this executes mstest once, and runs all assemblies at the same time. Faster, but no output to TC until they're all completed -->
<PropertyGroup>
<MsTestCommand>"$(MsTestExePath)" #(TestAssemblies->'/testcontainer:"%(FullPath)"', ' ') /resultsfile:"TestResults\Results.trx" /testsettings:"$(MsTestSettingsPath)"</MsTestCommand>
</PropertyGroup>
<Message Text="##teamcity[progressMessage 'Running tests']" Importance="High" />
<Message Text="Exec: $(MsTestCommand)" />
<Exec Command="$(MsTestCommand)" ContinueOnError="true" />
<Message Text="##teamcity[importData type='mstest' path='TestResults\Results.trx']" />
</Target>
also, you need a testsettings file with: <Execution parallelTestCount="0"> (0 means autodetect, default is 1) and need to invoke msbuild using the /m parameter and/or <Msbuild BulidInParallel="true">

Try this:
<ItemGroup>
<TestAssemblies Include="**\bin\**\*.Tests.dll" />
<TestAssembliesToBuild Include="Project.mstest.proj">
<Properties>TestAssembly=%(TestAssemblies.FileName)</Properties>
</TestAssembliesToBuild>
</ItemGroup>
<Message Text="'%(TestAssembliesToBuild.Properties)'" />

Related

Have MSBuild only build targets that require building

So long story short, we've got a situation of having multiple solutions with assembly references between them. Used to be, we had to go and build these solutions in the correct order to have our dependencies built correctly.
I'm trying to make things easier by using MSBuild tasks to first restore nuget on the dependent solution, then build the dependencies for you, ie:
<Target Name="BeforeBuild" Condition="'$(Configuration)'=='Debug'">
<Message Importance="high" Text="Kicking off internal dependency build for $(MSBuildProjectName) in Configuration=$(Configuration), Platform=$(Platform), OutDir=$(OutDir)" />
<!--Build the dependencies-->
<Message Importance="high" Text="Restoring MyDependencyProject NuGet External Packages..." />
<Exec Command="..\..\bin\nuget.exe restore ..\MyProject\MyDependencyProject\MyDependencyProject.sln" />
<Message Importance="high" Text="Building MyDependencyProject dependency..." />
<MSBuild Projects="..\MyProject\MyDependencyProject\MyDependencyProject\MyDependencyProject.csproj" Targets="Build" Properties="Configuration=$(Configuration)" />
<Message Importance="high" Text="Building MyDependencyProject2 dependency..." />
<MSBuild Projects="..\MyProject\MyDependencyProject\MyDependencyProject\MyDependencyProject2.csproj" Targets="Build" Properties="Configuration=$(Configuration)" />
</Target>
This works great! What used to be 7(!) separate solutions we had to open and build, is now condensed to 2 (we build our C++ COM libraries, then build our C# libraries).
One problem - while this is easier, it is very slow. I've been going over the diagnostic output from MSBuild, and it seems that the way I've set this up is forcing the dependencies to be rebuilt every single time. If I had set Targets="Rebuild" I would expect this, but I thought Targets="Build" would build ONLY IF a project was out of date (or had a file set to):
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
in the project file). I can not find any message in the diagnostic output to explain why the MSBuild target is rebuilding these dependencies, even if the dependencies have not been touched.
Why does MSBuild do a full rebuild of dependency projects that have not been altered with the above tasks?
Edit - More information
I see the following:
Target "IncrementalClean: (TargetId:324)" in file "C:\Program Files (x86)\MSBuild\14.0\bin\Microsoft.Common.CurrentVersion.targets" from project "c:\Source\MyProject\MyDependencyProject\MyDependencyProject\MyDependencyProject.csproj" (target "CoreBuild" depends on it):

Incremental Build of Nuget Packages

I want to execute an msbuild project which uses batching to determine that one or more csproj projects have been freshly-built, and therefore require fresh nuget packaging. The script I've made so far seems like a reasonable start, but it the incremental-build mechanism isn't working. The MainBuild target executes every time, no matter what.
Here is what I have:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="MainBuild" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)'=='' ">Debug</Configuration>
<Content>content\plugins\</Content>
</PropertyGroup>
<ItemGroup>
<Nuspec Include="$(MSBuildProjectDirectory)\plugins\*\*.nuspec" />
</ItemGroup>
<Target Name="MainBuild"
Inputs="%(Nuspec.RootDir)%(Nuspec.Directory)bin\$(Configuration)\*.dll"
Outputs="%(Nuspec.RootDir)%(Nuspec.Directory)%(FileName).pkg" >
<ItemGroup>
<Inputs Include="%(Nuspec.RootDir)%(Nuspec.Directory)bin\$(Configuration)\*.dll" />
<Outputs Include="%(Nuspec.RootDir)%(Nuspec.Directory)%(FileName).pkg" />
</ItemGroup>
<Message Text="INPUTS: %(Inputs.FullPath)" />
<Message Text="OUTPUTS: #(Outputs->'%(FullPath)')" />
<Copy SourceFiles="#(Inputs)" DestinationFiles="#(Outputs->'%(FullPath)')" />
</Target>
</Project>
The Copy task is just a debugging placeholder for calling-out to nuget and creating a new package.
The idea is that if any files in the bin\Debug directory are newer than the corresponding .nuspec file (found two folders above bin\Debug), then the MainBuild target should execute.
Any ideas?
p.s. The Inputs and Outputs attributes of the Target presumably each create an item. I think it strange that the items created can't be referenced inside the target. In the above example, I had to make a target-interna dynamic ItemGroup to re-create the items, just so that I could access them. Is there a way around that?
I read this in the MSBuild Batching documentation
If a task inside of a target uses batching, MSBuild needs to determine
if the inputs and outputs for each batch of items is up-to-date.
Otherwise, the target is executed every time it is hit.
Which may be the cuprit. Try changing your copy target to use batching instead of an ite transform (I don't think using item metadata in an item group satisfies the above requirement).
<Target Name="MainBuild"
Inputs="%(Nuspec.RootDir)%(Nuspec.Directory)bin\$(Configuration)\*.dll"
Outputs="%(Nuspec.RootDir)%(Nuspec.Directory)%(FileName).pkg" >
<ItemGroup>
<Inputs Include="%(Nuspec.RootDir)%(Nuspec.Directory)bin\$(Configuration)\*.dll" />
<Outputs Include="%(Nuspec.RootDir)%(Nuspec.Directory)%(FileName).pkg" />
</ItemGroup>
<Message Text="INPUTS: %(Inputs.FullPath)" />
<Message Text="OUTPUTS: #(Outputs->'%(FullPath)')" />
<Copy SourceFiles="#(Inputs)" DestinationFiles="%(Outputs.FullPath)" />
</Target>
It looks like the number of inputs may be different than the number of outputs (I suspect there is more than one .dll files in the output directory for each project), which will also cause the target to execute.

MSBuild hangs after NUnit is finished

I'm trying to set up a MSBuild with NUnit as unit test driver but the script keeps hanging after NUnit is done. It doesn't seem to finalize its work and let MSBuild get on with its job.
I'm working in .NET 4.0 and using NUnit 2.5.8.
If I run the test manually or using the gui (either VS2010 or NUnit) it works fine but not when called by MSBuild.
I'd appreciate any help with error finding or just a heads up on where to looks for answers.
The manual command looks like this:
C:\....>nunit\nunit-console.exe buildbinaries\YYYY.XXXX.Extractor.Test.IntegrationTest.dll /xml=nunit.xml
and the abbreviated MSBuild:
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- define folders for build output and reports -->
<PropertyGroup>
<BuildPath>buildbinaries\</BuildPath>
<ReportPath>buildreports\</ReportPath>
<ReleaseFolder>release_artefacts\</ReleaseFolder>
<PublishFolder>c:\ZZZ Applications\published builds\</PublishFolder>
<DeploymentFolder>\\seldclq99\ZZZ_Costanza_Dev$\</DeploymentFolder>
</PropertyGroup>
<PropertyGroup>
<!-- specify assemblies that should be included in coverage report -->
<NCoverAssemblyList>YYYY.XXXX.Extractor.Business.dll; YYYY.XXXX.Extractor.Common.dll YYYY.XXXX.Extractor.Configuration.dll YYYY.XXXX.Extractor.DAL.Access.dll YYYY.XXXX.Extractor.DAL.Facade.dll YYYY.XXXX.Extractor.Service.Contracts.dll YYYY.XXXX.Extractor.Service.dll YYYY.XXXX.Extractor.Service.Host.WebHost.dll YYYY.XXXX.Extractor.ServiceGateway.dll</NCoverAssemblyList>
</PropertyGroup>
<!-- define item group for deliverables -->
<ItemGroup>
<Binaries Include="$(BuildPath)/**/*.*" Exclude="$(BuildPath)nunit*" />
</ItemGroup>
<!--
This is the default target that will be executed if MSBuild is not started
with a specific target (this is decided by the DefaultTargets attribute in
the root element of this XML document)
-->
<Target Name="BuildAndTest">
<CallTarget Targets="SetupDirs" />
<CallTarget Targets="Build" />
<CallTarget Targets="UnitAndIntegrationTest" />
<CallTarget Targets="FxCop" />
<CallTarget Targets="CopyToReleaseFolder" />
</Target>
<!-- Setup folders used during the build -->
<Target Name="SetupDirs">
<RemoveDir Directories="$(ReportPath);$(BuildPath);$(ReleaseFolder)" ContinueOnError="true"/>
<MakeDir Directories="$(ReportPath);$(BuildPath);$(ReleaseFolder);$(AssemblyVersionFolder)" ContinueOnError="true"/>
</Target>
<Target Name="Build">
<!-- build the software using msbuild -->
<!-- Build error in the Install build-->
<MSBuild ContinueOnError="true" RebaseOutputs="false" Targets="Clean;Rebuild" Projects="YYYYXXXXExtractor.sln" Properties="Configuration=Release;OutDir=..\$(BuildPath)" />
</Target>
<!--Run the coverage stats-->
<Target Name="UnitAndIntegrationTest">
<Exec Command="nunit\nunit-console.exe buildbinaries\YYYY.XXXX.Extractor.Test.IntegrationTest.dll /xml=$(ReportPath)nunit.xml "/>
<CallTarget Targets="UnitTest" />
</Target>
<Target Name="UnitTest">
<Exec Command="nunit\nunit-console.exe buildbinaries\YYYY.XXXX.Extractor.Test.UnitTest.dll /xml=$(ReportPath)nunit.xml"/>
</Target>
<!-- Run FxCop -->
<!-- The ForceError.bat fires if the xml file is not found... aka an error was found -->
<!-- The quiet command forces an Xml file ONLY if warnings or Errors are found -->
<Target Name="FxCop">
<Exec Command="..\tools\fxcop\FxCopCmd.exe /p:..\FxCopSettings.FxCop /o:$(ReportPath)fxcop.xml" />
<Exec Condition="Exists('$(ReportPath)fxcop.xml')" Command="..\tools\fxcop\FX_Cop_Failed_Rule_Checks.bat" />
<!--STATS: Run again but don't fail and this time run for all rules.-->
<Exec Command="..\tools\fxcop\FxCopCmd.exe /p:..\FxCopSettingsALLRULES.FxCop /o:$(ReportPath)fxCopAllRules.xml" />
</Target >
I had the same problem with NUnit 2.5.8. There is some discussion of this at the nunit site about the test process hanging. I switched to NUnit 2.5.7 and the problem went away.
It looks like this was fixed a couple of weeks ago in 2.5.9.
I have noticed similar behaviour on out build server since upgrading to .NET 4. MsBuild seems to intermittently hang on either NUnit, FxCop or Dotcover EXEC commands. If you check task manager the process for externally executed command (e.g. Nunit.exe) is still hanging around. If you manually kill the process MsBuild continues on it's merry way - which is far from ideal!
Could this be a bug in the latest version of MsBuild? Our build server was running quite happily until the .NET 4 upgrade.
If you run ProcessExplorer on your server you will notice that an out of band process called nunit-agent is spawned which ends up blocking the nunit runner.
I have not validated that this is fixed in 2.5.9, but it might be some info that could be helpful.

Teamcity build loops on successful build

I have set up a build with Teamcity. See my build file below.
When the build is succesful and the tests pass, the build process just runs again and again indefinitely in a loop.
When the build fails, this does not happen.
I have tried to first set 60 second pause on buildtriggering, and finally disabled build triggering altogether. No difference.
What else could be the cause of this?
My MSBuild file looks like this:
<Project DefaultTargets="Build;Test" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<PropertyGroup>
<DeployDirectory>$(MSBuildProjectDirectory)\..\bin</DeployDirectory>
<DependencyDirectory>$(MSBuildProjectDirectory)\Dependencies</DependencyDirectory>
<LinqToSqlMapFolder>$(DeployDirectory)\LinqToSql</LinqToSqlMapFolder>
<NCoverVersionForMSI>$(BUILD_NUMBER)</NCoverVersionForMSI>
<NCoverVersionPeriod>$(BUILD_NUMBER)</NCoverVersionPeriod>
</PropertyGroup>
<ItemGroup>
<ProjectFiles Include="**\*.vbproj"/>
<ConfigFiles Include="**\*.config"/>
<MapFiles Include="**\*.linqtosql.config"/>
<TestAssemblies Include="$(DeployDirectory)\*.Test.dll"/>
<Dependencies Include="$(DependencyDirectory)\**\*" />
</ItemGroup>
<Target Name="Clean">
<MSBuild Projects="#(ProjectFiles)" Targets="Clean"/>
</Target>
<Target Name="Build">
<MSBuild Projects="#(ProjectFiles)" Targets="Rebuild">
<Output TaskParameter="TargetOutputs" ItemName="BuildOutput"/>
</MSBuild>
<Copy SourceFiles="#(BuildOutput)" DestinationFolder="$(DeployDirectory)" />
<Copy SourceFiles="#(Dependencies)" DestinationFolder="$(DeployDirectory)" SkipUnchangedFiles="true" />
<Copy SourceFiles="#(ConfigFiles)" DestinationFolder="$(DeployDirectory)" SkipUnchangedFiles="true" />
<Copy SourceFiles="#(MapFiles)" DestinationFolder="$(LinqToSqlMapFolder)" SkipUnchangedFiles="true" />
</Target>
<UsingTask AssemblyFile="$(DependencyDirectory)\Gallio\Gallio.MsBuildTasks.dll" TaskName="Gallio" />
<Target Name="Test">
<Gallio IgnoreFailures="true" Files="#(TestAssemblies)">
<Output TaskParameter="ExitCode" PropertyName="ExitCode"/>
</Gallio>
</Target>
</Project>
While it appears that this wasn't your issue, I ran into a similar looping problem of my own. I had enabled labeling in the project configuration. I was also using a check for modifications every 60 seconds rule to trigger the build. As a result, upon successful build, TeamCity would tag the build in the VCS and then 60 seconds later, it would see (it's own) modification and trigger another build.
To fix our problem, we just disabled labeling because we didn't want it anyways, but you can also configure a rule to ignore particular authors such that it won't trigger on modifications it made.
It appears there were some problems with the install of teamcity, and a backup of configuration following a reinstall solved the problem with exactly same configuration and buildscript.

MSBuild passing parameters to CallTarget

I'm trying to make a reusable target in my MSBuild file so I can call it multiple times with different parameters.
I've got a skeleton like this:
<Target Name="Deploy">
<!-- Deploy to a different location depending on parameters -->
</Target>
<Target Name="DoDeployments">
<CallTarget Targets="Deploy">
<!-- Somehow indicate I want to deploy to dev -->
</CallTarget>
<CallTarget Targets="Deploy">
<!-- Somehow indicate I want to deploy to testing -->
</CallTarget>
</Target>
But I can't work out how to allow parameters to be passed into the CallTarget, and then in turn the Target itself.
MSBuild targets aren't designed to receive parameters. Instead, they use the properties you define for them.
<PropertyGroup>
<Environment>myValue</Environment>
</PropertyGroup>
<Target Name="Deploy">
<!-- Use the Environment property -->
</Target>
However, a common scenario is to invoke a Target several times with different parameters (i.e. Deploy several websites). In that case, I use the MSBuild MSBuild task and send the parameters as Properties:
<Target Name="DoDeployments">
<MSBuild Projects ="$(MSBuildProjectFullPath)"
Properties="VDir=MyWebsite;Path=C:\MyWebsite;Environment=$(Environment)"
Targets="Deploy" />
<MSBuild Projects ="$(MSBuildProjectFullPath)"
Properties="VDir=MyWebsite2;Path=C:\MyWebsite2;Environment=$(Environment)"
Targets="Deploy" />
</Target>
$(MSBuildProjectFullPath) is the fullpath of the current MSBuild script in case you don't want to send "Deploy" to another file.
You can 'foreach' over an ItemGroup with a target, only you have to do it in declaritive manner. You can even have additional metadata in items, like in the code example:
<ItemGroup>
<What Include="Dev">
<How>With bugs</How>
</What>
<What Include="Test">
<How>With tests</How>
</What>
<What Include="Chicken">
<How>Deep fried</How>
</What>
</ItemGroup>
<Target Name="Deploy">
<Message Text="#(What), %(How)" />
</Target>
Using an item group as a scalar value #(What) inside a target does the trick, and %(How) references a metadata element in a foreach item.
It's a natural way of doing things in msbuild, for example you can find this pattern everywhere in project files generated with Visual Studio.
There might be a better way to do this in MSBuild, but in Ant, I would use global properties to carry information from one task to the next. It was a lousy solution, but I didn't see a better way at the time. You should be able to do this in MSBuild, but bear in mind that you will need to use the CreateProperty task to dynamically assign a property.
On the other hand, it's pretty easy to implement tasks in C# (or VB or whatever). Maybe that's a better solution for you.
<CreateProperty
Value="file1">
<Output
TaskParameter="Value"
PropertyName="filename" />
</CreateProperty>
<CallTarget Targets="Deploy"/>
<Message Text="$(filename)"/>
<CreateProperty
Value="file2">
<Output
TaskParameter="Value"
PropertyName="filename" />
</CreateProperty>
<Message Text="$(filename)"/>
<CallTarget Targets="Deploy"/>