修改MSBuild ItemGroup元数据

时间:2009-05-08 21:34:35

标签: msbuild

是否可以在声明后修改ItemGroup的元数据。

例如:

  <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>

我希望有一种方法不需要我删除该项目然后重新声明它。

感谢您的回答。 Vaccano

7 个答案:

答案 0 :(得分:40)

是的,可以修改或添加<ItemGroup>元数据后的数据(MSBuild 3.5)

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

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

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

    <!-- Now change it - can only do it inside a target -->
    <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: %(TestItemGroup.Identity)  MyMetaData:%(TestItemGroup.MyMetaData)  OtherMetaData:%(TestItemGroup.OtherMetaData)" Importance="high" />
  </Target>

  <Target Name="Build" />
</Project>

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

答案 1 :(得分:2)

我必须编写一个自定义任务来执行此操作:

以下是它的工作原理

<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>

它使用更改的值填充一个名为SolutionToBuildTemp的新项。然后我删除SolutionToBuild项目中的所有内容并使用SolutionToBuildTemp项填充它。

如果有人感兴趣的话,这是任务的代码(我确实将它提交给了MSBuildExtenstionPack)。

// 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;
        }
    }
}

我希望这对某些人有用,但代码显然是“使用风险自负”。

Vaccano

答案 2 :(得分:2)

现在,目标中的本地操作有效......

如何覆盖全局定义,例如

<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>

当我们在这里检查%(TestFiles)的内容时,我们将得到:

1)最初: 一个测试 b.test

2)在目标“PreProcess”中,我们得到: 一个测试 C.TEST

3)离开目标“PreProcess”后我们再次: 一个测试 b.test

那么有没有一种方法可以产生与2)相同的输出?

这会真正简化许多事情,例如:从编译等中排除特定目录中的代码。

干杯, 游牧

答案 3 :(得分:1)

无法修改现有项目,但您可以创建新列表。

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

答案 4 :(得分:1)

作为对Nomad的回应,当调用目标时,目标似乎获得属性和项目的当前值的副本。在您的示例中,您可以通过在PreProcess目标完成后调用DefaultTarget来解决问题。一种方法是指定DefaultTarget依赖于PreProcess目标:

<Target Name="DefaultTarget" DependsOnTargets="PreProcess"> ... </Target>

答案 5 :(得分:1)

我尝试使用4.0扩展包中的MSBuildHelper任务的UpdateMetaData TaskAction,但它没有按照我的预期执行,所以我使用了remove / replace方法。在这个例子中,我试图更新FilesForPackagingFromProject项目组中的DestinationRelativePath元数据属性。

    <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">

    <PropertyGroup>
        <CopyAllFilesToSingleFolderForPackageDependsOn>
            $(CopyAllFilesToSingleFolderForPackageDependsOn);
            SetFilePathsToRoot;
        </CopyAllFilesToSingleFolderForPackageDependsOn>
    </PropertyGroup>

    <Target Name="SetFilePathsToRoot">

        <Message Text="Stripping \bin directory from package file paths" />

        <!-- Tweak the package files' DestinationRelativePath property such that binaries don't go into a \bin directory -->
        <ItemGroup>
            <ModifiedFilesForPackagingFromProject Include="@(FilesForPackagingFromProject)">
                <DestinationRelativePath>%(FileName)%(Extension)</DestinationRelativePath>
            </ModifiedFilesForPackagingFromProject>
        </ItemGroup>

        <ItemGroup>
            <FilesForPackagingFromProject Remove="@(FilesForPackagingFromProject)" />
            <FilesForPackagingFromProject Include="@(ModifiedFilesForPackagingFromProject)" />
        </ItemGroup>

    </Target>

</Project>

答案 6 :(得分:0)

有一个新的使用 Update 属性修改元数据的方法 例如

<ItemGroup>
    <Compile Update="somefile.cs">  // or Update="*.designer.cs"
        <MetadataKey>MetadataValue</MetadataKey>
    </Compile>
</ItemGroup>  

MSBuild documentation

中的更多内容