Setting properties' values in MSBuild - 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.

Related

MSBuild: Output properties from imported projects

Let's say I have a build.proj like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"
DefaultTargets="AfterBuild"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<CustomAfterMicrosoftCSharpTargets>$(MSBuildThisFileDirectory)Common.Build.targets</CustomAfterMicrosoftCSharpTargets>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<ProjectProperties>
Configuration=$(Configuration);
Platform=$(Platform);
CustomAfterMicrosoftCSharpTargets=$(CustomAfterMicrosoftCSharpTargets);
</ProjectProperties>
</PropertyGroup>
<ItemGroup>
<ProjectToBuild Include="$(MSBuildThisFileDirectory)src\Proj\MyApp.csproj" />
</ItemGroup>
<Target Name="Build">
<MSBuild Targets="Build"
Projects="#(ProjectToBuild)"
Properties="$(ProjectProperties)" />
</Target>
<Target Name="AfterBuild" DependsOn="Build">
<Message Text="ChildProperty: $(ChildProperty)" />
</Target>
</Project>
In Common.Build.targets, I have a Target that creates a property:
<Target Name="DoSomethingUseful">
<!-- Do something useful -->
<CreateProperty Value="SomeComputedThingy">
<Output TaskParameter="Value" PropertyName="ChildProperty"/>
</CreateProperty>
</Target>
Now if I build build.proj, I do not see the value of ChildProperty in the message. The output is blank: ChildProperty:.
I was under the impression that any output for a target is merged back to global context after its execution. But it seems that it only applies to anything within that target file.
How do I make ChildProperty bubble up to the parent build.proj?
When you are calling <MSBuild> task on dependent projects, read TargetOutputs output parameter of the task. See example from MSDN:
<Target Name="BuildOtherProjects">
<MSBuild
Projects="#(ProjectReferences)"
Targets="Build">
<Output
TaskParameter="TargetOutputs"
ItemName="AssembliesBuiltByChildProjects" />
</MSBuild>
</Target>
You will also need to ensure the target you are calling in dependent projects correctly populates Returns or Output parameter (Returns takes precedence if used). E.g.:
<Target Name="MyTarget" Inputs="..." Outputs="..." Returns="$(MyOutputValue)">
<PropertyGroup>
<MyOutputValue>set it here</MyOutputValue>
</PropertyGroup>
</Target>

Test if an MSBuild property constains a substring

I have a property in an MSBuild project which is a semicolon-separated-list of string values. How can I test if the list constains a particular value?
In the example listing below, I want the target DeployToServer only to be executed if the property $(DCC_Define) constains 'WebDeploy'.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DCC_Define>WebDeploy;DEBUG</DCC_Define>
</PropertyGroup>
<Target Name="DeployToServer" Condition="$(DCC_Define) constains 'WebDeploy'">
<Message Text="Do something." />
</Target>
</Project>
I've used a bit of pseudo logic in the #Condition attribute to indicate what I mean. I am using a .NET framework version of 2.0.50727.3655; and MSBuild version of 3.4.30729.1 .
How can I achieve this? I don't have the luxury of being able to upgrade to MSBuild 4.
Well, since you can't use property function you have to get creative.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DCC_Define>WebDeploy;DEBUG;WebDeploy</DCC_Define>
</PropertyGroup>
<Target Name="DeployToServer">
<CreateItem Include="$(DCC_Define)">
<Output TaskParameter="Include" ItemName="DCC_Define" />
</CreateItem>
<!-- Not required since MSBuild doesn't execute targets twice -->
<!-- <CreateProperty Value="True" Condition="%(DCC_Define.Identity) == WebDeploy">
<Output TaskParameter="Value" PropertyName="WebDeploy" />
</CreateProperty> -->
<CallTarget Targets="_DeployToServer" Condition="%(DCC_Define.Identity) == WebDeploy" />
</Target>
<Target Name="_DeployToServer">
<Message Text="Do something." />
</Target>
</Project>

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>

Make ItemGroup created in MSBuild target available to calling targets

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>

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.