tags:

views:

2245

answers:

4

Is it possible to modify a ItemGroup's metadata after it is declared.

For Example:

  <ItemGroup>
    <SolutionToBuild Include="$(BuildProjectFolderPath)\MySolution.sln">
      <Targets></Targets>
      <Properties></Properties>
    </SolutionToBuild>

  </ItemGroup>

  <Target Name="BuildNumberOverrideTarget">
     <!--Code to get the version number from a file (removed)-->

     <!--Begin Pseudo Code-->
     <CodeToChangeItemGroupMetaData 
           ItemToChange="%(SolutionToBuild.Properties)" 
           Condition ="'%(SolutionToBuild.Identity)' ==
                       '$(BuildProjectFolderPath)\MySolution.sln'"
           NewValue="Version=$(Version)" />
     <!--End Pseudo Code-->         

  </Target>

I am hoping there is a way that does not require me to remove the item then re-declare it.

Thanks for any answers. Vaccano

+1  A: 

It isn't possible to modify an existing Item, but you can create a new list.

<CreateItem Include="@(SolutionToBuild)"  
            AdditionalMetadata="Version=$(Version)" >
      <Output ItemName="SolToBuildMods" TaskParameter="Include" />
</CreateItem>
<Message Text="%(SlnToBuildMods.Identity) %(SlnToBuildMods.Version)" />
Scott Weinstein
A: 

I had to write a custom task to do this:

Here is how it works

<ItemGroup>
  <ItemsToChange Include="@(SolutionToBuild)">
    <Properties>ChangedValue</Properties>
  </ItemsToChange>
  <MetaDataToChange Include="Properties"/>
</ItemGroup>

<UpdateMetadata SourceList="@(SolutionToBuild)" ItemsToModify="@(ItemsToChange)" MetadataToModify="@(MetaDataToChange)">
  <Output TaskParameter="NewList" ItemName="SolutionToBuildTemp" />
</UpdateMetadata>

<ItemGroup>
  <SolutionToBuild Remove="@(SolutionToBuild)"/>
  <SolutionToBuild Include ="@(SolutionToBuildTemp)"/>
</ItemGroup>

It fills a new item called SolutionToBuildTemp with the changed value. I then remove everything in the SolutionToBuild item and fill it iwith the SolutionToBuildTemp item.

Here is the code for the task if anyone is interested (I did submit it to the MSBuildExtenstionPack too).

