tags:

views:

44

answers:

2

i'm taking a list of files *.config and copying them to a list of directories. The directories are relative to a path C:\branches\ have a name and then the name.UnitTest.

so the copy looks like this without being refactored/batched:

<Target Name="CopyClientConfigs">

<ItemGroup>
    <ClientConfigDestinations Include="$(LocalSourcePath)\Module1\Module1.UnitTest\;
    $(LocalSourcePath)\Module2\Module2.UnitTest\;
    $(LocalSourcePath)\CommonControls\Module3\Module3.UnitTest\;
    $(LocalSourcePath)\Administration\Module4\Module4.UnitTest\;
    $(LocalSourcePath)\IndividualControls\Configuration\Module5\Module5.UnitTest\" />
    <ClientConfigs
    Include="$(ClientConfigPath)\*.config"
    Exclude="$(ClientConfigPath)\P*.config" >
    </ClientConfigs>
</ItemGroup>
<Copy
        SourceFiles="@(ClientConfigs)"
        DestinationFolder="%(ClientConfigDestinations.FullPath)"
        />

What I want is to be able to use this ItemGroup

<ItemGroup>
<MyModules Include="$(LocalSourcePath)\Module1;
    $(LocalSourcePath)\Module2;
    $(LocalSourcePath)\CommonControls\Module3;
    $(LocalSourcePath)\Administration\Module4;
    $(LocalSourcePath)\IndividualControls\Configuration\Module5"
/>          

So the task would be like

Copy
        SourceFiles="@(ClientConfigs)"
        DestinationFolder="%(ClientConfigDestinations.FullPath)\*.UnitTest\"
        />

Or better

Copy
        SourceFiles="@(ClientConfigs)"
        DestinationFolder="%(ClientConfigDestinations.FullPath)\%(ClientConfigDestinations.NameOnly).UnitTest\"
        />

How do I refactor or properly batch this operation?

+4  A: 

If I read your question right, I think you are trying to do a cross-product copy: copy all items in one ItemGroup to all the folders in a different group.

I actually have a neat target that I use to do this, as I hate the way TeamBuild puts all the binaries into a single folder - I want projects to be able to specify that their output is a "bundle" and that the output will also be copied to one or more locations.

To do this, I have two itemgroups: BundleFiles (which is the set of files that I want to copy) and BundleFolders which are the set of folders that I want to copy to.

<ItemGroup>
    <BundleOutDir Include="FirstFolder;SecondFolder" />
    <BundleFiles Include="file1;file2" />
</ItemGroup>

My target then contains two tasks like this:

<ItemGroup>
    <FilesByDirsCrossProduct Include="@(BundleFiles)">
        <BundleOutDir>%(BundleOutDir.FullPath)</BundleOutDir>
    </FilesByDirsCrossProduct>
</ItemGroup>

This creates an uber item group containing a cross product of files by folders.

The copy is then pretty simple:

<Copy SourceFiles="@(FilesByDirsCrossProduct)"
    DestinationFiles="@(FilesByDirsCrossProduct -> '%(BundleOutDir)\%(Filename)%(Extension)' ) "
    SkipUnchangedFiles="true" />

This then copies the files to the folder specified within their meta data.

My target is actually a little more clever in that I can declare that bundles will go to sub folders and/or I can rename a file during the copy through meta data, but that's a different story

Peter McEvoy
+1 this looks pretty close, but I need my destination folder to be a subfolder... `FirstFolder\FirstFolder.UnitTestFolder` and I don't think `%(BundleOutDir)\*.UnitTestFolder\%(Filename)%(Extension)` will function
Maslow
yeah I get illegal characters in path when I try it that way.
Maslow
Interesting approach for copying a set of files to a set of locations. I like it.
Sayed Ibrahim Hashimi
Hmm - Should be simple to do in the copy. You don't need a "*" as you are only working with a single item (when you see %(item) think of a "foreach" loop doing a single action on an item)DestinationFiles="@(FilesByDirsCrossProduct -> '%(BundleOutDir)\%(BundleOutDir).UnitTestFolder\%(Filename)%(Extension)' ) "
Peter McEvoy
@Peter, if when you said a single item you mean the configs item, no I'm copying 3 config files. If you meant the target, i'm having trouble following your comment. Does it still apply having seen my answer?
Maslow
@Maslow: Y - I think we are confusing each other - i was responding to your "+1 this looks pretty close.." comment where I saw you write "%(BundleOutDir)*.UnitTestFolder" - I was trying to explain how I think of a statement that contains %(xyz) - it does work on the entire set of "xyz" things - but it passes each item into the task one at a time. When I see @(xyz) in a task, it helps me to think that its like passing an array into the task
Peter McEvoy
A: 
<Target Name="CopyClientConfigsBatched" Outputs="%(MyModules.FullPath)">
    <Message Text="@(MyModules -> '%(FullPath)\%(FileName).UnitTest')"/>
  <ItemGroup>
    <ClientConfigs
    Include="$(ClientConfigPath)\*.config"
    Exclude="$(ClientConfigPath)\P*.config" >
    </ClientConfigs>
  </ItemGroup>
  <Copy SourceFiles="@(ClientConfigs)" DestinationFolder="@(MyModules -> '%(FullPath)\%(FileName).UnitTest')"
     SkipUnchangedFiles="true"/>
</Target>

target batching seems to have done it!

Maslow
Similar to this you should see http://stackoverflow.com/questions/3701177/how-to-always-execute-a-target-in-msbuild/3714585#3714585.
Sayed Ibrahim Hashimi
@Sayed thanks, interesting link. I don't think this will apply in my situation, as my team does not seem to embrace/allow any customizing of the project files, so the .proj code here is in a project that doesn't actually do any building, just refreshes my environment from the master.
Maslow
OK, yeah was just pointing out the related content.
Sayed Ibrahim Hashimi