Make ItemGroup created in MSBuild target available to calling targets - msbuild

If I have the following targets in an MSBuild file:
<Target Name="Temp">
<CallTarget Targets="CreateTestList" />
<Message Text="TestList: -- #(TestAssembly) -- " />
<Message Text="Testing "%(TestAssembly.Identity)"" />
</Target>
<Target Name="CreateTestList">
<CreateItem Include="**\bin\$(Configuration)\*Tests.dll">
<Output TaskParameter="Include" ItemName="TestAssembly" />
</CreateItem>
<Message Text="TestList: -- #(TestAssembly) -- " />
<Message Text="Testing "%(TestAssembly.Identity)"" />
</Target>
How do I make the Message statements in my Temp target print out the items that the CreateTestList target put into the #(TestAssemblyList) ItemGroup?

Two things to note. First, the CreateItem task is essentially obsolete. Make it more readable by just declaring an ItemGroup inside your target. Second, due to how MSBuild publishes items, you need to make the CreateTestList target run as a dependency, not with CallTarget, which in most cases has limited usefulness. So,
<Target Name="Temp" DependsOnTargets="CreateTestList">
<Message
...
</Target>
<Target Name="CreateTestList">
<ItemGroup>
<TestAssembly Include="**\bin\$(Configuration)\*Tests.dll">
</ItemGroup>
<Message
...
</Target>

Related

What's the right way to get outputs in msbuild?

What's the right way for getting the outputs from CallTarget in msbuild? I tried doing something like this (see CoreOutput and EnumsOutput in CallTarget):
<Target Name="TerrainEngine" Outputs="$(GeneratedAssembly)">
<Message Text="Core0: $(CoreOutput)" />
<Message Text="Enum0: $(EnumsOutput)" />
<CallTarget Targets="Core" >
<Output TaskParameter="TargetOutputs" PropertyName="CoreOutput"/>
</CallTarget>
<Message Text="Core1: $(CoreOutput)" />
<Message Text="Enum1: $(EnumsOutput)" />
<CallTarget Targets="Enums" >
<Output TaskParameter="TargetOutputs" PropertyName="EnumsOutput"/>
</CallTarget>
<Message Text="Core2: $(CoreOutput)" />
<Message Text="Enum2: $(EnumsOutput)" />
<ItemGroup>
<RefsList Include="$(CoreOutput)" />
<RefsList Include="$(EnumsOutput)" />
</ItemGroup>
<PropertyGroup>
<Refs>#(RefsList)</Refs>
</PropertyGroup>
<Message Text="ModuleDir!!!!: $(Refs)" />
<MSBuild Projects="$(MSBuildProjectFullPath)"
Targets="CreateModule"
Properties="ModuleDir=BigMaps;
RefAssemblies=$(UnityLibsPath)/UnityEngine.dll;
RefModules=$Refs">
<Output TaskParameter="TargetOutputs" PropertyName="GeneratedAssembly"/>
</MSBuild>
</Target>
For some reason the second CallTarget invalidates both variables. I get output like this:
Core0:
Enum0:
Core1: modules/Core.mobule
Enum1:
Core2:
Enum2: modules/Enums.mobule
As far I understand this is due to some bug in msbuild logic (that's the impression that I've got from reading about it online).
Can you tell me how to solve it?
I've read that I should use DependsOnTargets instead, but I couldn't really figure out how to take the outputs from these then. Is that possible?

MSBuild incremental build error

