msbuildmetadatatargetmsbuild-4.0itemgroup

Referencing well-known item metadata inside metadata definition in an ItemGroup in a target


Here's an MSBuild script:

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="AugmentItemGroup" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
 <ItemGroup> 
   <ItmGrp Include="File1.txt">
       <Dest>dest\%(FileName)%(Extension)</Dest> 
   </ItmGrp>
   <ItmGrp Include="File2.txt">
       <Dest>dest\%(FileName)%(Extension)</Dest> 
   </ItmGrp>
   <ItmGrp Include="File3.txt">
       <Dest>dest\%(FileName)%(Extension)</Dest> 
   </ItmGrp>
  </ItemGroup>

   <Target Name="AugmentItemGroup">
     <ItemGroup>
      <ItmGrp Include="File4.txt">
        <Dest>dest\%(FileName)%(Extension)</Dest> 
      </ItmGrp>          
     </ItemGroup>
     <Message Text="%(ItmGrp.FullPath) to %(ItmGrp.Dest)" />
   </Target>
</Project>

The output I would expect from it is:

  D:\t\File1.txt to dest\File1.txt
  D:\t\File2.txt to dest\File2.txt
  D:\t\File3.txt to dest\File3.txt
  D:\t\File4.txt to dest\File4.txt

But the result is:

  D:\t\File1.txt to dest\File1.txt
  D:\t\File2.txt to dest\File2.txt
  D:\t\File3.txt to dest\File3.txt
  D:\t\File4.txt to dest\File1.txt
  D:\t\File4.txt to dest\File2.txt
  D:\t\File4.txt to dest\File3.txt

Why is the behavior of the %(FileName)%(Extension) well-known metadata reference is different when an ItemGroup is inside a target?

Is it possible to get the "outside a target" behavior inside a target?


Solution

  • This will give the output you desire. Though it may not be the correct approach in the general case, it does avoid the batching that occurs with "File4" by making the custom metadata a part of the item definition that is calculated:

    <Project
       xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
       DefaultTargets="AugmentItemGroup" 
       ToolsVersion="4.0">  
       <ItemDefinitionGroup>
          <ItmGrp>
             <Dest>dest\%(FileName)%(Extension)</Dest>  
          </ItmGrp>
       </ItemDefinitionGroup>
    
       <ItemGroup>  
          <ItmGrp Include="File1.txt" />
          <ItmGrp Include="File2.txt" /> 
          <ItmGrp Include="File3.txt" /> 
       </ItemGroup> 
    
       <Target Name="AugmentItemGroup"> 
          <ItemGroup> 
             <ItmGrp Include="File4.txt" />
          </ItemGroup> 
          <Message Text="%(ItmGrp.FullPath) to %(ItmGrp.Dest)" /> 
       </Target> 
    </Project>
    

    edit:

    If (as your comment below says) each item has a different value for %(Dest), you just need to make the final value calculated:

    <Project ...>
        <ItemDefinitionGroup>
           <ItmGrp>
              <_Dest />
           </ItmGrp>
        </ItemDefinitionGroup>
    
        <ItemGroup>  
           <ItmGrp Include="File1.txt"><Dest>dest1</Dest></ItmGrp>
           <ItmGrp Include="File2.txt"><Dest>dest2</Dest></ItmGrp>
           <ItmGrp Include="File3.txt"><Dest>dest3</Dest></ItmGrp>
        </ItemGroup> 
    
        <Target Name="AugmentItemGroup"> 
           <ItemGroup> 
              <ItmGrp Include="File4.txt"><Dest>dest4</Dest></ItmGrp>
              <ItmGrp>
                 <_Dest>%(Dest)\%(FileName)%(Extension)</_Dest>
              </ItmGrp>
           </ItemGroup> 
           <Message Text="%(ItmGrp.FullPath) to %(ItmGrp._Dest)" /> 
        </Target> 
    </Project>
    

    Excerpted from MSBuild Trickery tricks #70, 71