Uruchamianie zadań lub obiektów docelowych w partiach na podstawie metadanych elementu

Program MSBuild dzieli listy elementów na różne kategorie lub partie na podstawie metadanych elementu i uruchamia element docelowy lub zadanie pojedynczo z każdą partią.

Przetwarzanie wsadowe zadań

Przetwarzanie wsadowe zadań pozwala uprościć pliki projektu, udostępniając sposób dzielenia list elementów na różne partie i oddzielne przekazywanie każdej z tych partii do zadania. Oznacza to, że plik projektu musi mieć zadeklarowane zadanie i jego atrybuty tylko raz, mimo że można go uruchomić kilka razy.

Należy określić, że program MSBuild ma wykonywać przetwarzanie wsadowe z zadaniem przy użyciu %(ItemMetaDataName) notacji w jednym z atrybutów zadania. Poniższy przykład dzieli Example listę elementów na partie na Color podstawie wartości metadanych elementu i przekazuje poszczególne partie do MyTask zadania oddzielnie.

Uwaga

Jeśli nie odwołujesz się do listy elementów w innym miejscu atrybutów zadania lub nazwa metadanych może być niejednoznaczna, możesz użyć notacji %(<ItemCollection.ItemMetaDataName>), aby w pełni zakwalifikować wartość metadanych elementu do użycia do dzielenia na partie.

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

Aby uzyskać bardziej szczegółowe przykłady dzielenia na partie, zobacz Metadane elementów w partiach zadań.

Przetwarzanie wsadowe docelowe

Program MSBuild sprawdza, czy dane wejściowe i wyjściowe obiektu docelowego są aktualne, zanim uruchomi obiekt docelowy. Jeśli zarówno dane wejściowe, jak i wyjściowe są aktualne, element docelowy zostanie pominięty. Jeśli zadanie wewnątrz obiektu docelowego używa przetwarzania wsadowego, program MSBuild musi określić, czy dane wejściowe i wyjściowe dla każdej partii elementów są aktualne. W przeciwnym razie element docelowy jest wykonywany za każdym razem, gdy zostanie trafiony.

W poniższym przykładzie pokazano Target element zawierający Outputs atrybut z %(ItemMetadataName) notacją. Program MSBuild podzieli Example listę elementów na partie na Color podstawie metadanych elementu i przeanalizuje znaczniki czasu plików wyjściowych dla każdej partii. Jeśli dane wyjściowe z partii nie są aktualne, element docelowy zostanie uruchomiony. W przeciwnym razie obiekt docelowy zostanie pominięty.

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

Aby zapoznać się z innym przykładem przetwarzania wsadowego docelowego, zobacz Metadane elementu w partii docelowej.

Mutacje elementów i właściwości

W tej sekcji opisano, jak zrozumieć skutki zmiany właściwości i/lub metadanych elementu podczas przetwarzania wsadowego docelowego lub dzielenia zadań na partie.

Ponieważ przetwarzanie wsadowe obiektów docelowych i przetwarzanie wsadowe zadań to dwie różne operacje MSBuild, ważne jest, aby dokładnie zrozumieć, która forma przetwarzania wsadowego programu MSBuild jest używana w każdym przypadku. Gdy składnia %(ItemMetadataName) dzielenia na partie pojawia się w zadaniu docelowym, ale nie w atrybucie docelowym, program MSBuild używa przetwarzania wsadowego zadań. Jedynym sposobem określania wsadowania docelowego jest użycie składni dzielenia na partie w atrybucie Target, zazwyczaj atrybutu Outputs .

W przypadku przetwarzania wsadowego docelowego i przetwarzania wsadowego zadań można uznać partie za niezależne. Wszystkie partie zaczynają się od kopii tego samego początkowego stanu właściwości i wartości metadanych elementu. Wszelkie mutacje wartości właściwości podczas wykonywania wsadowego nie są widoczne dla innych partii. Rozważmy następujący przykład:

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

Dane wyjściowe to:

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

Obiekt ItemGroup docelowy jest niejawnie zadaniem, a w %(Color) atrybucie Condition wykonywane jest dzielenie zadań na partie. Istnieją dwie partie: jedna dla czerwonego i druga dla niebieskiego. Właściwość %(NeededColorChange) jest ustawiana tylko wtedy, gdy %(Color) metadane są niebieskie, a ustawienie wpływa tylko na pojedynczy element pasujący do warunku, gdy została uruchomiona niebieska partia. Atrybut Message zadania nie wyzwala przetwarzania Text wsadowego, pomimo %(ItemMetadataName) składni, ponieważ jest używany wewnątrz przekształcenia elementu.

Partie są uruchamiane niezależnie, ale nie równolegle. Ma to wpływ na dostęp do wartości metadanych, które zmieniają się w ramach wykonywania wsadowego. W przypadku ustawienia właściwości na podstawie niektórych metadanych w wykonaniu wsadowym właściwość będzie miała ostatni zestaw wartości:

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

Po wykonaniu wsadowym właściwość zachowuje ostateczną wartość %(MetadataValue).