I am facing one problem in building and copying .NET solution output to a deployment folder.
What i want to do is. Build solution and put the output into C:\TempOutput
then copy the outfiles from C:\TempOutput to another deployment folder.
at 1st attemp it creates folder in C:\TempOutput and creates output dlls and exes in this folder
also it creates folder "Exec\Debug\Bin" but does not copy files from C:\TempOutput
logs says that
Target CopyBuildFiles:
Skipping target "CopyBuildFiles" because it has no outputs.
When i run the script again this time it copies the files from C:\TempOutput to "Exec\Debug\Bin"
Am i missing something? Why it is not detecting output at 1st attempt?
following is the msbuild script
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Deploy">
<PropertyGroup>
<ProjectName>Common Projects</ProjectName>
<SolutionFilePath>..\..\Solution\Solution.sln</SolutionFilePath>
<!--Build/Rebuild-->
<BuildType>Build</BuildType>
<!--Debug -> output : local-->
<!--Release -> output : local-->
<!--ProduDbg -> output : X:\Debug-->
<!--ProduRel -> output : X:\Release-->
<BuildMode>Debug</BuildMode>
<OutputPath>..\Exec\$(BuildMode)\Bin\</OutputPath>
<ExecPath>..\..\bin\$(BuildMode)</ExecPath>
<DestinitionFolder>$(OutputPath)</DestinitionFolder>
<SubDirPath>$(ExecPath)\**</SubDirPath>
<BuildFolder>C:\TempOutputs\</BuildFolder>
</PropertyGroup>
<ItemGroup>
<File Include="
$(BuildFolder)\*.*
"
Exclude="
$(BuildFolder)\*.vshost*
"
>
</File>
</ItemGroup>
<Target Name="PreBuild">
<MakeDir Directories="$(BuildFolder)" />
<MakeDir Directories="$(DestinitionFolder)" />
</Target>
<Target Name="Compile">
<MakeDir Directories="$(BuildFolder)" />
<!-- Build does build only-->
<Message Text="*******************************************************"/>
<Message Text="-->Building $(ProjectName)"/>
<Message Text="*******************************************************"/>
<Message Text="*******************************************************"/>
<Message Text="-->Building in [$(BuildMode) | $(BuildType)] mode"/>
<Message Text="*******************************************************"/>
<MSBuild Projects="$(SolutionFilePath)" Targets="$(BuildType)" Properties="Configuration=$(BuildMode);OutDir=$(BuildFolder)"/>
</Target>
<Target Name="CopyBuildFiles"
Inputs="#(File)"
Outputs=
"#(File->'$(DestinitionFolder)%(RecursiveDir)%(Filename)%(Extension)')">
<Copy SourceFiles="#(File)"
DestinationFiles="#(File->'$(DestinitionFolder)%(RecursiveDir)%(Filename)%(Extension)')"
/>
</Target>
<Target Name="Deploy">
<CallTarget Targets="PreBuild"/>
<CallTarget Targets="Compile"/>
<Message Text="*******************************************************"/>
<Message Text="#(File)"/>
<Message Text="$(DestinitionFolder)"/>
<Message Text="*******************************************************"/>
<CallTarget Targets="CopyBuildFiles"/>
</Target>
</Project>
Try changing this bit
<Target Name="CopyBuildFiles"
Inputs="#(File)"
Outputs="#(File->'$(DestinitionFolder)%(File.RecursiveDir)%(File.Filename)%(File.Extension)')">
<Copy SourceFiles="#(File)"
DestinationFiles="#(File->'$(DestinitionFolder)%(File.RecursiveDir)%(File.Filename)%(File.Extension)')"/>
</Target>

Pass Output items to separate target with MSBuild

