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ě. Dávkování znamená, že soubor projektu musí mít pouze úkol a jeho atributy deklarovány jednou, i když je možné ho spustit několikrát.

Určíte, že má nástroj MSBuild provádět dávkování s úkolem pomocí %(ItemMetaDataName) notace v některém 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 pokud může být název metadat nejednoznačný, můžete použít notaci %(<ItemCollection.ItemMetaDataName>), abyste plně kvalifikovali hodnotu metadat položek, kterou použijete pro dávkování.

<Project>

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

Cílové dávkování

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 se 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 dávkového procesu nejsou aktuální, cíl se spustí. Jinak se cíl přeskočí.

<Project>

    <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 cílového dávkování najdete v tématu Metadata položky pro cílové dávkování.

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ílů a úkolů jsou dvě různé operace nástroje MSBuild, je důležité přesně pochopit, jaký způsob dávkování nástroj MSBuild pro každý 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 stanovit cílové seskupování, je použití syntaxe dávkování u atributu Target, obvykle u atributu Outputs.

Při dávkování cílových dávek i dávkování úkolů lze dávky považovat za to, že běží nezávisle. Všechny dávky začínají kopií stejného počátečního stavu vlastností a hodnot metadat položek. Jakékoli změny hodnot atributů v průběhu dávkového provádění nejsou viditelné pro jiné procesy. 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 atributem %(Color) v 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 má vliv, když přistupujete k hodnotám metadat, které se mění během dávkového zpracová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 prvek PropertyGroup nebo ItemGroup objeví v Target, každé deklaraci vlastnosti ve skupině se implicitně přisuzuje status samostatné úlohy CreateProperty nebo CreateItem. Toto chování znamená, že provádění sestavení se liší, pokud je cíl dávkován, oproti tomu, kdy není dávkován (to znamená, pokud v atributu Outputs chybí syntax %(ItemMetadataName)). Když je cílový úkol dávkový, ItemGroup se provede jednou pro každý cíl, ale pokud cíl není dávkový, pak se implicitní ekvivalenty úkolů CreateItem nebo CreateProperty dávkují pomocí dávkování úkolů, takže se cílový úkol provede pouze jednou, a každá položka nebo vlastnost ve skupině se dávkují 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í a odlišný.

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

Funkce vlastností využívající metadata

Dávkování lze ovládat funkcemi parametrů, které zahrnují metadata. Příklad:

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

slouží Combine ke kombinování cesty ke kořenové složce s cestou ke kompilovanému souboru.

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>
  <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 řeší pro každou instanci položky zvlášť (není ovlivněno žádnými dříve definovanými nebo vytvořenými instancemi položek) – což vede k očekávanému výsledku:

  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>
  <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 v tomto případě odkazují na dávkování, což může vést 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í toto chování vede k násobení instance aktuální položky (proto g/h.txt se instance položky ve výsledném seznamu vyskytuje dvakrát).

Chcete-li explicitně informovat o tomto potenciálně nezamýšleném chování, novější verze nástroje MSBuild vydávají zprávu MSB4120:

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:

  • Zachovat kód a ignorovat zprávu
  • Definování položky mimo cíl
  • Použití pomocné položky a operace transformace

Použijte pomocný prvek a transformační operaci

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 použitím operace transformace k vytvoření instancí položek s požadovanými metadaty:

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