<When> element below <Choose> element is unknown — why? - msbuild

I manually edited my MSBuild file to execute a few commands only when a certain condition is met:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
...
<Target Name="Rebuild" DependsOnTargets="Clean">
<Choose>
<When Condition="'$(MSBuildProjectDirectory)'!=''" >
<RemoveDir Directories="$(MSBuildProjectDirectory)\intermediate" />
<RemoveDir Directories="$(MSBuildProjectDirectory)\$(BaseIntermediateOutputPath)" />
<RemoveDir Directories="$(MSBuildProjectDirectory)\$(BaseOutputPath)" />
<Message Text="Artifact files have been deleted." />
</When>
</Choose>
</Target>
</Project>
Yet, Visual Studio 2019 denies to load the .vcxproj file. There error message is:
<When> element below <Choose> elements is unknown.
What did I do wrong here?
According to https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditional-constructs?view=vs-2019 I was convinced that everything I did was by the book.

See Choose element (MSBuild) and note the parent elements. Choose cannot be used within a Target.
However, $(MSBuildProjectDirectory) will always be set with a value and <When Condition="'$(MSBuildProjectDirectory)' != ''" > will always be true.

Related

Compiler Additional Options computed in a custom Target

I have a msbuild custom Target and a Task computing a Value.
The Task will output the Value as Property.
This Property I would like to uses as Additional Option to the Compiler call.
But the Property is empty when used as Additional Option.
My *.targets File looks like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask TaskName="GetBranchName_TASK" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
<ParameterGroup>
<sPath ParameterType="System.String" Required="true" />
<sBranchName ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
<![CDATA[
... some Code ...
]]>
</Code>
</Task>
</UsingTask>
<Target Name="GetBranchName_TARGET">
<GetBranchName_TASK sPath="$(MSBuildThisFileDirectory)">
<Output PropertyName="BranchName" TaskParameter="sBranchName" />
</GetBranchName_TASK>
<Message Importance="High" Text="BranchName = $(BranchName)" />
</Target>
<PropertyGroup>
<BuildDependsOn>
GetBranchName_TARGET;
$(BuildDependsOn);
</BuildDependsOn>
</PropertyGroup>
</Project>
My *.props File is like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Configuration">
... some Properties here ...
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="IRSGetBranchName.targets" />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalOptions>/DBRANCHNAME=$(BranchName) /DMORE=BAR</AdditionalOptions>
<ClCompile>
<ItemDefinitionGroup>
</Project>
This .props File then is imported into several .vcxproj
The Value printed as Message in my GetBranchName_TARGET is correct as expected (showing the correct TFS-Branch Name).
But when looking at Detailed Build Output, the Value seems empty:
1>ClCompile
1> ..\FOO.cpp
1> AdditionalOptions = /DBRANCHNAME= /DMORE=BAR
I tried for hours but found no solution and I really hope someone help whats wrong here ...
a) Is the Property BranchName not available globally? I tried to print the Property from other custom Targets and it worked well!
b) Or is the ClCompile.AdditionalOptions evaluated/build before my Target is excuted? In this case how can I re-evaluate?
c) ...
I'am very thankful for any Input.
You should be familiar with the msbuild evaluation process, as described here:
When the MSBuild engine begins to process a build file, it is evaluated in a top-down fashion in a multi-pass manner. These passes are described in order in the following list:
Load all environment and global properties, and toolset properties. In Microsoft Visual Studio 2010, for example, C++ defines several properties in the MSBuild 4.0 toolset.
Evaluate properties and process imports as encountered
Evaluate item definitions
Evaluate items
Evaluate using tasks
Start build and reading targets
So, in your case, the ItemDefinitionGroup for ClCompile has been evaluated before the GetBranchName_TARGET has been executed. So, it is empty by design.
In order to achieve the desired behavior, you should Add the following:
<Target Name="GetBranchName_TARGET">
<GetBranchName_TASK sPath="$(MSBuildThisFileDirectory)">
<Output PropertyName="BranchName" TaskParameter="sBranchName" />
</GetBranchName_TASK>
<Message Importance="High" Text="BranchName = $(BranchName)" />
<ItemGroup>
<ClCompile>
<AdditionalOptions>/DBRANCHNAME=$(BranchName) /DMORE=BAR</AdditionalOptions>
</ClCompile>
</ItemGroup>
</Target>
You can use a Condition attribute in the ClCompile in order to include only your sources, for example. Actually, what you are looking for is the feature to modify item metadata after it was declared.

MSBuild Command Line arguments length exceeds 8191 characters

