Spouštění úkolů nebo cílů v dávkách na základě metadat položek

Nástroj MSBuild rozdělí seznamy položek do různých kategorií nebo dávek na základě metadat položek a jednou spustí cíl nebo úlohu s každou dávkou.

Dávkování úkolů

Dávkování úkolů umožňuje zjednodušit soubory projektu tím, že poskytuje způsob, jak rozdělit seznamy položek do různých dávek a předat jednotlivé dávky do úkolu samostatně. To znamená, že soubor projektu musí mít pouze úkol a jeho atributy deklarovány jednou, i když se dá spustit několikrát.

Určíte, že má nástroj MSBuild provádět dávkování s úkolem pomocí %(ItemMetaDataName) zápisu v jednom z atributů úkolu. Následující příklad rozdělí Example seznam položek na dávky na Color základě hodnoty metadat položky a předá jednotlivé dávky úkolu MyTask samostatně.

Poznámka:

Pokud neodkazujete na seznam položek jinde v atributech úkolu nebo název metadat může být nejednoznačný, můžete použít notaci %(<ItemCollection.ItemMetaDataName>) a plně kvalifikovat hodnotu metadat položky, která se má použít pro dávkování.

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

    <ItemGroup>
        <Example Include="Item1">
            <Color>Blue</Color>
        </Example>
        <Example Include="Item2">
            <Color>Red</Color>
        </Example>
    </ItemGroup>

    <Target Name="RunMyTask">
        <MyTask
            Sources = "@(Example)"
            Output = "%(Color)\MyFile.txt"/>
    </Target>

</Project>

Konkrétnější příklady dávkování najdete v tématu Metadata položek v dávkování úkolů.

Dávkování cíle

Nástroj MSBuild zkontroluje, jestli jsou vstupy a výstupy cíle aktuální před spuštěním cíle. Pokud jsou vstupy i výstupy aktuální, cíl se přeskočí. Pokud úkol uvnitř cíle používá dávkování, nástroj MSBuild musí určit, jestli jsou vstupy a výstupy pro každou dávku položek aktuální. V opačném případě se cíl spustí pokaždé, když dojde k jeho dosažení.

Následující příklad ukazuje Target prvek, který obsahuje Outputs atribut s zápisem %(ItemMetadataName) . Nástroj MSBuild rozdělí Example seznam položek na dávky na Color základě metadat položky a analyzuje časové razítka výstupních souborů pro každou dávku. Pokud výstupy z dávky nejsou aktuální, cíl se spustí. Jinak se cíl přeskočí.

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

    <ItemGroup>
        <Example Include="Item1">
            <Color>Blue</Color>
        </Example>
        <Example Include="Item2">
            <Color>Red</Color>
        </Example>
    </ItemGroup>

    <Target Name="RunMyTask"
        Inputs="@(Example)"
        Outputs="%(Color)\MyFile.txt">
        <MyTask
            Sources = "@(Example)"
            Output = "%(Color)\MyFile.txt"/>
    </Target>

</Project>

Další příklad dávkování cíle najdete v tématu Metadata položky v cílové dávce.

Mutování položek a vlastností

Tato část popisuje, jak porozumět účinkům změny vlastností nebo metadat položek při používání cílových dávek nebo dávkování úkolů.

Vzhledem k tomu, že dávkování cílových dávek a úkolů jsou dvě různé operace NÁSTROJE MSBuild, je důležité přesně pochopit, jakou formu dávkování nástroj MSBuild v každém případě používá. Když se syntaxe %(ItemMetadataName) dávkování zobrazí v úkolu v cíli, ale ne v atributu v cíli, nástroj MSBuild používá dávkování úkolů. Jediným způsobem, jak určit cílové dávkování, je použití syntaxe dávkování u atributu Outputs Target, obvykle atributu.

