So ... I have build.xml that loads property file from basedir.
Then, as the target I perform the following:
<var name="Var1" value="<property_from_**first**_loaded_property_file>" />
<var name="<property_from_**first**_loaded_property_file>" unset="true"/>
<property file="../<other directory>/<**second**_property_file>.properties" />
<var name="Var2" value="<property_from_**second**_loaded_property_file>"/>
The ceavat here is that both has same property name. It cannot be changed.
So, in the end, I should get the property like:
Var1=<property_from_**first**_loaded_property_file>
Var2=<property_from_**second**_loaded_property_file>
But instead -
I am getting signs that property (Var1) from first properties file is not unset and then filled with new value from second properties file. The thing that ant-contribs unset should deal with :/ ... something like:
Var1 = Var2
Why I am not getting the expected result?
I think the issue is that even though you're loading the variable into an antcontrib var, it's still an ant property first, thus immutable.
I know you can't change the property files, but what kind of freedom do you have with the script itself? You can try to leverage the scoping rules and the antcallback task to scope where the variables get loaded.
For example, the following achieves - albeit somewhat messily - what I think you're after:
<?xml version="1.0" encoding="utf-8"?>
<project name="Test" basedir=".">
<path id="ant.classpath">
<fileset dir="${basedir}">
<include name="ant-contrib_AP.jar"/>
</fileset>
</path>
<taskdef resource="net/sf/antcontrib/antcontrib.properties" classpathref="ant.classpath"/>
<target name="test">
<antcallback target="load-more-prop" return="Var2"/>
<loadproperties>
<file file="prop1.properties"/>
</loadproperties>
<property name="Var1" value="${var}" />
<echo>${Var1}</echo>
<echo>${Var2}</echo>
</target>
<target name="load-more-prop">
<loadproperties>
<file file="prop2.properties"/>
</loadproperties>
<property name="Var2" value="${var}" />
</target>
</project>
In my console I see:
Buildfile: C:\Users\mfelzani\workspace-junk\junk\build.xml
test:
load-more-prop:
[echo] 7
[echo] 1
BUILD SUCCESSFUL
Total time: 905 milliseconds
Which matches the values i set in prop1.properties and prop2.properties, respectively, for the var property.
You can't unset the value.
WRONG: <var name="<property_from_**first**_loaded_property_file>" unset="true"/>
You have to unset the variable
CORRECT: <var name="Var1" unset="true"/>
If you need to overwrite some existing property or userproperty (those properties defined via ant commandline parameter -Dkey=value), you might use Ant Plugin Flaka as alternative for antcontrib.
With Flaka's let task you either create a new property or overwrite any existing property straightforward :
<project xmlns:fl="antlib:it.haefelinger.flaka">
<property name="foo" value="bar"/>
<!-- create new property -->
<fl:let>foo := 'baar'</fl:let>
<echo>$${foo} => ${foo}</echo>
<!--
overwrite existing property
notice the double '::' in foo ::= 'baz'
-->
<fl:let>foo ::= 'baz'</fl:let>
<echo>$${foo} => ${foo}</echo>
</project>
Related
For a C++ project, I want to autogenerate a defs.h file with project definitions, such as the date, git commit, ... to automate the versioning process of my application.
Therefore I am trying to create a MSBuild Target that will extract the latest git tag, git commit, and the current date and save it to a temporary gitinfo.txt file.
Another build target will depend on that file and generate a .h file.
In order to avoid unnecessary recompiles of my project, the .h file and for that reason the gitinfo.txt file shall only be rewritten, if any of the information has changes.
So my idea is the following:
Calculate git and date info
If available, read in the existing gitinfo.txt
Compare the calculated values to those in the txt file
If anything has changed, rewrite the gitinfo.txt
I've mastered steps 1. and 2., however I am not sure how to process the values after reading them.
<!-- The purpose of this target is to update gitinfo.txt if git information (commit...) has changed -->
<Target
Name="GetHeaderInfos"
BeforeTargets="ClCompile"
Outputs="$(IntDir)\gitinfo.txt"
>
<!-- Get information about the state of this repo-->
<GitDescribe>
<Output TaskParameter="Tag" PropertyName="NewGitTag" />
<Output TaskParameter="CommitHash" PropertyName="NewGitCommitHash" />
<Output TaskParameter="CommitCount" PropertyName="NewGitCommitCount" />
</GitDescribe>
<!-- Get the current date -->
<Time Format="dd.MM.yyyy">
<Output TaskParameter="FormattedTime" PropertyName="NewBuildDate" />
</Time>
<ReadLinesFromFile File="$(IntDir)\gitinfo.txt" Condition="Exists('$(IntDir)\gitinfo.txt')">
<Output TaskParameter="Lines" ItemName="Version" />
</ReadLinesFromFile>
<!-- Comparison here! HOW TO DO IT PROPERLY -->
<PropertyGroup>
<TagChanged> <!-- `$(NewGitTag)` == `$(Version)[0]` --> </TagChanged>
<!-- Other comparisons -->
</PropertyGroup>
</Target>
And this could be the content of gitinfo.txt
v4.1.4
04fe34ab
1
31.07.2016
I am not quite sure how to compare the values now. I need to compare $(NewGitTag) to the first value in the $(Version) version variable, and so on.
I haven't found an example, that actually accesses the variables after reading them from a file. The official documentation provides no help, nor have I found anything on stackoverflow or the likes.
I only know that the $(Version) variable holds a list, and I can batch process it. How can I compare its content to the defined variables $(NewGitTag), $(NewGitCommitHash), $(NewGitCommitCount) and $(NewBuildDate)?
Suppose we start with this data:
<ItemGroup>
<Version Include="v4.1.4;04fe34ab;1;31.07.2016"/>
</ItemGroup>
<PropertyGroup>
<GitTag>v4.1.4</GitTag>
<GitSHA>04fe34ab</GitSHA>
<Count>1</Count>
<Date>31.07.2016</Date>
</PropertyGroup>
Then here are at least 3 ways to achieve comparision (apart from the one mentioned in the comment) and there are probably other ways as well (I'll post them if I can come up with something else):
Just compare the items
I'm not sure why you want to compare everything seperately when this works just as well: compare the whole ItemGroup at once.
<Target Name="Compare1">
<PropertyGroup>
<VersionChanged>True</VersionChanged>
<VersionChanged Condition="'#(Version)' == '$(GitTag);$(GitSHA);$(Count);$(Date)'">False</VersionChanged>
</PropertyGroup>
<Message Text="VersionChanged = $(VersionChanged)" />
</Target>
Batch and check if there's one difference
Each item of Version is compared with e.g. GitTag via batching. The result will be False;False;False;False if there's a difference, else it will be True;False;False;False. Count the distinct elements and if it's 2 it means we got the latter so GitTag did not change. Note this obviousle only works if each of your source items can never have the same value as one of the other items.
<Target Name="Compare2">
<PropertyGroup>
<TagChanged>True</TagChanged>
<TagChanged Condition="'#(Version->Contains($(GitTag))->Distinct()->Count())' == '2'">False</TagChanged>
</PropertyGroup>
<Message Text="TagChanged = $(TagChanged)" />
</Target>
you can then compare the other items as well and combine the result.
Use an inline task to access items by index
This comes closest to what's in your question, but it does need a bit of inline code.
<UsingTask TaskName="IndexItemGroup" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
<ParameterGroup>
<Items Required="true" ParameterType="Microsoft.Build.Framework.ITaskItem[]"/>
<Index Required="true" ParameterType="System.Int32"/>
<Item Output="true" ParameterType="Microsoft.Build.Framework.ITaskItem"/>
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
<![CDATA[Item = Items[ Index ];]]>
</Code>
</Task>
</UsingTask>
<Target Name="Compare3">
<IndexItemGroup Items="#(Version)" Index="1">
<Output PropertyName="OldGitSHA" TaskParameter="Item"/>
</IndexItemGroup>
<PropertyGroup>
<SHAChanged>True</SHAChanged>
<SHAChanged Condition="'$(GitSHA)' == '$(OldGitSHA)'">False</SHAChanged>
</PropertyGroup>
<Message Text="OldGitSHA = $(OldGitSHA), changed = $(SHAChanged)" />
</Target>
Is it possible using default MSBuild technology to access a listing within an item group as a property in msbuild? I know I can do this in a custom task in C#, but I am trying to use built-in capabilities if possible.
Example:
I have an item group:
<ItemGroup>
<SolutionToBuild Include="$(SolutionRoot)\Solutions\ClassLib\ClassLib.sln">
<Properties>
AssemblySigningKey=MySigningKey;
OutDir=$(BinariesRoot)\SomeLocation\;
LibraryName=ClassLib;
PlatformTarget=x86;
</Properties>
</SolutionToBuild>
<SolutionToBuild Include="$(SolutionRoot)\Solutions\BLAH\BLAH.sln">
<Properties>
ProjectType=Web;
</Properties>
</SolutionToBuild>
</ItemGroup>
I would like to extract the value of AssemblySigningKey, if it exists, and place this value into an MSBuild variable.
I have tried a few methods and the closest example I could find is using a tranformation within a separate target, but even this looks to be a bit of a hack, even if I could get the Condition to work I would then have to parse out the value splitting on the =. Is there no standard method to access this metadata within the item group?
<Target Name="TransformProps"
Inputs="%(SolutionToBuild.Identity)"
Outputs="_Non_Existent_Item_To_Batch_">
<PropertyGroup>
<IncludeProps>%(SolutionToBuild.Properties)</IncludeProps>
</PropertyGroup>
<ItemGroup>
<IncludeProps Include="$(IncludeProps)" />
<Solution Include="#(SolutionToBuild)">
<IncludeProps Condition="'True'=='True' ">#(IncludeProps ->'-PROP %(Identity)', ' ')</IncludeProps>
</Solution>
</ItemGroup>
</Target>
My main target would call into the tranform in the following manner:
<Target Name="Main" DependsOnTargets="TransformProps">
<Message Text="Solution info: %(Solution.Identity) %(Solution.IncludeProps)" />
</Target>
Items Metadata are declared and transformed using xml tags. It seems like you're using the MSBuild Task to build some solutions - the properties tag is a parameter specific to this task.
The conversion from comma separated list and items as you tried won´t help because, as you mentioned, you still have the equal sign as the link from the keys to the values. I think there´s no way of obtaining the signing key value without parsing. After all msbuild do not consider the list of properties as metadata, it is just a list of strings.
I did the script below to exemplify how msbuild declare and read metadata. It is not an option for you because your ItemGroup structure cannot be changed.
IMHO in this case you have no option but use a custom task and do the parsing. Use Inline Tasks if you´re building with msbuild 4.0.
<?xml version="1.0" encoding="UTF-8" ?>
<Project DefaultTargets="Main" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<SolutionToBuild Include="$(SolutionRoot)\Solutions\ClassLib\ClassLib.sln">
<AssemblySigningKey>MySigningKey123</AssemblySigningKey>
<Properties>
AssemblySigningKey=MySigningKey456;
OutDir=$(BinariesRoot)\SomeLocation\;
LibraryName=ClassLib;
PlatformTarget=x86;
</Properties>
</SolutionToBuild>
</ItemGroup>
<Target Name="TransformProps">
<PropertyGroup>
<MySigningKey>#(SolutionToBuild->'%(AssemblySigningKey)')</MySigningKey>
</PropertyGroup>
</Target>
<Target Name="Main" DependsOnTargets="TransformProps">
<Message Text="My desired Property Value: $(MySigningKey)" />
</Target>
For my team city configuration, I have a build parameter with a potential preprocessor macro definition. It's a checkbox with this value: /p:DefineConstants=IncludeFleetSimulation . It has that value so that I can easily add that into my MSBuild parameters, obviously. However, I also want to change the output filename. I don't want /p:... in the filename. Instead, I want "IFS". Is there some way to insert something conditionally with the TeamCity parameter parsing? I'm picturing something like this: %IFS% != "" ? "_IFS" : ""% . How do I achieve that in TeamCity?
I ended up writing a Nant script step to make this happen:
<project name="RenameFilesBasedOnParamters" default="Rename">
<target name="Rename">
<property name="IncludeFleetSimulation" value="%IncludeFleetSimulation%" />
<property name="currentDirectory" value="%system.teamcity.build.workingDir%" />
<if test="${IncludeFleetSimulation != ''}">
<foreach item="File" property="file">
<in>
<items basedir="${currentDirectory}">
<include name="*%build.number%_%build.vcs.number%*" />
</items>
</in>
<do>
<move file="${file}"
tofile="${currentDirectory + '\' +
path::get-file-name-without-extension(file) +
'_FS' + path::get-extension(file)}" />
</do>
</foreach>
</if>
...
I can't figure out how to pass values into an MSBuild task like I would a method. Take the following project file...
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTargets="Main">
<PropertyGroup>
<Var1>Foo</Var1>
<Var2>Bar</Var2>
</PropertyGroup>
<Target Name="Main">
<Message Text="$(Var1)" Importance="high" />
<Message Text="$(Var2)" Importance="high" />
</Target>
</Project>
I want to refactor the Message task into a target and then pass over Var1 and Var2 to it to get the same output. This is a very simplified example but the concept is the same.
I think you want to do something like this:
<ItemGroup>
<Messages Include="Message1">
<Text>Hello from Message1</Text>
</Messages>
<Messages Include="Message2">
<Text>Hello from Message2</Text>
</Messages>
</ItemGroup>
<Target Name="TestMessage">
<Message Text="%(Messages.Text)"/>
</Target>
This produces the following output:
TestMessage:
Hello from Message1
Hello from Message2
This is meant to complement, not replace, #BryanJ’s answer.
There are two types of batching. One is Task batching which happens when you use %(ItemName.MetaData) syntax. You just specify this value into a task parameter as if %(ItemName.MetaData) would only ever expand to one particular value. MSBuild then automatically executes the task multiple times, avoiding the need for the task to explicitly support iterating over a list of items.
Another batching type is Target batching. Target batching happens when you use the Inputs and Outputs attributes of <Target/>. To batch over an arbitrary set of Items in such a way that the target gets executed exactly once per Item, you can specify Inputs="#(ItemName)" Outputs=%(Identity).bogus. What’s important is that %(Identity) is present. Batching will look at all the possible expansions of Inputs and Outputs and decide its batching based on these expansions. Thus, you must make sure that each item gets a unique expansion if you want the Target to run separately for each item. I give #BryanJ’s code with modifications to use Target batching of this style:
<?xml version="1.0"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTargets="all">
<ItemGroup>
<Messages Include="Message1">
<Text>Hello from Message1</Text>
<Group>1</Group>
</Messages>
<Messages Include="Message2">
<Text>Hello from Message2</Text>
<Group>1</Group>
</Messages>
<Messages Include="Message3">
<Text>Hello from Message3</Text>
<Group>2</Group>
</Messages>
</ItemGroup>
<Target Name="all" DependsOnTargets="TestMessage;TestMessageGrouping" />
<!--
Use the Inputs/Outputs attributes to specify Target
batching. The metadata value I am batching over is
Identity. Since Identity is unique per item, this means the
Target will get run in full once for every value in
Messages. We provide something bogus for Outputs. It is
important that our bogus values do not coincide with real
filenames. If MSBuild finds a file with the name of a value
in Outputs and another file, with an older timestamp,
matching the corresponding value in Inputs, it will skip
running this Target. (This is useful in many situations, but
not when we want to just print out messages!)
-->
<Target Name="TestMessage" Inputs="#(Messages)" Outputs="%(Identity).bogus">
<Message Text="I will print the Text metadata property of %(Messages.Identity)" />
<Message Text="%(Messages.Text)" />
</Target>
<!--
If you want to combine Task and Target batching, you can specify
a different metadata value than Identity to group the items
by. I use the Group metadata I specified in the ItemGroup.
-->
<Target Name="TestMessageGrouping" Inputs="#(Messages)" Outputs="%(Group).bogus">
<Message Text="I will print the Text metadata property of all messages from Group %(Messages.Group)" />
<!--
Now, within the Target batch, we use Task batching to print
all of the messages in our %(Messages.Group) at once.
-->
<Message Text="%(Messages.Text)" />
</Target>
</Project>
with output:
TestMessage:
I will print the Text metadata property of Message1
Hello from Message1
TestMessage:
I will print the Text metadata property of Message2
Hello from Message2
TestMessage:
I will print the Text metadata property of Message3
Hello from Message3
TestMessageGrouping:
I will print the Text metadata property of all messages from Group 1
Hello from Message1
Hello from Message2
TestMessageGrouping:
I will print the Text metadata property of all messages from Group 2
Hello from Message3
I am constructing a hierarchy of property sheets (many that are conditionally included according to Platform and Configuration) and I am attempting to write a set of targets that can help diagnose any errors that may sneak in.
What I would like is a list of property sheets that have been included.
Example:
<ImportGroup Condition="$(Configuration.Contains('Debug'))">
<Import Project="ps.cpp.config.debug.props"/>
</ImportGroup>
<ImportGroup Condition="$(Configuration.Contains('Release'))">
<Import Project="ps.cpp.config.release.props"/>
</ImportGroup>
<ImportGroup Condition="'$(Platform)' == 'x64'">
<Import Project="ps.cpp.plat.x64.props"/>
</ImportGroup>
<ImportGroup Condition="'$(Platform)' == 'Win32'">
<Import Project="ps.cpp.plat.win32.props"/>
</ImportGroup>
And a target like this:
<Target Name="DumpPropertySheets">
<!-- This doesn't work! -->
<!-- <Message Text="%(Import.Project)"/> -->
</Target>
Which should result in console output like this when built with msbuild test.vcxproj /t:DumpPropertySheets /p:Platform=x64 /p:Configuration:Debug
DumpPropertySheets:
ps.cpp.config.debug.props
ps.cpp.plat.x64.props
There is not an obvious way to do what you are trying to do. Imports are pre-processed to aggregate all of the content into a single file, they are not a datatype like item arrays or properties that can be referenced later on.
The syntax %(Import.Project) doesn't work because that syntax is valid only for item arrays, and you are trying to use it on the Import keyword, which is not a populated item array.
Also note that your use of the ImportGroup elements surrounding the imports is optional (and probably a bit verbose). The following two constructs in an MSBuild file are equivalent...
<ImportGroup Condition="$(Configuration.Contains('Debug'))">
<Import Project="ps.cpp.config.debug.props"/>
</ImportGroup>
...and (line-wrapped for clarity)...
<Import
Condition="$(Configuration.Contains('Debug'))"
Project="ps.cpp.config.debug.props"
/>
If you are trying to diagnose property sheet import errors, don't forget about the /pp command-line switch, which will dump the complete preprocessed file. You also could (for your own files at least) give each import a unique entry into an item array, e.g.
<Import
Condition="$(Configuration.Contains('Debug'))"
Project="ps.cpp.config.debug.props"
/>
then inside ps.cpp.config.debug.props,
<ItemGroup>
<_Import Include="$(MSBuildThisFile)" />
</ItemGroup>
then later in your build you could get what you appear to be looking for, to some degree, with,
<Target Name="DumpPropertySheets">
<!-- This does work! -->
<Message Text="%(_Import.Identity)" />
</Target>