Search code examples
msbuildmsbuild-targetmsbuild-batching

MsBuild Target with multiple Outputs


Is there a way to overcome Targets one-to-one mapping when you have multiple outputs? Seems like that should be possible, but I cannot find out how, given I'm pretty new to MsBuild I'm probably missing something.

The following piece of msbuild script is from microsoft's documentation. What should I change when I have multiple backup folders? So a list @(BackupFolders) and I would like to keep the incremental behaviour of the build?

<Target Name="Backup" Inputs="@(Compile)"   
    Outputs="@(Compile->'$(BackupFolder)%(Identity).bak')">  
    <Copy SourceFiles="@(Compile)" DestinationFiles=  
        "@(Compile->'$(BackupFolder)%(Identity).bak')" />  
</Target>  

Solution

  • First of all, the Inputs and Outputs attributes on the Target node are for incremental builds. They need to have the same amount of entries in order for msbuild to understand which items should be filtered when building. MSBuild checks if the output is already there and up-to-date, and if so, the matching input item is filtered from the input list. If you don't care for incremental building, you can skip this mechanism altogether. If inputs and outputs don't match (or are not present), msbuild will always execute the target with all items, because it can't decide which items lead to which output.

    Second, what these attributes expect is a list of items. This doesn't have to be one list, it could be an arbitrary list. So it's perfectly fair to extend your example like this:

    <Target Name="Backup" Inputs="@(Compile);@(Compile2)"   
      Outputs="@(Compile->'$(BackupFolder)%(Identity).bak');@(Compile2->'$(BackupFolder)%(Identity).bak')">  
      <Copy SourceFiles="@(Compile)" DestinationFiles=  
         "@(Compile->'$(BackupFolder)%(Identity).bak')" />  
      <Copy SourceFiles="@(Compile2)" DestinationFiles=  
         "@(Compile2->'$(BackupFolder)%(Identity).bak')" />  
    </Target>
    

    But you want to copy the same items to different backup folders, right? So something like this should do:

    <Target Name="Backup">  
      <Copy SourceFiles="@(Compile)" DestinationFiles=  
          "@(Compile->'$(BackupFolder)%(Identity).bak')" />  
      <Copy SourceFiles="@(Compile)" DestinationFiles=  
          "@(Compile->'$(BackupFolder2)%(Identity).bak')" />  
    </Target>
    

    With two backup folders, an item may actually already be up-to-date in one folder, but missing in the other. You could define one as the "main" backup folder, and tell MSBuild to use this as reference for incremental builds.

    Edit: For incremental builds to two locations, probably the easiest solution is to combine two targets, both building incrementally:

    <Target Name="Backup" DependsOnTargets="_Backup1;_Backup2">
    </Target>
    
    <Target Name="_Backup1" Inputs="@(Compile)"   
      Outputs="@(Compile->'$(BackupFolder)%(Identity).bak')">  
      <Copy SourceFiles="@(Compile)" DestinationFiles=  
         "@(Compile->'$(BackupFolder)%(Identity).bak')" />    
    </Target>
    
    <Target Name="_Backup2" Inputs="@(Compile)"   
      Outputs="@(Compile->'$(BackupFolder2)%(Identity).bak')">  
      <Copy SourceFiles="@(Compile)" DestinationFiles=  
         "@(Compile->'$(BackupFolder2)%(Identity).bak')" />  
    </Target>