Изменить метаданные группы элементов 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 ответов
Я должен был написать пользовательское задание, чтобы сделать это:
Вот как это работает
<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:
/// <?xml version="1.0" encoding="utf-8"?>
///<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Testing" ToolsVersion="3.5">
///
/// <!-- Do not edit this -->
/// <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets" />
/// <UsingTask AssemblyFile="C:\Base\Junk\UpdateMetadata\UpdateMetadata\bin\Debug\UpdateMetadata.dll" TaskName="UpdateMetadata"/>
///
///
/// <!--Re-setup the solutions to build definition-->
/// <ItemGroup>
/// <SolutionToBuild Include="$(BuildProjectFolderPath)\ChangeThisOne.sln">
/// <Properties>Change</Properties>
/// </SolutionToBuild>
/// <SolutionToBuild Include="$(BuildProjectFolderPath)\ChangeThisToo.sln">
/// <Properties>Change</Properties>
/// </SolutionToBuild>
/// <SolutionToBuild Include="$(BuildProjectFolderPath)\DontChangeThisOne.sln">
/// <Properties>Don'tChange</Properties>
/// </SolutionToBuild>
/// </ItemGroup>
///
/// <Target Name="Testing">
/// <Message Text="Before = %(SolutionToBuild.Identity) %(SolutionToBuild.Properties)" />
///
/// <ItemGroup>
/// <ItemsToChange Include="@(SolutionToBuild)">
/// <Properties>ChangedValue</Properties>
/// </ItemsToChange>
///
/// <ItemsToChange Remove="%(ItemsToChange.rootdir)%(ItemsToChange.directory)DontChangeThisOne%(ItemsToChange.extension)"/>
/// </ItemGroup>
///
/// <ItemGroup>
/// <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>
///
/// <Message Text="After = %(SolutionToBuild.Identity) %(SolutionToBuild.Properties)"/>
/// </Target>
///</Project>
///</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
Да, вы можете изменить или добавить в <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)
Существует новый способ изменения метаданных с помощью атрибута обновления, например
<ItemGroup>
<Compile Update="somefile.cs"> // or Update="*.designer.cs"
<MetadataKey>MetadataValue</MetadataKey>
</Compile>
</ItemGroup>
Больше в документации MSBuild
Теперь, манипуляция локально в цели работает...
Как насчет перезаписи глобальных определений, например
<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) Первоначально: a.test b.test
2) в рамках цели "PreProcess" мы получаем: a.test c.test
3) после выхода из цели "PreProcess" мы снова имеем: a.test b.test
Так есть ли способ, чтобы 3) генерировал тот же результат, что и 2)?
Это действительно упростит многие вещи, например, исключит код из определенных каталогов из компиляции и т. Д.
Ура, кочевник
В ответ на Nomad кажется, что цель получает копию текущего значения свойств и элементов при вызове цели. В вашем примере вы можете решить эту проблему, вызвав DefaultTarget после того, как цель PreProcess достигнута. Один из способов сделать это - указать, что DefaultTarget зависит от цели PreProcess:
<Target Name="DefaultTarget" DependsOnTargets="PreProcess">
...
</Target>
Невозможно изменить существующий элемент, но вы можете создать новый список.
<CreateItem Include="@(SolutionToBuild)"
AdditionalMetadata="Version=$(Version)" >
<Output ItemName="SolToBuildMods" TaskParameter="Include" />
</CreateItem>
<Message Text="%(SlnToBuildMods.Identity) %(SlnToBuildMods.Version)" />
Я попытался использовать UpdateMetaData TaskAction из задачи MSBuildHelper в пакете расширений 4.0, но он не сделал то, что я ожидал, поэтому я выбрал метод удаления / замены. В этом примере я пытаюсь обновить свойство метаданных 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>