I am creating a buildscript, where I'm outputting the TargetOutputs of an MSBuild, then wanting to call FXCop in a separate target, and using those outputs in the TargetAssemblies.
<Target Name="Build">
<MSBuild Projects="#(Projects)"
Properties="Platform=$(Platform);Configuration=$(Configuration);"
Targets="Build"
ContinueOnError="false">
<Output TaskParameter="TargetOutputs" ItemName="TargetDLLs"/>
</MSBuild>
<CallTarget Targets="FxCopReport" />
</Target>
<Target Name="FxCopyReport">
<Message Text="FXCop assemblies to test: #(TargetDLLs)" />
<FxCop
ToolPath="$(FXCopToolPath)"
RuleLibraries="#(FxCopRuleAssemblies)"
AnalysisReportFileName="FXCopReport.html"
TargetAssemblies="#(TargetDLLs)"
OutputXslFileName="$(FXCopToolPath)\Xml\FxCopReport.xsl"
ApplyOutXsl="True"
FailOnError="False" />
</Target>
When I run this, in the FxCopyReport target, the Message of TargetDLLs in empty, whereas if I put this in the Build target, it populates.
How can I pass/reference this value?
There is a blog post by Sayed Ibrahim Hashimi (co-author of Inside MSBuild book), describing the issue you ran into, dating back in 2005. Essentially CallTarget task is behaving weird. I'm not sure if it is a bug or designed behavior, but the behavior is still the same in MSBuild 4.0.
As a workaround, use normal MSBuild mechanism of setting order of execution of targets in MSBuild, using attributes DependsOnTargets, BeforeTargets or AfterTargets.
I was able to figure this one out.
Essentially, after the MSBuild step, I created an ItemGroup, which I then referenced in the calling Target.
<Target Name="Build">
<Message Text="Building Solution Projects: %(Projects.FullPath)" />
<MSBuild Projects="#(Projects)"
Properties="Platform=$(Platform);Configuration=$(Configuration);"
Targets="Build"
ContinueOnError="false">
<Output TaskParameter="TargetOutputs" ItemName="TargetDllOutputs"/>
</MSBuild>
<ItemGroup>
<TestAssemblies Include="#(TargetDllOutputs)" />
</ItemGroup>
</Target>
<Target Name="FXCopReport">
<Message Text="FXCop assemblies to test: #(TestAssemblies)" />
<FxCop
ToolPath="$(FXCopToolPath)"
RuleLibraries="#(FxCopRuleAssemblies)"
AnalysisReportFileName="$(BuildPath)\$(FxCopReportFile)"
TargetAssemblies="#(TestAssemblies)"
OutputXslFileName="$(FXCopToolPath)\Xml\FxCopReport.xsl"
Rules="$(FxCopExcludeRules)"
ApplyOutXsl="True"
FailOnError="True" />
<Message Text="##teamcity[importData id='FxCop' file='$(BuildPath)\$(FxCopReportFile)']" Condition="'$(TEAMCITY_BUILD_PROPERTIES_FILE)' != ''" />
</Target>

MSBuild ItemGroup Include/Exclude pattern issue