// By Stephen Schaff (Vaccano).  
// Free to use for your code. Need my Permission to Sell it.
using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace UpdateMetadata
{
    ///<summary>
    /// Used to update the metadata in a ItemGroup (Note: Requires an MSBuild Call After using this task to complete the update.  See Usage.)
    /// Usage:
    /// &lt;?xml version="1.0" encoding="utf-8"?&gt;
    ///&lt;Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Testing" ToolsVersion="3.5"&gt;
    /// 
    ///  &lt;!-- Do not edit this --&gt;
    ///  &lt;Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets" /&gt;
    ///  &lt;UsingTask AssemblyFile="C:\Base\Junk\UpdateMetadata\UpdateMetadata\bin\Debug\UpdateMetadata.dll" TaskName="UpdateMetadata"/&gt;
    /// 
    /// 
    ///  &lt;!--Re-setup the solutions to build definition--&gt;
    ///  &lt;ItemGroup&gt;
    ///    &lt;SolutionToBuild Include="$(BuildProjectFolderPath)\ChangeThisOne.sln"&gt;
    ///      &lt;Properties&gt;Change&lt;/Properties&gt;
    ///    &lt;/SolutionToBuild&gt;
    ///    &lt;SolutionToBuild Include="$(BuildProjectFolderPath)\ChangeThisToo.sln"&gt;
    ///      &lt;Properties&gt;Change&lt;/Properties&gt;
    ///    &lt;/SolutionToBuild&gt;
    ///    &lt;SolutionToBuild Include="$(BuildProjectFolderPath)\DontChangeThisOne.sln"&gt;
    ///      &lt;Properties&gt;Don'tChange&lt;/Properties&gt;
    ///    &lt;/SolutionToBuild&gt;
    ///  &lt;/ItemGroup&gt;
    /// 
    ///  &lt;Target Name="Testing"&gt;
    ///    &lt;Message Text="Before = %(SolutionToBuild.Identity) %(SolutionToBuild.Properties)" /&gt;
    /// 
    ///    &lt;ItemGroup&gt;
    ///      &lt;ItemsToChange Include="@(SolutionToBuild)"&gt;
    ///        &lt;Properties&gt;ChangedValue&lt;/Properties&gt;
    ///      &lt;/ItemsToChange&gt;
    ///   
    ///      &lt;ItemsToChange Remove="%(ItemsToChange.rootdir)%(ItemsToChange.directory)DontChangeThisOne%(ItemsToChange.extension)"/&gt;      
    ///    &lt;/ItemGroup&gt;
    /// 
    ///    &lt;ItemGroup&gt;
    ///      &lt;MetaDataToChange Include="Properties"/&gt;
    ///    &lt;/ItemGroup&gt;
    /// 
    ///    &lt;UpdateMetadata SourceList="@(SolutionToBuild)" ItemsToModify="@(ItemsToChange)" MetadataToModify="@(MetaDataToChange)"&gt;
    ///      &lt;Output TaskParameter="NewList" ItemName="SolutionToBuildTemp" /&gt;
    ///    &lt;/UpdateMetadata&gt;
    /// 
    ///    &lt;ItemGroup&gt;
    ///      &lt;SolutionToBuild Remove="@(SolutionToBuild)"/&gt;
    ///      &lt;SolutionToBuild Include ="@(SolutionToBuildTemp)"/&gt;
    ///    &lt;/ItemGroup&gt;
    ///         
    ///    &lt;Message Text="After  = %(SolutionToBuild.Identity) %(SolutionToBuild.Properties)"/&gt;
    ///  &lt;/Target&gt;
    ///&lt;/Project&gt;
    ///</summary>
    public class UpdateMetadata : Task
    {
        ///<summary>
        /// The list to modify.
        ///</summary>
        [Required]
        public ITaskItem[] SourceList { get; set; }

        ///<summary>
        /// Items in <see cref="SourceList"/> to change the Metadata for.  
        /// It should have the valid metadata set.
        ///</summary>
        [Required]
        public ITaskItem[] ItemsToModify { get; set; }


        ///<summary>
        /// List of metadata to modify.  This is an item group, but any metadata in it is ignored.
        ///</summary>
        public ITaskItem[] MetadataToModify { get; set; }

        ///<summary>
        /// If true then info about the update is output
        ///</summary>
        public Boolean OutputMessages { get; set; }

        ///<summary>
        /// Changed List.  If you call the following it can replace the <see cref="SourceList"/>:
        ///</summary>
        [Output]
        public ITaskItem[] NewList { get; set; }

        ///<summary>
        /// Runs the task to output the updated version of the property
        ///</summary>
        ///<returns></returns>
        public override bool Execute()
        {
            // If we got empty params then we are done.
            if ((SourceList == null) || (ItemsToModify == null) || (MetadataToModify == null))
            {
                Log.LogMessage("One of the inputs to ModifyMetadata is Null!!!", null);
                return false;
            }
            if (OutputMessages)
                Log.LogMessage(MessageImportance.Low, "Beginning Metadata Changeover", null);
            int sourceIndex = 0;
            foreach (ITaskItem sourceItem in SourceList)
            {
                // Fill the new list with the source one
                NewList = SourceList;
                foreach (ITaskItem itemToModify in ItemsToModify)
                {
                    // See if this is a match.  If it is then change the metadat in the new list
                    if (sourceItem.ToString() == itemToModify.ToString())
                    {
                        foreach (ITaskItem metadataToModify in MetadataToModify)
                        {
                            try
                            {

                                if (OutputMessages)
                                    Log.LogMessage(MessageImportance.Low, "Changing {0}.{1}",
                                        NewList[sourceIndex].ToString(), metadataToModify.ToString());
                                // Try to change the metadata in the new list.
                                NewList[sourceIndex].SetMetadata(metadataToModify.ToString(),
                                                                 itemToModify.GetMetadata(metadataToModify.ToString()));

                            }
                            catch (System.ArgumentException exception)
                            {
                                // We got some bad metadata (like a ":" or something).
                                Log.LogErrorFromException(exception);
                                return false;
                            }
                        }
                    }
                }
                sourceIndex += 1;
            }

            return true;
        }
    }
}