I are using MSBUILD.exe to perform build for the application. as part of this I pass the required variables as command line arguments for MSBUILD.exe.
I have 2 files. service.xml and MyService.proj. Below line is present in service.xml file.
<installCommand name="MyService" cmd="msbuild.exe "MyService.proj" /p:{vairables}">
initially the length of the command was small and everything was fine as I was able to build my project but as the project size increased the number of parameters also increased, and now I am at a stage where the command line is displaying an error (Input line is too Long).
Upon some searching I found out that command line cannot be more than 8191 characters.
Can any one Suggest any alternatives for this.
What's installCommand?
MSBuild engine merges all system, user and process variables as well as parameters and properties into one big pool, so every property that you pass via {vairables} can be set first separatly. Keep in mind that properties pass via command line are global properties so environment variable equivalent will not override corresponding project property unless it has Condition=" '$(Foo)' == '' " on it.
http://msdn.microsoft.com/en-us/library/ms171458.aspx
You can set variables in an xml file, and pass the xml file-name to the msbuild script.
Here is a simple example that uses the MSBuildCommunityTasks.
Parameters.xml (contents below)
<?xml version="1.0" encoding="utf-8"?>
<parameters>
<setParameter name="LineNumber1" value="PeanutsAreCool" />
<setParameter name="LineNumber2" value="" />
</parameters>
MyMsbuildDef.proj (contents below)
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="AllTargetsWrapped">
<Import Project="$(MSBuildExtensionsPath32)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
<PropertyGroup>
<!-- Always declare some kind of "base directory" and then work off of that in the majority of cases -->
<WorkingCheckout>.</WorkingCheckout>
</PropertyGroup>
<Target Name="AllTargetsWrapped">
<CallTarget Targets="ReadXmlPeekValue" />
<CallTarget Targets="WriteXmlPeekValue" />
</Target>
<Target Name="ReadXmlPeekValue">
<!-- you do not need a namespace for this example, but I left it in for future reference -->
<XmlPeek Namespaces="<Namespace Prefix='peanutNamespace' Uri='http://schemas.microsoft.com/developer/msbuild/2003'/>"
XmlInputPath=".\Parameters.xml"
Query="/parameters/setParameter[#name='LineNumber1']/#value">
<Output TaskParameter="Result" ItemName="Peeked" />
</XmlPeek>
<Message Text="#(Peeked)"/>
<XmlPeek Namespaces="<Namespace Prefix='peanutNamespace' Uri='http://schemas.microsoft.com/developer/msbuild/2003'/>"
XmlInputPath=".\Parameters.xml"
Query="/parameters/setParameter[#name='LineNumber1']/#value">
<Output TaskParameter="Result" PropertyName="PeekedSingle" />
</XmlPeek>
<Message Text="PeekedSingle = $(PeekedSingle) "/>
</Target>
<Target Name="WriteXmlPeekValue" Condition=" '$(PeekedSingle)' != '' ">
<XmlPoke Namespaces="<Namespace Prefix='msb' Uri='http://schemas.microsoft.com/developer/msbuild/2003'/>"
XmlInputPath=".\Parameters.xml"
Query="/parameters/setParameter[#name='LineNumber2']/#value"
Value="$(PeekedSingle)" />
</Target>
</Project>
MyBatFile.bat (contents below)
set msBuildDir=%WINDIR%\Microsoft.NET\Framework\v4.0.30319
call %msBuildDir%\msbuild /target:AllTargetsWrapped "MyMsbuildDef.proj" /p:Configuration=Debug;FavoriteFood=Popeyes /l:FileLogger,Microsoft.Build.Engine;logfile=ZZZZZAllTargetsWrapped.log
set msBuildDir=

Dynamically create a property in msbuild to be used in a calltarget subtarget

How do I create a property in msbuild so that I can use it in a CallTarget directive?
Essentially I am trying to call a target 'subroutine' where the properties act as parameters.
I tried making a toy csproj file which attempts to create a property, and then calls a target which echos it. It echos null.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Test">
<CreateProperty Value="AAA">
<Output TaskParameter="Value" PropertyName="Foo" />
</CreateProperty>
<CallTarget Targets="Test2" />
</Target>
<Target Name="Test2">
<Message Text="Target Test2: Foo=$(Foo)" />
</Target>
</Project>
Running msbuild TestProj.csproj /t:Test outputs:
Test:
Target Test: Foo=AAA
Test2:
Target Test2: Foo=
I guess the problem is I'm thinking about msbuild in an imperative fashion (which is apparently a common mistake), so I'm hoping someone can correct what appears to be a very fundamental misunderstanding in how msbuild works.
You can use the target property DependsOnTarget to get the property passed from task to task.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Test">
<CreateProperty Value="AAA">
<Output TaskParameter="Value" PropertyName="Foo" />
</CreateProperty>
</Target>
<Target Name="Test2" DependsOnTargets="Test">
<Message Text="Target Test2: Foo=$(Foo)" />
</Target>
</Project>
The just call the second target.
Holy crap. This is apparently a bug in msbuild?
Overwrite properties with MSBuild
http://weblogs.asp.net/bhouse/archive/2006/03/20/440648.aspx
edit: Or this is a feature? https://stackoverflow.com/a/7539455

