Exécuter des tâches ou des cibles dans des lots en fonction des métadonnées de l’élément

MSBuild divise les listes d’éléments en différentes catégories, ou lots, en fonction des métadonnées d’élément et exécute une cible ou une tâche une fois avec chaque lot.

Traitement par lots des tâches

Le traitement de tâches par lots vous permet de simplifier vos fichiers projet en fournissant un moyen de diviser les listes d’éléments en différents lots et de passer chacun de ces lots dans une tâche séparément. Cela signifie qu’une tâche et ses attributs ne doivent être déclarés qu’une seule fois dans un fichier projet, même si elle peut être exécutée plusieurs fois.

Vous spécifiez que MSBuild doit effectuer un traitement par lots avec une tâche en utilisant la notation %(ItemMetaDataName) dans un des attributs de la tâche. L’exemple suivant fractionne la liste d’éléments Example en lots en fonction de la valeur des métadonnées de l’élément Color et passe séparément chacun des lots à la tâche MyTask.

Notes

Si vous ne référencez pas la liste d’éléments ailleurs dans les attributs de tâche, ou si le nom des métadonnées peut être ambigu, vous pouvez utiliser la notation %(<ItemCollection.ItemMetaDataName>) pour qualifier complètement la valeur des métadonnées de l’élément à utiliser pour le traitement par lots.

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

Pour obtenir des exemples de traitement par lots plus spécifiques, consultez Métadonnées d’élément dans le traitement par lots des tâches.

Traitement par lots des cibles

MSBuild vérifie si les entrées et sorties d’une cible sont à jour avant d’exécuter la cible. Si les entrées et les sorties sont à jour, la cible est ignorée. Si une tâche à l’intérieur d’une cible utilise le traitement par lots, MSBuild doit déterminer si les entrées et sorties de chaque lot d’éléments sont à jour. Sinon, la cible est exécutée chaque fois qu’elle est atteinte.

L’exemple suivant montre un élément Target contenant un attribut Outputs avec la notation %(ItemMetadataName). MSBuild divise la liste d’éléments Example en lots en fonction des métadonnées de l’élément Color et analyse les timestamps des fichiers de sortie pour chaque lot. Si les sorties d’un lot ne sont pas à jour, la cible est exécutée. Sinon, la cible est ignorée.

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

Pour obtenir un autre exemple de traitement par lots des cibles, consultez Métadonnées d’élément dans le traitement par lots des cibles.

Mutations d’élément et de propriété

Cette section explique comment comprendre les effets de la modification des propriétés et/ou des métadonnées d’élément, lors de l’utilisation du traitement par lots cible ou du traitement par lots de tâches.

Étant donné que le traitement par lots cible et le traitement par lots de tâches sont deux opérations MSBuild différentes, il est important de comprendre exactement quelle forme de traitement par lot MSBuild utilise dans chaque cas. Lorsque la syntaxe de traitement par lots %(ItemMetadataName) apparaît dans une tâche dans une cible, mais pas dans un attribut sur la cible, MSBuild utilise le traitement par lots de tâches. La seule façon de spécifier le traitement par lots cible consiste à utiliser la syntaxe de traitement par lots sur un attribut Cible, généralement l’attribut Outputs.

Avec le traitement par lot cible et le traitement par lots de tâches, les lots peuvent être considérés comme s’exécutant indépendamment. Tous les lots commencent par une copie de l’état initial des valeurs de métadonnées de propriété et d’élément. Toutes les mutations de valeurs de propriété pendant l’exécution du lot ne sont pas visibles par d’autres lots. Prenons l’exemple suivant :

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

La sortie est la suivante :

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

Dans la cible, ItemGroup est implicitement une tâche et avec %(Color) dans l’attribut Condition, le traitement par lots de tâches est effectué. Il existe deux lots : un pour le rouge et l’autre pour le bleu. La propriété %(NeededColorChange) est définie uniquement si les métadonnées %(Color) sont bleues et que le paramètre affecte uniquement l’élément individuel qui correspond à la condition lors de l’exécution du lot bleu. L’attribut Text de la tâche Message ne déclenche pas de traitement par lots, malgré la syntaxe %(ItemMetadataName), car elle est utilisée à l’intérieur d’une transformation d’élément.

Les lots s’exécutent indépendamment, mais pas en parallèle. Cela fait une différence lorsque vous accédez aux valeurs de métadonnées qui changent dans l’exécution par lots. Dans le cas où vous définissez une propriété en fonction de certaines métadonnées dans l’exécution par lot, la propriété prend la dernière valeur définie :

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

Après l’exécution du lot, la propriété conserve la valeur finale de %(MetadataValue).