I hope this is useful to some one, but the code is obviously "Use at your own risk".

Vaccano

Vaccano
This task has been added to the MSBuild Extension Tasks.
Vaccano
+7  A: 

Yes you can modify or add to an <ItemGroup>'s meta data after it is defined (MSBuild 3.5)

<!-- Define ItemGroup -->
<ItemGroup>
  <TestItemGroup Include="filename.txt">
    <MyMetaData>Test meta data</MyMetaData>
  </TestItemGroup>
  <TestItemGroup Include="filename2.txt">
    <MyMetaData>Untouched</MyMetaData>
  </TestItemGroup>
</ItemGroup>

<!-- Show me-->
<Message Text="PRE: %(Identity) %(TestItemGroup.MyMetaData) %(TestItemGroup.OtherMetaData)" Importance="high" />

<!-- Now change it -->
<ItemGroup>
  <TestItemGroup Condition="'%(TestItemGroup.MyMetaData)'=='Test meta data' AND 'AnotherCondition'=='AnotherCondition'">
    <MyMetaData>Well adjusted</MyMetaData>
    <OtherMetaData>New meta data</OtherMetaData>
  </TestItemGroup>
</ItemGroup>

<!-- Show me the changes -->
<Message Text="POST: %(Identity) %(TestItemGroup.MyMetaData) %(TestItemGroup.OtherMetaData)" Importance="high" />

Reference: MSDN Library: New Methods for Manipulating Items and Properties (MSBuild)

Alex
A: 

Now, manipulation locally in a target works...

How about overwriting global definitions, e.g.

<Project ...>

  <ItemGroup>
    <TestFiles Include="a.test" />
    <TestFiles Include="b.test" />
  </ItemGroup>


  <Target Name="DefaultTarget">
    <Message Text="Files befor change ItemGroup:" />
    <Message Text="%(TestFiles.Identity)" />

    <CallTarget Targets="PreProcess" />

    <Message Text="Files after change ItemGroup:" />
    <Message Text="%(TestFiles.Identity)" />
  </Target>  

  <Target Name="PreProcess">

    <ItemGroup>
      <TestFiles Remove="b.test" />
    </ItemGroup>


    <CreateItem Include="c.test">
        <Output TaskParameter="Include" ItemName="TestFiles" /> 
    </CreateItem>

    <Message Text="Files after change ItemGroup (local in target):" />
    <Message Text="%(TestFiles.Identity)" />

  </Target>  

</Project>

When we check the content of %(TestFiles) here we will get:

1) Initially: a.test b.test

2) within target "PreProcess" we get: a.test c.test

3) after leaving target "PreProcess" we have again: a.test b.test

So ist there a way that 3) would generate the same output as 2) ?

This would really simplify a lot of things, e.g. exclude code in specific directories from compilation etc.

Cheers, Nomad

This should prob have been a new question. I wrote a task that was added to the MSBuild Extension Pack (http://www.codeplex.com/MSBuildExtensionPack) to do an update. It was too long ago for me to remember if it will work in the scenario you have outlined above, but I would recommend you give it a try. (It is called UpdateMetadata in the MSBuild.ExtensionPack.Framework.MSBuildHelper class.
Vaccano