How do I stop Item Batching from executing a batch when there are zero items?

Execute this with msbuild:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Main" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Colors Include="Blue">
<Shade>Dark</Shade>
</Colors>
</ItemGroup>
<Target Name="Main">
<Message Text="Color: %(Colors.Shade) %(Colors.Identity)"/>
</Target>
</Project>
And it outputs:
Color: Dark Blue
All well, and good, but delete the color and use this:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Main" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
</ItemGroup>
<Target Name="Main">
<Message Text="Color: %(Colors.Shade) %(Colors.Identity)"/>
</Target>
</Project>
And it outputs:
Color:
Why is one batch of the Message task being executed when there are no items in the group? I would have expected for zero items, the batch would execute zero times and I would not see "Color:" followed by nothing in the output.
Am I doing something wrong or is there a workaround for this?
Thanks.
Update:
I've found you can do:
<Message Condition="'#(Colors)'!=''" Text="Color: %(Colors.Shade) %(Colors.Identity)"/>
But, if feels unsatisfactory to have to explicitly write code for the case when there are no items every time batching is used.
My 2 cents :
In your Message Task, there is information from Batching and static information ("Colors :"). I think MsBuild prints the static information and then batch over the values of your Colors Item. The probleme is that you don't have any data in your collection (it is even undeclared), I suppose MsBuild interpretates this as an empty list, which, when you try to print it, print the empty string ''.
If you remove the static content ("Colors :" and the whitespace before identity), you won't have anything displayed.
A solution for printing with batching only if the items collection is not empty would be either :
Check if the collection is empty
<Message Condition="'#(Colors)'!=''" Text="Color: %(Colors.Shade) %(Colors.Identity)"/>
Use MSBuild transforms
<Message Text="#(Colors->'Color : %(Shade) %(Identity)')"/>
Just wanted to add an alternative solution for this as well. If you can change your batching to Target batching, instead of Task batching, you can add your Condition statement to the Target.
I added the Target batching here:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Main" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
</ItemGroup>
<Target Name="Main" Outputs="%(Colors.Identity)">
<Message Text="Color: %(Colors.Shade) %(Colors.Identity)"/>
</Target>
</Project>
...and that can be conditionally made to only execute when something exists in the Colors item group:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Main" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
</ItemGroup>
<Target Condition="'#(Colors)'!=''" Name="Main" Outputs="%(Colors.Identity)">
<Message Text="Color: %(Colors.Shade) %(Colors.Identity)"/>
</Target>
</Project>

MSBuild MSBuildCommunityTasks Task Time

I have a MSBuild project and I want the current date to be added to a zip file that I am creating.
I am using the MSBuildCommunityTasks.
<!-- Import the CommunityTasks Helpper -->
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
On the website http://msbuildtasks.tigris.org/ I can see a task called time. I have not been able to find doc on how to use Time.
In msbuild 4 you can now
$([Namespace.Type]::Method(..parameters…))
$([Namespace.Type]::Property)
$([Namespace.Type]::set_Property(value))
so I am using
$([System.DateTime]::Now.ToString(`yyyy.MMdd`))
those ticks around the format are backticks not '
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Deploy" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<!-- Include MSBuild tasks here -->
<ItemGroup>
<DefaultExclude Include="****" />
</ItemGroup>
<Target Name="Deploy" >
<Time Format="yyyy-MM-dd">
<Output TaskParameter="FormattedTime" PropertyName="buildDate" />
</Time>
<Message Text="Deploying ...."></Message>
<Copy SourceFiles="#(DeploymentFiles)" DestinationFolder="C:\CCNET\$(buildDate)\bin\" />
</Target>
</Project>
Maslow's answer is correct (I can't comment on it or I would); I would only add to it that you have to be careful when implicitly calling System.DateTime.Parse.
A parsed string value like $([System.DateTime]::Parse("1970-01-01T00:00:00.0000000Z") doesn't seem to end up with a Kind of DateTimeKind.Utc.
But you can use nested property functions to make it work; like this (to get the Unix timestamp):
$([System.DateTime]::UtcNow.Subtract($([System.DateTime]::Parse("1970-01-01T00:00:00.0000000Z").ToUniversalTime())).TotalSeconds.ToString("F0"))