Подтвердить что ты не робот

Изменение метаданных ItemGroup 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

4b9b3361

Ответ 1

Мне пришлось написать пользовательскую задачу, чтобы сделать это:

Вот как это работает

<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

Да, вы можете изменить или добавить метаданные <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: новые методы манипулирования элементами и свойствами (MSBuild)

Ответ 3

Теперь манипуляция локально в целевой работе...

Как переписывать глобальные определения, например

<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

Итак, существует ли способ, которым 3) будет генерировать тот же результат, что и 2)?

Это действительно упростило бы многие вещи, например. исключить код в определенных каталогах из компиляции и т.д.

Cheers, Nomad

Ответ 4

Невозможно изменить существующий элемент, но вы можете создать новый список.

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

Ответ 5

В ответ на Nomad, кажется, что цель получает копию текущего значения свойств и элементов при вызове цели. В вашем примере вы можете устранить проблему, вызвав DefaultTarget после завершения цели PreProcess. Один из способов сделать это - указать, что DefaultTarget зависит от цели PreProcess:

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

Ответ 6

Я попытался использовать TaskAction UpdateMetaData из задачи MSBuildHelper в пакете расширений 4.0, но не сделал того, что ожидал, поэтому пошел с методом remove/replace. В этом примере я пытаюсь обновить свойство метаданных DestinationRelativePath в группе элементов FilesForPackagingFromProject.

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

Ответ 7

Существует новый способ изменения метаданных с помощью атрибута обновления, например

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

Больше в документации MSBuild