Mimo że partie są uruchamiane niezależnie, ważne jest, aby wziąć pod uwagę różnicę między wsadowaniem docelowym a dzieleniem zadań na partie i wiedzieć, który typ ma zastosowanie do danej sytuacji. Rozważmy poniższy przykład, aby lepiej zrozumieć znaczenie tego rozróżnienia.

Zadania mogą być niejawne, a nie jawne, co może być mylące w przypadku dzielenia zadań na partie zadań niejawnych. Gdy element PropertyGroup lub ItemGroup pojawi się w Targetobiekcie , każda deklaracja właściwości w grupie jest niejawnie traktowana jak oddzielne zadanie CreateProperty lub CreateItem . Oznacza to, że zachowanie jest inne, gdy obiekt docelowy jest wsadowy, a gdy obiekt docelowy nie jest wsadowy (czyli gdy brakuje %(ItemMetadataName) składni w atrybucie Outputs ). Gdy obiekt docelowy jest wsadowy, wykonywane raz na obiekt docelowy, ItemGroup ale gdy obiekt docelowy nie jest wsadowy, niejawne odpowiedniki CreateItem zadań lub CreateProperty zadań są wsadowe przy użyciu przetwarzania wsadowego zadań, więc obiekt docelowy jest wykonywany tylko raz, a każdy element lub właściwość w grupie jest wsadowywane oddzielnie przy użyciu przetwarzania wsadowego zadań.

W poniższym przykładzie pokazano przetwarzanie wsadowe obiektów docelowych i przetwarzanie wsadowe zadań w przypadku, gdy metadane są zmutowane. Rozważ sytuację, w której masz foldery A i B z niektórymi plikami:

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

Teraz przyjrzyj się danych wyjściowych tych dwóch podobnych projektów.

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

Dane wyjściowe to:

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

Teraz usuń Outputs atrybut określony docelowy wsadowy.

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

Dane wyjściowe to:

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

Zwróć uwagę, że nagłówek Test1 jest drukowany tylko raz, ale w poprzednim przykładzie został wydrukowany dwa razy. Oznacza to, że element docelowy nie jest wsadowy. W rezultacie dane wyjściowe są mylące różne.

Przyczyną jest to, że w przypadku korzystania z przetwarzania wsadowego obiektów docelowych każda partia docelowa wykonuje wszystko z własną niezależną kopią wszystkich właściwości i elementów, ale po pominięcia Outputs atrybutu poszczególne wiersze w grupie właściwości są traktowane jako odrębne, potencjalnie wsadowe zadania. W takim przypadku ComponentDir zadanie jest wsadowe (używa %(ItemMetadataName) składni), tak aby w czasie ComponentName wykonywania wiersza obie partie ComponentDir wiersza zostały zakończone, a druga, która została uruchomiona, określiła wartość, jak widać w drugim wierszu.

Funkcje właściwości korzystające z metadanych

Przetwarzanie wsadowe może być kontrolowane przez funkcje właściwości, które zawierają metadane. Przykład:

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

używa Combine metody do łączenia ścieżki folderu głównego ze ścieżką elementu Kompiluj.

Funkcje właściwości mogą nie być wyświetlane w ramach wartości metadanych. Przykład:

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

jest niedozwolona.

Aby uzyskać więcej informacji na temat funkcji właściwości, zobacz Funkcje właściwości.

Przetwarzanie wsadowe elementów w metadanych odwołujące się do siebie

Rozważmy następujący przykład odwoływania się do metadanych z definicji elementu:

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

Należy pamiętać, że zachowanie różni się, gdy jest zdefiniowane poza dowolnym obiektem docelowym i w obrębie elementu docelowego.

Samodzielnie odwołujące się do elementu metadane poza dowolnym obiektem docelowym

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

Odwoływanie się do metadanych jest rozpoznawane dla wystąpienia elementu (bez wpływu na żadne wcześniej zdefiniowane lub utworzone wystąpienia elementów) — co prowadzi do oczekiwanych danych wyjściowych:

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

Samodzielne odwoływanie się do metadanych elementu w obiekcie docelowym

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

Metadane odwołujące się w tym przypadku prowadzą do dzielenia na partie, co daje prawdopodobnie nieoczekiwane i niezamierzone dane wyjściowe:

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

Dla każdego wystąpienia elementu aparat stosuje metadane wszystkich wstępnie istniejących wystąpień elementów (dlatego element MyPath jest pusty dla pierwszego elementu i zawiera b.txt drugi element). W przypadku większej liczby wstępnie istniejących wystąpień prowadzi to do mnożenia bieżącego wystąpienia elementu (dlatego g/h.txt wystąpienie elementu występuje dwa razy na wynikowej liście).

Aby jawnie poinformować o tym, prawdopodobnie niezamierzone zachowanie, późniejsze wersje komunikatu MSB4120o problemie 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]

Jeśli odwołanie samodzielne jest zamierzone, masz kilka opcji w zależności od rzeczywistego scenariusza i dokładnych potrzeb:

Korzystanie z elementu pomocnika i przekształcania

Jeśli chcesz zapobiec zachowaniu przetwarzania wsadowego wywołanego przez odwołanie do metadanych, możesz to osiągnąć, definiując oddzielny element, a następnie wykorzystując operację przekształcania w celu utworzenia wystąpień elementów z żądanymi metadanymi:

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