In MsBuild, is it possible to create an MSBuild condition (or another situation) that will evaluate whether a Property is 'defined' (presuming that this is previous to assigning the property a value somewhere)?
The following seems a little too clumsy to be reliable:
<PropertyGroup Label="Undefined State">
<Defined></Defined>
</PropertyGroup>
<Choose>
<When Condition="('$(Defined)' == '' OR '$(Defined)' != '')">
<Message Text="Defined is probably/likely/assuredly defined"/>
</When>
<Otherwise>
<Message Text="Defined is reportedly/maybe/possibly not defined"/>
</Otherwise>
<Choose>
There exists common method for overriding properties.
Sample from C:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets
<PropertyGroup>
<TargetFrameworkIdentifier Condition="'$(TargetFrameworkIdentifier)' == ''">.NETFramework</TargetFrameworkIdentifier>
<TargetFrameworkVersion Condition=" '$(TargetFrameworkVersion)' == '' ">v4.0</TargetFrameworkVersion>
</PropertyGroup>
If you will try to get value from $(NeverDefinedProperty) you just get an empty string.
Can you describe the problem you want to solve?
Related
I have a target like below. It needs to replace content of a file with new content. I have multiple files I am matching with ItemGroup.
I couldn't figure out a way to get this working.
Here is my target definition.
<ItemGroup>
<PRSetting Include="$(settings_root)\**\settings_config_*.xml">
<NewContent>$([System.IO.File]::ReadAllText('%(Identity)')).Replace('[config.version]', '$(PR_Version)'))</NewContent>
</PRSetting>
</ItemGroup>
<Target Name="PrepSettings" Inputs="#(PRSetting)"
Outputs="#(PRSetting->'$out\$Filename.xml')" >
<Message Text="%(PRSetting.Identity) new contents:" />
<Message Text="%(PRSetting.NewContent)"/>
</Target>
I hope I explained it right what I am trying to do. When the target is built, I am getting an error that the path to File::ReadFile() can't be empty string. I am using VS 2019. This is work in progress. I am yet to figure out how to save the new content in destination file.
Update
I have the Itemgroup outside. I updated the question. The reason it is outside is because the target inputs parameter needs it.
Try the following and see if it works:
<Target Name="PrepSettings">
<ItemGroup>
<PRSetting Include="$(settings_root)\**\settings_config_*.xml" />
<PRSetting>
<NewContent Condition="%(Identity) != ''">$([System.IO.File]::ReadAllText('%(Identity)')).Replace('[config.version]', '$(PR_Version)'))</NewContent>
</PRSetting>
</ItemGroup>
<Message Text="#(PRSetting.Identity) new contents:" />
<Message Text="%(PRSetting.NewContent)"/>
</Target>
There are two changes:
There seems to be an issue with an Include that doesn't use an existing ItemGroup and metadata that is self-referencing. So, setting up PRSetting is split in two.
First, establish PRSetting with the Include.
Second, revisit to add the NewContent metadata item.
Add Condition="%(Identity) != ''" on the NewContent metadata.
I'm not able to fully test your exact scenario at present but I tested an analogue.
Here is my test analogue:
<Target Name="PrepSettings">
<ItemGroup>
<PRSetting Include="1;4;2;3"/>
<PRSetting>
<NewContent Condition="%(Identity) != ''">$([MSBuild]::Add('%(Identity)', '1'))</NewContent>
</PRSetting>
</ItemGroup>
<Message Text="PRSetting is #(PRSetting->'(%(Identity),%(NewContent))');" />
</Target>
The output is
PrepSettings:
PRSetting is (1,2);(4,5);(2,3);(3,4);
Regarding your code change to move the ItemGroup outside the target:
The technique of splitting the ItemGroup as written won't work, but if you are using VS2017 or later and working with .NET Core/.NET 5+ you can use Update.
As shown in the question, the Outputs attribute has syntax errors. I assume Outputs="#(PRSetting->'$(OutputPath)\%(Filename).xml')" (or something close) is intended.
As shown in the question, the Outputs attribute will never be satisfied because PrepSettings doesn't create the files. I assume PrepSettings as shown is not complete.
MSBuild (targets file), how can I get the second parent of an address like this?
<PropertyGroup>
<LibFolder>$([System.IO.Directory]::GetParent($(MSBuildProjectDirectory)))</LibFolder>
</PropertyGroup>
This is for one level parent, how can I get the 2nd level? (Parent of the parent)
Simply call the function again, on the previous result:
<PropertyGroup>
<LibFolder>$([System.IO.Directory]::GetParent($(MSBuildProjectDirectory)))</LibFolder>
<LibFolder>$([System.IO.Directory]::GetParent($(LibFolder)))</LibFolder>
</PropertyGroup>
I'm trying to use a custom property in a VSTO add-in project file to ease pain when switching between VMs with different versions of Office installed. Given a PropertyGroup:
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug2010|AnyCPU' ">
<OfficeVersion>14.0</OfficeVersion>
</PropertyGroup>
This works:
<ProjectProperties HostName="Word" HostPackage="{29A7B9D7-A7F1-4328-8EF0-6B2D1A56B2C1}" OfficeVersion="$(OfficeVersion)" VstxVersion="4.0" ApplicationType="Word" Language="cs" TemplatesPath="VSTOTemplates" DebugInfoExeName="#Software\Microsoft\Office\14.0\Word\InstallRoot\Path#WINWORD.EXE" DebugInfoCommandLine="/x" AddItemTemplatesGuid="{51063C3A-E220-4D12-8922-BDA915ACD783}" />
This does not:
<ProjectProperties HostName="Word" HostPackage="{29A7B9D7-A7F1-4328-8EF0-6B2D1A56B2C1}" OfficeVersion="$(OfficeVersion)" VstxVersion="4.0" ApplicationType="Word" Language="cs" TemplatesPath="VSTOTemplates" DebugInfoExeName="#Software\Microsoft\Office\$(OfficeVersion)\Word\InstallRoot\Path#WINWORD.EXE" DebugInfoCommandLine="/x" AddItemTemplatesGuid="{51063C3A-E220-4D12-8922-BDA915ACD783}" />
The only difference is a literal 14.0 to DebugInfoExeName in the first instance, and a reference to OfficeVersion in the second. Is there a way through this?
I have the following problem in scripting with MSBuild:
I create a default item "itemA" with two metadata "metadata1" and "metadata2", whereby metadata2 refers to metadata1.
When I define itemA later and overwrite metadata1, the metadata2 contains still the default value of metadata1. How can I make the metadata2 to refer to the "new" metadata1?
Illustration in code as below:
<ItemDefinitionGroup>
<itemA>
<Metadata1>default</Metadata1>
<Metadata2>%(itemA.Metadata1)</Metadata2>
</itemA>
</ItemDefinitionGroup>
<ItemGroup>
<itemA Include="first" >
<Metadata1>m_data1</Metadata1>
</itemA>
</ItemGroup>
But see the print
<Message Text="itemA.Metadata1 = %(itemA.Metadata1)" />
<Message Text="itemA.Metadata2 = %(itemA.Metadata2)" />
delivers:
itemA.Metadata1 = m_data1 ***<-- correctly updated***
itemA.Metadata2 = default ***<-- why showing the default value, not* m_data1??**
how can I make itemA.Metadata2 to have the same value as itemA.Metadata1 after it has been updated?
I think this is not possible because order of evaluation Item Definitions - Value Sources - Note:
Item metadata from an ItemGroup is not useful in an ItemDefinitionGroup metadata declaration because ItemDefinitionGroup elements are processed before ItemGroup elements.
You have to override itemA's Metadata2 value in ItemGroup
<ItemDefinitionGroup>
<itemA>
<Metadata1>default</Metadata1>
<Metadata2>%(Metadata1)</Metadata2>
</itemA>
</ItemDefinitionGroup>
<ItemGroup>
<itemA Include="first" >
<Metadata1>m_data1</Metadata1>
<Metadata2>%(Metadata1)</Metadata2>
</itemA>
</ItemGroup>
As palo states, since Metadata2 has already been evaluated, you'll have to explicitly overwrite the value. Your change to Metadata1 won't automatically propagate to other places where it was referenced during initialization.
However, you can "re-evaluate" your items' metadata by starting a new instance of MSBuild and passing the updated metadata in as a property. Running msbuild /t:Wrapper on this project from the command line will result in Metadata1 and Metadata2 printing the same value:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DefaultMetadata1 Condition="DefaultMetadata1==''">default</DefaultMetadata1>
</PropertyGroup>
<ItemDefinitionGroup>
<itemA>
<Metadata1>$(DefaultMetadata1)</Metadata1>
<Metadata2>%(itemA.Metadata1)</Metadata2>
</itemA>
</ItemDefinitionGroup>
<ItemGroup>
<itemA Include="first" >
<Metadata1>m_data1</Metadata1>
</itemA>
</ItemGroup>
<Target Name="Wrapper">
<MSBuild
Projects="$(MSBuildProjectFile)"
Targets="Worker"
Properties="DefaultMetadata1=%(itemA.Metadata1)"
/>
</Target>
<Target Name="Worker">
<Message Text="itemA.Metadata1 = %(itemA.Metadata1)" />
<Message Text="itemA.Metadata2 = %(itemA.Metadata2)" />
</Target>
</Project>
The usefulness of this approach will depend on what you're trying to accomplish. You can undoubtedly find an alternate solution using properties instead of item metadata.
While the solution above works for the case you describe, it can quickly get out of hand. There's probably a more simple solution that may involve some redundant code.
My recommendation would be to use the simple solution and eliminate as much redundancy as you reasonably can without inventing new ways to get around MSBuild's small feature set. Clever tricks here probably won't save you that many LOC at the end of the day and may result in less readable code, making it more difficult for newcomers to understand what's going on.
Can anyone tell me how does the MsBuild picks up the value of the property..??
eg.
<TempProperty>Property Value</TempProperty>
now I can use $(TempProperty) anywhere to get the value of it.
now the scenario is I have made custom task that has configuration like this..
<PropertyGroup>
<ItemList>
<ConfigChange>
<PlaceHolder>#MACHINE_NAME#</PlaceHolder>
<Value>$(TempProperty)</Value>
<IsList>False</IsList>
</ConfigChange>
</ItemList>
</PropertyGroup>
now instead of getting "Property Value" in the tag I am getting $(TempProperty)... can anyone tell me how to get actual value in tag ???
thanks in advance.
Hey, Guys I have got the actual Problem and solution too... I think I was not able to describe my problem here.. .the problem was.. In my custom task I was passing the file path of the file containing the above ItemList tag..now when I tried to parse the XMLNode "ItemList" it was getting "$(TempProperty)" as value in the Item.. and I think thats correct because thats what is present in the passed XML.
So to overcome the issue , I did two things..
I created ItemGroup instead of propertyGroup and passed that ItemGroup to my custom task instead of file path.. thus now at my code I am getting the desired values.
thanks for your replies.
There's not enough there to really diagnose your problem. It looks correct at first glance, but where is the PropertyGroup for TempProperty declared?
For an illustrative example, here's a snippet from a C# project file:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
...
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
...
</PropertyGroup>
The Configuration Property is being set to Debug (if it is blank at at that point). In the following group, it keys off of the Confuration and Platform properties.
The only gotcha tidbit is that for a property value to show up properly, it must be declared before it is used.
Please try to provide a little more context, that may help with understanding the issue.
I just tried this:
<Project
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
ToolsVersion="4.0"
DefaultTargets="Demo" >
<PropertyGroup>
<TempProperty>property value</TempProperty>
<ItemList>
<ConfigChange>
<PlaceHolder>#MACHINE_NAME#</PlaceHolder>
<Value>$(TempProperty)</Value>
<IsList>False</IsList>
</ConfigChange>
</ItemList>
</PropertyGroup>
<Target Name="Demo">
<Message Text="TempProperty: $(TempProperty)"/>
<Message Text="ItemList: $(ItemList)"/>
</Target>
</Project>
And my results where:
Task "Message"
TempProperty: property value
Done executing task "Message".
Task "Message"
ItemList:
<ConfigChange xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PlaceHolder>#MACHINE_NAME#</PlaceHolder>
<Value>property value</Value>
<IsList>False</IsList>
</ConfigChange>
Done executing task "Message".
Are you seeing something else?
on Ritch's suggestion I am adding my solution.
Initially I had
<TempProperty>Property Value</TempProperty>
<PropertyGroup>
<ItemList>
<ConfigChange>
<PlaceHolder>#MACHINE_NAME#</PlaceHolder>
<Value>$(TempProperty)</Value>
<IsList>False</IsList>
</ConfigChange>
</ItemList>
</PropertyGroup>
now my problem was I was providing my properties file part to my custom task
like
<UpdatePegasusConfigXML
Environment="$(Environment)"
Instance="$(Instance)"
BuildSourceRoot="$(BuildSourceRoot)"
></UpdatePegasusConfigXML>
now since I was providing file path itself so it took the value which was put in "Value" tag and was not picking up the property value, thus at code level I was getting "$(TempProperty)" instead of "Property Value"
now what I did was instead of creating property Group I created ItemGroup like this
<ItemGroup>
<PlaceHolders Include="#MACHINE_NAME#">
<Value>$(TempProperty)</Value>
<IsList>True</IsList>
</PlaceHolders>
</ItemGroup>
now I updated my custom task to take IteamGroup as one of the inputs, thus calling changed to
<UpdatePegasusConfigXML
Environment="$(Environment)"
Instance="$(Instance)"
BuildSourceRoot="$(BuildSourceRoot)"
PlaceHolders="#(PlaceHolders)"
></UpdatePegasusConfigXML>
now at code level I am able to get the value in "Value" tag of the ItemGroup.
I hope I had explained my solution that it was understandable.