views:

138

answers:

1

I have been trying to make a task in my TFS builds more generic, and one of the things I am trying to do is copy some files to different directories depending on the build using the task. I toyed with the idea of using properties, but I couldn't think of a way to do that cleanly, so I tried to go with using item metadata, as I've been able to do so in another place in the same target file I'm working on, only this time, I'd like to use properties.

Here's what I want to do:

<ItemGroup>
  <DestinationParent Include="$(DeploymentPath)">
    <DestinationParentPath>$(DeploymentPath)</QuartzParentPath>
  </DestinationParent>
</ItemGroup>

And later in the build, I tried to copy some files to the destination folder by referencing the item metadata:

<Copy SourceFiles="@(FilesToCopy)" DestinationFiles="@(FilesToCopy-&gt;'%(DestinationParentPath)\Destination\%(RecursiveDir)%(Filename)%(Extension)')" ContinueOnError="false" ></Copy>

Unfortunately, after the build runs, my BuildLog shows the following:

Copying file from "$(BinariesRoot)\%(ConfigurationToBuild.FlavorToBuild)\<File being copied>" to "\Destination\<File being copied>".

%(DestinationParentPath) had expanded to an empty string, for whatever reason. Using %(DestinationParent.DestinationParentPath) produced an error, telling me that I should simply be using %(DestinationParentPath). $(DeploymentPath) is expanded to the correct string as expected in several other places in the build.

Another source of confusion is that using %(ConfigurationToBuild.FlavorToBuild) yielded the correct value, i.e. Test, as can be seen in the following:

EDIT: this is defined under the root node Project, whereas the ItemGroup with DestinationParentPath is defined under a Target node. Does this also make a difference?

<ItemGroup>
  <ConfigurationToBuild Include="Test|Any CPU">
    <FlavorToBuild>Test</FlavorToBuild>
    <PlatformToBuild>Any CPU</PlatformToBuild>
  </ConfigurationToBuild>
</ItemGroup>

It does not seem as though the Include attribute is relevant when you're only interested in the string in the item's metadata since I'm pretty sure "Test|Any CPU" does not reference any actual file.

So once again, why is %(DestinationParentPath) expanding to an empty string?

EDIT: I forgot to mention that I also tried hard-coding the actual path for DestinationParentPath, but this still resulted in %(DestinationParentPath) expanding to an empty string.

+1  A: 

EDIT: this is defined under the root node Project, whereas the ItemGroup with DestinationParentPath is defined under a Target node. Does this also make a difference?

Yes, it makes a difference. The ability to define an ItemGroup inside a Target is new to msbuild 3.5. Despite looking declarative, it's actually executed at runtime just as if you'd called the old style CreateItem / CreateProperty tasks. That alone leads to potential issues: you need to consider when the containing task is (first) called. Order of operations is not always obvious to the naked eye. May be wise to make the task where you use %(DestinationParentPath) dependent on the task where it's created, even if there's no "logical" dependency.

In addition, there are the age-old msbuild scoping quirks/bugs. Dynamically created properties & items are not visible to "sibling" tasks. Also, items updated in nested builds aren't always bubbled up.

Check out the workarounds in the links, you should be able to find something that works for you even if it's icky.

Richard Berg
Thanks for your answers and for the links... figured out how to solve my problem :) (and yes, it is a bit icky, but it works quite well nonetheless)One question that still confuses me though: it seems that %(Configuration.FlavorToBuild) I mentioned above is treated as a global... item? Is this why I am able to use it in other parts of the field?
k4k4sh1
The Configuration item is set by Team Build very early in the process so I'm not surprised it works throughout your scripts w/o any extra work. ///// Perhaps "scope" is the wrong word to use. Properties and items are always "global" in msbuild, in the sense that $(foo) always refers to "the same" $(foo) regardless where it appears. C# analogy: imagine there is no namespace keyword and only 1 class definition. As a rule we always pass-by-reference a single instance of this class. First link describes flow control. Latter two describe quirks where a stale copy is passed in/out.
Richard Berg