Problem: an ItemGroups array isn't correctly build based on the value passed in the exclude attribute.
If you run this scrip it creates some sample file then tries to create an array called TheFiles based on the Include/Exclude attributes, problem is when the Exclude is anything other than hardcoded or a very simple property it gets it wrong.
The target DynamicExcludeList's incorrectly selects these files:
.\AFolder\test.cs;.\AFolder\test.txt
The target HardcodedExcludeList's correctly selects these files:
.\AFolder\test.txt
Any help much appreciated, this is driving me nuts.
(note its msbuild v4)
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Run">
<Target Name="Run" >
<CallTarget Targets="CreateSampleFiles" />
<CallTarget Targets="DynamicExcludeList" />
<CallTarget Targets="HardcodedExcludeList" />
</Target>
<Target Name="CreateSampleFiles" >
<MakeDir Directories="AFolder" />
<WriteLinesToFile Lines="Test" File="AFolder\test.cs" Overwrite="true" />
<WriteLinesToFile Lines="Test" File="AFolder\test.txt" Overwrite="true" />
</Target>
<Target Name="DynamicExcludeList" >
<PropertyGroup>
<CommonFileExclusion>.\DIRECTORY_NAME_TOKEN\**\*.cs</CommonFileExclusion>
<FinalExcludes>$(CommonFileExclusion.Replace('DIRECTORY_NAME_TOKEN', 'AFolder'))</FinalExcludes>
</PropertyGroup>
<Message Text="FinalExcludes: $(FinalExcludes)" />
<ItemGroup>
<TheFiles
Include=".\AFolder\**\*;"
Exclude="$(FinalExcludes)"
/>
</ItemGroup>
<Message Text="TheFiles: #(TheFiles)" />
</Target>
<Target Name="HardcodedExcludeList" >
<PropertyGroup>
<FinalExcludes>.\AFolder\**\*.cs</FinalExcludes>
</PropertyGroup>
<Message Text="FinalExcludes: $(FinalExcludes)" />
<ItemGroup>
<TheFilesWithHardcodedExcludes
Include=".\AFolder\**\*;"
Exclude="$(FinalExcludes)"
/>
</ItemGroup>
<Message Text="TheFilesWithHardcodedExcludes: #(TheFilesWithHardcodedExcludes)" />
</Target>
</Project>
This is the output, note the differences between 'TheFiles' and 'TheFilesWithHardcodedExcludes'
PS C:\SVN\TrunkDeployment\TestMsBuild> msbuild .\Test.build.xml
Microsoft (R) Build Engine Version 4.0.30319.1
[Microsoft .NET Framework, Version 4.0.30319.1]
Copyright (C) Microsoft Corporation 2007. All rights reserved.
Build started 8/10/2010 2:30:42 PM.
Project "C:\SVN\TrunkDeployment\TestMsBuild\Test.build.xml" on node 1 (default targets).
DynamicExcludeList:
FinalExcludes: .\AFolder\**\*.cs
TheFiles: .\AFolder\test.cs;.\AFolder\test.txt
HardcodedExcludeList:
FinalExcludes: .\AFolder\**\*.cs
TheFilesWithHardcodedExcludes: .\AFolder\test.txt
Done Building Project "C:\SVN\TrunkDeployment\TestMsBuild\Test.build.xml" (default targets).
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.06
EDITS
I've updated the above script to use the CreateItem, however there is still an issue when the list of items to exclude contains more than 1 path (i.e. the value of CommonFileExclusion has changed):
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Run">
<Target Name="Run" >
<CallTarget Targets="CreateSampleFiles" />
<CallTarget Targets="DynamicExcludeList" />
<CallTarget Targets="HardcodedExcludeList" />
</Target>
<Target Name="CreateSampleFiles" >
<MakeDir Directories="AFolder" />
<WriteLinesToFile Lines="Test" File="AFolder\test.cs" Overwrite="true" />
<WriteLinesToFile Lines="Test" File="AFolder\test.txt" Overwrite="true" />
<WriteLinesToFile Lines="Test" File="AFolder\test.vb" Overwrite="true" />
</Target>
<Target Name="DynamicExcludeList" >
<PropertyGroup>
<CommonFileExclusion>.\DIRECTORY_NAME_TOKEN\**\*.cs;.\DIRECTORY_NAME_TOKEN\**\*.vb;</CommonFileExclusion>
<FinalExcludes>$(CommonFileExclusion.Replace('DIRECTORY_NAME_TOKEN', 'AFolder'))</FinalExcludes>
</PropertyGroup>
<Message Text="FinalExcludes: $(FinalExcludes)" />
<CreateItem Include=".\AFolder\**\*;"
Exclude="$(FinalExcludes)">
<Output TaskParameter="Include" ItemName="TheFiles"/>
</CreateItem>
<Message Text="TheFiles: #(TheFiles)" />
</Target>
<Target Name="HardcodedExcludeList" >
<PropertyGroup>
<FinalExcludes>.\AFolder\**\*.cs;.\AFolder\**\*.vb</FinalExcludes>
</PropertyGroup>
<Message Text="FinalExcludes: $(FinalExcludes)" />
<CreateItem Include=".\AFolder\**\*;"
Exclude="$(FinalExcludes)">
<Output TaskParameter="Include" ItemName="TheFilesWithHardcodedExcludes"/>
</CreateItem>
<Message Text="TheFilesWithHardcodedExcludes: #(TheFilesWithHardcodedExcludes)" />
</Target>
</Project>
Ok, I tried a little and I think the problem comes from the fact that you use a property which represent SCALAR value for multiple values. I would recommend batching and transforming (see http://scottlaw.knot.org/blog/?p=402 and http://msdn.microsoft.com/en-us/library/ms171476.aspx). For example the following code is working :
<Target Name="DynamicExcludeList" >
<ItemGroup>
<ExtensionsExcluded Include="cs;vb" />
</ItemGroup>
<CreateItem Include=".\AFolder\**\*"
Exclude="#(ExtensionsExcluded->'.\AFolder\**\*.%(identity)')">
<Output TaskParameter="Include" ItemName="TheFiles"/>
</CreateItem>
<Message Text="TheFiles: #(TheFiles)" />
</Target>
In target DynamicExcludeList use CreateItem task instead of ItemGroup to dynamically generate your item :
<Target Name="DynamicExcludeList" >
<PropertyGroup>
<CommonFileExclusion>.\DIRECTORY_NAME_TOKEN\**\*.cs</CommonFileExclusion>
<FinalExcludes>$(CommonFileExclusion.Replace('DIRECTORY_NAME_TOKEN', 'AFolder'))</FinalExcludes>
</PropertyGroup>
<Message Text="FinalExcludes: $(FinalExcludes)" />
<CreateItem Include=".\AFolder\**\*;"
Exclude="$(FinalExcludes)">
<Output TaskParameter="Include" ItemName="TheFiles"/>
</CreateItem>
<Message Text="TheFiles: #(TheFiles)" />
</Target>
Theoretically ItemGroup and CreateItem are equivalent but I've seen case (dynamic situation) like this one when CreateItem must be use.

Setting properties' values in MSBuild

Let's consider the below example.
There, I have:
target MAIN calls target t and then calls target tt.
target t calls target ttt, and target tt calls target tttt.
target t defines property aa, and target ttt modifies aa.
target tttt tries to print property aa's value.
In short, we have: MAIN -> {t -> {ttt->modifies aa, defines aa}, tt -> tttt -> prints aa}
But in target tttt, we can't "see" aa's updated value (by ttt)! How do I make that value visible to target tttt?
The whole script is as below:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="MAIN" >
<Target Name="MAIN" >
<CallTarget Targets="t" />
<CallTarget Targets="tt" />
</Target>
<Target Name="t">
<Message Text="t" />
<PropertyGroup>
<aa>1</aa>
</PropertyGroup>
<CallTarget Targets="ttt" />
</Target>
<Target Name="tt">
<Message Text="tt" />
<CallTarget Targets="tttt" />
</Target>
<Target Name="ttt">
<PropertyGroup>
<aa>122</aa>
</PropertyGroup>
<Message Text="ttt" />
</Target>
<Target Name="tttt">
<Message Text="tttt" />
<Message Text="tttt:$(aa)" />
</Target>
</Project>
As already said in an answer to another post you should model your MSBuild project with dependencies between your Targets rather than calling Targets one after another.
<Project DefaultTargets="tttt" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="t">
<Message Text="t" />
<PropertyGroup>
<aa>1</aa>
</PropertyGroup>
</Target>
<Target Name="tt" DependsOnTargets="t">
<Message Text="tt" />
</Target>
<Target Name="ttt" DependsOnTargets="t;tt">
<PropertyGroup>
<aa>122</aa>
</PropertyGroup>
<Message Text="ttt" />
</Target>
<Target Name="tttt" DependsOnTargets="t;tt;ttt">
<Message Text="tttt" />
<Message Text="tttt:$(aa)" />
</Target>
</Project>
An approach I use, is to define a Target as my final goal, putting it into the projects DefaultTargets.
Then add all the things that need to happen to achieve this goal.