Při dávkování cílových dávek i dávkování úkolů je možné dávky považovat za nezávislé spuštění. Všechny dávky začínají kopií stejného počátečního stavu vlastností a hodnot metadat položek. Jakékoli mutace hodnot vlastností během dávkového provádění nejsou viditelné pro jiné dávky. Představte si následující příklad:

  <ItemGroup>
    <Thing Include="2" Color="blue" />
    <Thing Include="1" Color="red" />
  </ItemGroup>

  <Target Name="DemoIndependentBatches">
    <ItemGroup>
      <Thing Condition=" '%(Color)' == 'blue' ">
        <Color>red</Color>
        <NeededColorChange>true</NeededColorChange>
      </Thing>
    </ItemGroup>
    <Message Importance="high"
             Text="Things: @(Thing->'%(Identity) is %(Color); needed change=%(NeededColorChange)')"/>
  </Target>

Výstup je:

Target DemoIndependentBatches:
  Things: 2 is red; needed change=true;1 is red; needed change=

Cíl ItemGroup je implicitně úkol a s %(Color) atributem Condition se provádí dávkování úkolů. Existují dvě dávky: jedna pro červenou a druhou pro modrou. Vlastnost %(NeededColorChange) je nastavena pouze v případě, že %(Color) metadata jsou modrá a nastavení má vliv pouze na jednotlivé položky, které odpovídaly podmínce při spuštění modré dávky. Atribut Message úkolu Text neaktivuje dávkování bez ohledu na %(ItemMetadataName) syntaxi, protože se používá uvnitř transformace položky.

Dávky běží nezávisle, ale ne paralelně. To je rozdíl, když přistupujete k hodnotám metadat, které se mění v dávkovém spuštění. V případě, že nastavíte vlastnost na základě některých metadat v dávkovém spuštění, bude mít tato vlastnost poslední sadu hodnot:

   <PropertyGroup>
       <SomeProperty>%(SomeItem.MetadataValue)</SomeProperty>
   </PropertyGroup>

Po dávkovém spuštění zachová vlastnost konečnou hodnotu %(MetadataValue).

I když dávky běží nezávisle, je důležité vzít v úvahu rozdíl mezi cílovým dávkováním a dávkováním úkolů a zjistit, jaký typ se vztahuje na vaši situaci. Podívejte se na následující příklad, abyste lépe porozuměli důležitosti tohoto rozdílu.

Úkoly mohou být implicitní, nikoli explicitní, což může být matoucí při dávkování úkolů s implicitními úkoly. Když se v PropertyGroup objektu Targetobjeví nebo ItemGroup prvek , každá deklarace vlastnosti ve skupině je implicitně považována za samostatnou úlohu CreateProperty nebo CreateItem. To znamená, že chování se liší při dávkovém dávkování cíle a v případě, že cíl není dávkový (to znamená, že chybí %(ItemMetadataName) syntaxe v atributu Outputs ). Když je cíl dávkový, ItemGroup provede se jednou pro cíl, ale pokud cíl není dávkový, implicitní ekvivalenty CreateItem úkolů nebo CreateProperty úkolů se dávkovají pomocí dávkování úkolů, takže cíl se provede pouze jednou a každá položka nebo vlastnost ve skupině se dávková samostatně pomocí dávkování úkolů.

Následující příklad znázorňuje cílové dávkování vs. dávkování úkolů v případě, že jsou metadata mutovaná. Představte si situaci, kdy máte složky A a B s některými soubory:

A\1.stub
B\2.stub
B\3.stub

