修改MSBuild ItemGroup元数据

25

在一个 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

6个回答

42

MSBuild 3.5支持在定义<ItemGroup>后修改或添加元数据。

<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图书馆:操作项和属性的新方法(MSBuild)


1
请注意,您必须在目标内部才能执行此操作。 - Zain Rizvi

5

通过使用Update属性,有一种新的方法来修改元数据。 例如。

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

然而:
可选属性。(仅适用于Visual Studio 2017或更高版本的.NET Core项目。)
更多信息请参阅MSBuild文档

3
我需要编写一个自定义任务来完成这个任务:
以下是它的工作原理:
<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

此任务已添加到 MSBuild 扩展任务中。 - Vaccano

1

无法修改已存在的项目,但可以创建新列表。

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

1

我尝试使用MSBuildHelper任务中的UpdateMetaData TaskAction来更新4.0扩展包中的元数据,但它并没有达到我的预期,所以我选择了删除/替换方法。在这个例子中,我正在尝试更新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>

1
针对Nomad的问题,当调用目标时,似乎会获取属性和项的当前值的副本。在您的示例中,您可以通过在PreProcess目标完成后调用DefaultTarget来解决该问题。一种方法是指定DefaultTarget依赖于PreProcess目标: <Target Name="DefaultTarget" DependsOnTargets="PreProcess"> ... </Target>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接