When to Use Transforms vs Just Item Metadata
I'm having a bit of a problem when building my targets understanding when to use a transform vs just using item meta data. For example, given this target:
| |
<Target Name="Backup" Inputs="@(OutputFiles)" Outputs="@(OutputFiles->'%(filename).bak')"> <Exec Command="copy @(OutputFiles->'%(identity)') %(OutputFiles.filename).bak" /> <Exec Command="copy %(OutputFiles.identity) @(OutputFiles->'%(filename).bak2')" /> </Target>
|
Both of the copy commands see to work the same even though they interchange use of a transform like @(OutputFiles->'%(filename).bak2') and use of item metadata without a transform e.g. %(OutputFiles.filename).bak. I guess I'm wondering why I need transforms at all?
As another example, the help on transforms shows this:
| |
<Target Name="Messages"> <Message Text="rootdir: @(schema->'%(rootdir)')"/> ... </Target>
|
It seems like this is clearer and simpler:
| |
<Target Name="Messages"> <Message Text="rootdir: %(schema.rootdir)"/> ... </Target>
|
Transforms turn a vector into a scalar. Bare metadata retains vector semantics.
I realize in your example, they seem to do the same thing... here's the reason.
When we see a task invocation with bare metadata (like %(OutputFiles.filename)) we bucketize the relevant item lists. Each bucket contains a set of items from the item list/s that have identical values for the referenced metadata. Then we run the task once on each bucket.
In your case, each member of @(OutputFiles) has a different value for %(filename) (or %(Identity) in your second Exec). So we run the task once for each item in the list. When we run the task, we feed it just those items in the bucket. We evaluate the macro transform @(OutputFiles->'%(identity)') now. Each time around, it only contains one item.
You could equally well have written this
<Exec Command="copy %(OutputFiles.identity) %(OutputFiles.filename).bak2" />If the metadata referenced isn't unique on each of the items, you'll see a difference between transforms and bare metadata. Let's say you have 3 files each with the same value of %(Culture). Then passing a task
Sources="@(MyFiles)" Culture="%(Culture)"
will get it passed the files one at a time (3 culture buckets - say "eng", "fre", "deu"). But writingSources="@(MyFiles)" Culture="@(MyFiles->'%(Culture)')"
will invoke the task once, passing in "eng;fre;deu" for the culture.So, sometimes you want transforms, and sometimes not and it depends on whether you want vector or scalar semantics.
Dan
Thanks for that explanation. If something like that isn't in the docs (I don't think it is) then it should be. I created this example which helped me to understand this and it might help others, so I'll post it here. I did find it interesting that item collections are treated like scalars in tasks at least in terms of how many times the task gets invoked:
| |
<Project DefaultTargets="Exp"> <ItemGroup> <Files Include="foo.cs;bar.cs;baz.cs"> <Culture>en-US</Culture> </Files> <Files2 Include="foo2.cs;bar2.cs"> <Culture>en-US</Culture> </Files2> <Files2 Include="baz2.cs"> <Culture>de-AT</Culture> </Files2> </ItemGroup> <Target Name="Exp"> <Message Text="1. @_(Files) is a scalar: @(Files)" /> <Message Text="2. @_(Files2) is a scalar: @(Files2)" /> <Message Text="3. @_(Files->'%_(Filename).bk') is a scalar: @(Files->'%(Filename).bk')" /> <Message Text="4. %_(Files.Identity) is a vector: %(Files.Identity)" /> <Message Text="5. @_(Files2->'%_(Culture)') is a scalar: @(Files2->'%(Culture)')" /> <Message Text="6. %_(Files2.Culture) is a vector: %(Files2.Culture)" /> <Message Text="7. @_(Files)/%_(Culture) is a vector: @(Files)/%(Culture)" /> <Message Text="8. @_(Files2)/%_(Culture) is a vector: @(Files2)/%(Culture)" /> </Target> </Project>
|
This produces the following output:
| |
Target Exp: 1. @_(Files) is a scalar: foo.cs;bar.cs;baz.cs 2. @_(Files2) is a scalar: foo2.cs;bar2.cs;baz2.cs 3. @_(Files->'%_(Filename).bk') is a scalar: foo.bk;bar.bk;baz.bk 4. %_(Files.Identity) is a vector: foo.cs 4. %_(Files.Identity) is a vector: bar.cs 4. %_(Files.Identity) is a vector: baz.cs 5. @_(Files2->'%_(Culture)') is a scalar: en-US;en-US;de-AT 6. %_(Files2.Culture) is a vector: en-US 6. %_(Files2.Culture) is a vector: de-AT 7. @_(Files)/%_(Culture) is a vector: foo.cs;bar.cs;baz.cs/en-US 8. @_(Files2)/%_(Culture) is a vector: foo2.cs;bar2.cs/en-US 8. @_(Files2)/%_(Culture) is a vector: baz2.cs/de-AT
|
BTW, is there a way to escape processing of item collections and properties in the Message task rather than inserting bogus characters (like the _)?
I missed your last question.
Yes, you can just escape one of the characters, instead of this
<Message Text="@(stuff)" />
<Message Text="%40(stuff)" />
0x40 is hex for ascii value of @. This may or may not work in the build you have though. It will in the current community tech preview.
Hmm, why not use the standard XML character reference mechanism e.g.:
<Message Text="@(stuff)">
Because that's transparent to us. When we read the content from the DOM, it's already unescaped things like @ because they've served their purpose to the XML reader code at that point.
%nn is a different kind of escaping: instead of preventing the XML parser getting confused, it's to prevent the subsequent MSBuild parser getting confused. That's why we have a different kind of escaping. We fairly arbitrarily picked the same system as you see in URLs.
Dan