Teď se podívejte na výstup těchto dvou podobných projektů.

    <ItemGroup>
      <StubFiles Include="$(MSBuildThisFileDirectory)**\*.stub"/>

      <StubDirs Include="@(StubFiles->'%(RecursiveDir)')"/>
    </ItemGroup>

    <Target Name="Test1" AfterTargets="Build" Outputs="%(StubDirs.Identity)">
      <PropertyGroup>
        <ComponentDir>%(StubDirs.Identity)</ComponentDir>
        <ComponentName>$(ComponentDir.TrimEnd('\'))</ComponentName>
      </PropertyGroup>

      <Message Text=">> %(StubDirs.Identity) '$(ComponentDir)' '$(ComponentName)'"/>
    </Target>

Výstup je:

Test1:
  >> A\ 'A\' 'A'
Test1:
  >> B\ 'B\' 'B'

Teď odeberte Outputs atribut, který určil cílové dávkování.

    <ItemGroup>
      <StubFiles Include="$(MSBuildThisFileDirectory)**\*.stub"/>

      <StubDirs Include="@(StubFiles->'%(RecursiveDir)')"/>
    </ItemGroup>

    <Target Name="Test1" AfterTargets="Build">
      <PropertyGroup>
        <ComponentDir>%(StubDirs.Identity)</ComponentDir>
        <ComponentName>$(ComponentDir.TrimEnd('\'))</ComponentName>
      </PropertyGroup>

      <Message Text=">> %(StubDirs.Identity) '$(ComponentDir)' '$(ComponentName)'"/>
    </Target>

Výstup je:

Test1:
  >> A\ 'B\' 'B'
  >> B\ 'B\' 'B'

Všimněte si, že nadpis Test1 se vytiskne jenom jednou, ale v předchozím příkladu se vytiskne dvakrát. To znamená, že cíl není dávkový. Výstup je proto matoucí.

Důvodem je, že při použití cílové dávky každá cílová dávka provede vše v cíli s vlastní nezávislou kopií všech vlastností a položek, ale když atribut vynecháte Outputs , jednotlivé řádky ve skupině vlastností se považují za odlišné, potenciálně dávkové úkoly. V tomto případě ComponentDir je úkol dávkový (používá %(ItemMetadataName) syntaxi), aby se po ComponentName dokončení řádku dokončily obě dávky ComponentDir řádku a druhá dávka, která běžela, určila hodnotu, jak je vidět na druhém řádku.

Funkce vlastností využívající metadata

Dávkování je možné řídit funkcemi vlastností, které obsahují metadata. Příklad:

$([System.IO.Path]::Combine($(RootPath),%(Compile.Identity)))

slouží Combine ke kombinování cesty ke kořenové složce s cestou ke kompilaci položky.

Funkce vlastností se nemusí zobrazovat v hodnotách metadat. Příklad:

%(Compile.FullPath.Substring(0,3))

není povoleno.

Další informace o funkcích vlastností naleznete v tématu Funkce vlastností.

Dávkování položek u metadat odkazujících na sebe

Podívejte se na následující příklad odkazování na metadata z definice položky:

<ItemGroup>
  <i Include='a/b.txt' MyPath='%(Filename)%(Extension)' />
  <i Include='c/d.txt' MyPath='%(Filename)%(Extension)' />
  <i Include='g/h.txt' MyPath='%(Filename)%(Extension)' />
</ItemGroup>

Je důležité si uvědomit, že chování se liší, když je definováno mimo jakýkoli cíl a v rámci cíle.

Metadata odkazující na položku mimo jakýkoli cíl

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <i Include='a/b.txt' MyPath='%(Filename)%(Extension)' />
    <i Include='c/d.txt' MyPath='%(Filename)%(Extension)' />
    <i Include='g/h.txt' MyPath='%(Filename)%(Extension)' />
  </ItemGroup>
  <Target Name='ItemOutside'>
    <Message Text="i=[@(i)]" Importance='High' />
    <Message Text="i->MyPath=[@(i->'%(MyPath)')]" Importance='High' />
  </Target>
</Project>

Odkazování na metadata se překládá na instanci položky (není ovlivněno dříve definovanými nebo vytvořenými instancemi položek) – což vede k očekávanému výstupu:

  i=[a/b.txt;c/d.txt;g/h.txt]
  i->MyPath=[b.txt;d.txt;h.txt]

Vlastní odkazování na metadata položky uvnitř cíle

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name='ItemInside'>  
    <ItemGroup>
      <i Include='a/b.txt' MyPath='%(Filename)%(Extension)' />
      <i Include='c/d.txt' MyPath='%(Filename)%(Extension)' />
      <i Include='g/h.txt' MyPath='%(Filename)%(Extension)' />
    </ItemGroup>
    <Message Text="i=[@(i)]" Importance='High' />
    <Message Text="i->MyPath=[@(i->'%(MyPath)')]" Importance='High' />
  </Target>
</Project>

Metadata odkazující v tomto případě vedou k dávkování, což vede k neočekávaným a nezamýšleným výstupům:

  i=[a/b.txt;c/d.txt;g/h.txt;g/h.txt]
  i->MyPath=[;b.txt;b.txt;d.txt]

Pro každou instanci položky modul použije metadata všech dříve existujících instancí položek (proto MyPath je prázdná pro první položku a obsahuje b.txt pro druhou položku). V případě více existujících instancí to vede k násobení aktuální instance položky (proto instance položky, která g/h.txt se ve výsledném seznamu vyskytuje dvakrát).

Chcete-li o tom explicitně informovat, pravděpodobně nezamýšlené chování, pozdější verze zprávy MSB4120o problému nástroje MSBuild:

proj.proj(4,11):  message : MSB4120: Item 'i' definition within target is referencing self via metadata 'Filename' (qualified or unqualified). This can lead to unintended expansion and cross-applying of pre-existing items. More info: https://aka.ms/msbuild/metadata-self-ref
proj.proj(4,11):  message : MSB4120: Item 'i' definition within target is referencing self via metadata 'Extension' (qualified or unqualified). This can lead to unintended expansion and cross-applying of pre-existing items. More info: https://aka.ms/msbuild/metadata-self-ref
proj.proj(5,11):  message : MSB4120: Item 'i' definition within target is referencing self via metadata 'Filename' (qualified or unqualified). This can lead to unintended expansion and cross-applying of pre-existing items. More info: https://aka.ms/msbuild/metadata-self-ref
proj.proj(5,11):  message : MSB4120: Item 'i' definition within target is referencing self via metadata 'Extension' (qualified or unqualified). This can lead to unintended expansion and cross-applying of pre-existing items. More info: https://aka.ms/msbuild/metadata-self-ref
proj.proj(6,11):  message : MSB4120: Item 'i' definition within target is referencing self via metadata 'Filename' (qualified or unqualified). This can lead to unintended expansion and cross-applying of pre-existing items. More info: https://aka.ms/msbuild/metadata-self-ref
proj.proj(6,11):  message : MSB4120: Item 'i' definition within target is referencing self via metadata 'Extension' (qualified or unqualified). This can lead to unintended expansion and cross-applying of pre-existing items. More info: https://aka.ms/msbuild/metadata-self-ref
  i=[a/b.txt;c/d.txt;g/h.txt;g/h.txt]
  i->MyPath=[;b.txt;b.txt;d.txt]

Pokud je odkaz na sebe záměrný, máte několik možností v závislosti na skutečném scénáři a přesných potřebách:

Použití pomocné položky a transformace

Pokud chcete zabránit dávkovému chování vyvolané odkazem na metadata, můžete toho dosáhnout definováním samostatné položky a následným využitím operace transformace vytvořit instance položek s požadovanými metadaty:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name='ItemOutside'>  
    <ItemGroup>
      <j Include='a/b.txt' />
      <j Include='c/*' />
      <i Include='@(j)' MyPath="%(Filename)%(Extension)" />
    </ItemGroup>
    <Message Text="i=[@(i)]" Importance='High' />
    <Message Text="i->MyPath=[@(i->'%(MyPath)')]" Importance='High' />
  </Target>
</Project>