3

I'm trying to create a reusable Target in msbuild, following the basic model outlined in How to invoke the same msbuild target twice?

I'm stuck trying to pass a property that I want interpreted as a list. I haven't found an example online that deals with this situation. As I understand it, the problem is that Properties is already treated as a list item, so it doesn't like having a list item passed in as well. Is there a way to get msbuild to pack and unpack the list correctly here?

Msbuild is complaining with:

error MSB4012: The expression "FilesToZip=@(Scripts)" cannot be used in this context. Item lists cannot be concatenated with other strings where an item list is expected. Use a semicolon to separate multiple item lists.

Here's an example caller:

<Target Name="BuildMigrationZip">

   <MSBuild Projects="BuildZip.msbuild"
      Targets="BuildZip"
      Properties="FilesToZip=@(Scripts);OutputZipFile=$(MigrationPackageFilePath);OutputFolder=$(MigrationPackagePath);Flatten=true"/>

  <Message Text="Created database migration zip: $(MigrationPackageFilePath)" Importance="high"/>

</Target>

And the base target:

<Target Name="BuildZip">

  <MakeDir Directories="$(OutputFolder)"/>

  <Zip Files="@(FilesToZip)" 
  ZipFileName="$(OutputZipFile)"
  Flatten="$(Flatten)"
  ParallelCompression="false" />

</Target>

I'm basically at the point of just going back to cut and paste for these, although I want to package up a number of zips here.

UPDATE: The same issue applies to setting Inputs on the reusable target. My question up to this point addresses the raw functionality, but it would be nice to keep dependencies working. So for example:

<Target Name="BuildZip"
   Inputs="@(FilesToZip)"
   Outputs="$(OutputZipFile)">

  <MakeDir Directories="$(OutputFolder)"/>

  <Zip Files="@(FilesToZip)" 
  ZipFileName="$(OutputZipFile)"
  Flatten="$(Flatten)"
  ParallelCompression="false" />

</Target>
Community
  • 1
  • 1
eddie.sholl
  • 740
  • 1
  • 6
  • 18

1 Answers1

5

They key is to pass the list around as a property. So when your Scripts list is defined as

<ItemGroup>
  <Scripts Include="A"/>
  <Scripts Include="B"/>
  <Scripts Include="C"/>
</ItemGroup>

then you first convert it into a property (which just makes semicolon seperated items, but msbuild knows how to pass this via the Properties of the MSBuild target) then pass it to the target:

<Target Name="BuildMigrationZip">
  <PropertyGroup>
    <ScriptsProperty>@(Scripts)</ScriptsProperty>
  </PropertyGroup>

  <MSBuild Projects="$(MSBuildThisFile)" Targets="BuildZip"
           Properties="FilesToZip=$(ScriptsProperty)" />
</Target>

(note I'm using $(MSBuildThisFile) here: you don't necessarily need to create seperate build files for every single target, in fact for small targets like yours it's much more convenient to put it in the same file)

Then in your destination target you turn the property into a list again:

<Target Name="BuildZip">
  <ItemGroup>
    <FilesToZipList Include="$(FilesToZip)"/>
  </ItemGroup>
  <Message Text="BuildZip: @(FilesToZipList)" />
</Target>

Output:

BuildZip: A;B;C

Update

When working with Inputs, you cannot pass @(FilesToZip) since that expands to nothing because is not a list: it's a property - which happens to be a number of semicolon-seperated strings. And as such, it is usable for Inputs you just have to expand it as the property it is i.e. $(FilesToZip):

<Target Name="BuildZip"
        Inputs="$(FilesToZip)"
        Outputs="$(OutputZipFile)">
  ...
</Target>

Output of second run:

BuildZip:
Skipping target "BuildZip" because all output files are up-to-date with respect to the input files.
stijn
  • 34,664
  • 13
  • 111
  • 163
  • That's the ticket :) Thanks so much for the advice there. With all that packing and unpacking of lists, I think we end up with roughly the same amount of XML whether its just copy and paste zip targets, or a reusable target. But you've definitely got my answer here. I don't think that will work for the Inputs of my target, I don't think the expansion occurs in time. Any ideas on solving that side of it? – eddie.sholl Jul 07 '14 at 10:40
  • *I don't think that will work for the Inputs of my target, I don't think the expansion occurs in time* I don't understand your question - there is no difference between the targets you posted and the one I used, except I don't Zip/MakeDirectory etc - what is the problem exactly? – stijn Jul 08 '14 at 07:33
  • 1
    On the copy/paste issue I can be clear though: quality of code is not measured by the amount of XML and you will not find any major programming principle (DRY and SRP apply perfectly here) that will promote using copy/paste over anything else. With reason. Sure your BuildZip target looks small, but what if you copy/paste it mutliple times and suddenly need to change the implementation? Yes, that's a mess, cause you'll be forced to change it in multiple places.. – stijn Jul 08 '14 at 07:38
  • Just updated question to clarify on Inputs. Your suggestion is working fine for generating my zips, but if I try to set up dependencies (Inputs) they don't seem to benefit from the list unpacking, and end up as an empty list. This is a nice to have and I'll probably go with what I've got so far anyway. Thanks! – eddie.sholl Jul 09 '14 at 00:41