Bien que les lots s’exécutent indépendamment, il est important de prendre en compte la différence entre le traitement par lots cible et le traitement par lots de tâches et savoir quel type s’applique à votre situation. Prenez en compte l’exemple suivant pour mieux comprendre l’importance de cette distinction.

Les tâches peuvent être implicites, plutôt qu’explicites, ce qui peut être déroutant lorsque le traitement par lots de tâches se produit avec des tâches implicites. Lorsqu’un élément PropertyGroup ou ItemGroup apparaît dans Target, chaque déclaration de propriété du groupe est implicitement traitée comme une tâche CreateProperty ou CreateItem. Cela signifie que le comportement est différent lorsque la cible est en lots et lorsqu’elle n’est pas en lots (autrement dit, lorsqu’elle n’a pas la syntaxe %(ItemMetadataName) dans l’attribut Outputs). Lorsque la cible est en lots, ItemGroup s’exécute une fois par cible, mais lorsqu’elle n’est pas en lot, les équivalents implicites des tâches CreateItem ou CreateProperty sont mises en lot à l’aide du traitement par lots de tâches, de sorte que la cible s’exécute une seule fois, et chaque élément ou propriété du groupe est traité séparément à l’aide du traitement par lots de tâches.

L’exemple suivant illustre le traitement par lots cible et le traitement par lots de tâches dans le cas où les métadonnées sont mutées. Prenons l’exemple d’une situation où vous avez des dossiers A et B avec certains fichiers :

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

Examinez maintenant la sortie de ces deux projets similaires.

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

La sortie est la suivante :

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

Supprimez maintenant l’attribut Outputs qui a spécifié le traitement par lots cible.

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

La sortie est la suivante :

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

Notez que le titre Test1 n’est imprimé qu’une seule fois, mais dans l’exemple précédent, il a été imprimé deux fois. Cela signifie que la cible n’est pas en lots. Et par conséquent, la sortie est différente.

La raison est que lorsque vous utilisez le traitement par lots cible, chaque lot cible exécute tout dans la cible avec sa propre copie indépendante de toutes les propriétés et éléments, mais lorsque vous omettez l’attribut Outputs, les lignes individuelles du groupe de propriétés sont traitées comme des tâches distinctes, potentiellement par lots. Dans ce cas, la tâche ComponentDir est exécutée par lots (elle utilise la syntaxe %(ItemMetadataName)), de sorte qu’au moment de l’exécution de la ligne ComponentName, les deux lots de la ligne ComponentDir se sont terminés et le deuxième a déterminé la valeur comme indiqué dans la deuxième ligne.

Fonctions de propriété utilisant des métadonnées

Le traitement par lots peut être contrôlé par des fonctions de propriété qui incluent des métadonnées. Par exemple,

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

utilise Combine pour combiner un chemin de dossier racine avec un chemin d’élément de compilation.

Les fonctions de propriété ne peuvent pas apparaître dans des valeurs de métadonnées. Par exemple,

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

n’est pas autorisé.

Pour plus d’informations sur les fonctions de propriété, consultez Fonctions de propriété.

Traitement par lot d’éléments sur des métadonnées avec référence circulaire

Prenons l’exemple suivant de référence de métadonnées à partir d’une définition d’élément :

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

Notez que le comportement diffère selon qu’il est défini en dehors ou à l’intérieur d’une cible.

Métadonnées d’élément avec référence circulaire en dehors de toute cible

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

La référence de métadonnées est résolue par instance d’élément (non affecté par les instances d’élément précédemment définies ou créées), ce qui conduit à la sortie attendue :

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

Métadonnées d’élément avec référence circulaire à l’intérieur d’une cible

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

Dans ce cas, la référence des métadonnées entraîne le traitement par lot, qui génère une sortie éventuellement inattendue et involontaire :

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

Pour chaque instance d’élément, le moteur applique les métadonnées de toutes les instances d’élément préexistantes (c’est pourquoi MyPath est vide pour le premier élément et contient b.txt pour le deuxième élément). Dans le cas d’instances préexistantes, cela entraîne la multiplication de l’instance d’élément actuelle (c’est pourquoi l’instance d’élément g/h.txt se produit deux fois dans la liste résultante).

Pour informer explicitement ce comportement, éventuellement involontaire, les versions ultérieures du message du problème MSBuild 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]

Si la référence circulaire est intentionnelle, vous avez peu d’options selon le scénario réel et les besoins exacts :

Utilisation d’un élément d’assistance et d’une transformation

Si vous voulez empêcher le comportement de traitement par lot induit par la référence de métadonnées, vous pouvez le faire en définissant un élément distinct, puis en utilisant l’opération de transformation pour créer des instances d’élément avec les métadonnées souhaitées :

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