Partager via


Exécuter des tâches ou des cibles dans des lots en fonction des métadonnées d’é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.

Regroupement des tâches

Le traitement par lots de tâches 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. Le traitement par lots signifie qu’un fichier projet doit uniquement avoir la tâche et ses attributs déclarés une seule fois, même s’il peut être exécuté plusieurs fois.

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

Note

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 entièrement la valeur des métadonnées d’élément à utiliser pour le traitement par lots.

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

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

Traitement par lots cible

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 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 pour 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 qui contient un TargetOutputs attribut avec la %(ItemMetadataName) notation. MSBuild divise la Example liste d’éléments en lots en fonction des métadonnées de l’élément Color et analyse les horodatages 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>

    <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 cible, consultez les métadonnées d’élément dans le traitement par lots cible.

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 %(ItemMetadataName) de traitement par lots s’affiche 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 des propriétés et des éléments. Toutes les modifications de valeurs de propriété lors de l'exécution par lots 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=

La ItemGroup dans la cible est implicitement une tâche, et avec le %(Color) dans l’attribut Condition, le traitement par lots des 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 %(Color) métadonnées 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 celui-ci est utilisé à l’intérieur d’une transformation d’élément.

Les processus 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 lots, la propriété prend la dernière valeur définie :

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

Après le traitement par lots, 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. Considérez l’exemple suivant pour mieux comprendre l’importance de cette distinction.

Les tâches peuvent être implicites, plutôt que explicites, ce qui peut être déroutant lorsque le traitement par lots de tâches se produit avec des tâches implicites. Lorsqu’un PropertyGroup ou ItemGroup élément apparaît dans un Target, chaque déclaration de propriété du groupe est considérée implicitement comme une tâche CreateProperty ou CreateItem distincte. Ce comportement signifie que l’exécution du processus de construction est différente lorsque la cible est traitée par lot, par rapport à quand la cible n'est pas traitée par lot (autrement dit, lorsqu’elle n’a pas la syntaxe %(ItemMetadataName) dans l’attribut Outputs). Lorsque la cible est traitée en lot, elle ItemGroup s’exécute une fois par cible. Cependant, lorsque la cible n’est pas traitée en lot, les équivalents implicites des tâches CreateItem ou CreateProperty sont traités en lot grâce au traitement par lots de tâches. Ainsi, la cible ne s’exécute qu'une seule fois, et chaque élément ou propriété du groupe est traité séparément avec le 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. Considérez 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 groupée. Et par conséquent, la sortie est différente de manière déroutante.

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 lotées. Dans ce cas, la tâche ComponentDir est regroupée en lot (elle utilise la syntaxe %(ItemMetadataName)), de sorte qu’au moment où la ligne ComponentName s’exécute, les deux lots de la ligne ComponentDir sont complétés, et c’est le second qui a exécuté a déterminé la valeur que l’on peut observer dans la deuxième ligne.

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

Le traitement par lots peut être contrôlé par les 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’accès d’élément Compile.

Les fonctions de propriété peuvent ne pas apparaître dans les 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 lots d'éléments sur les métadonnées auto-référentes

Prenons l’exemple suivant de référencer des 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>

Il est important de noter que le comportement diffère lorsqu’il est défini en dehors de n’importe quelle cible et au sein de la cible.

Métadonnées d'auto-référencement d’élément en dehors de toute cible

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

Le référencement des métadonnées est résolu par instance d’élément (non affecté par les instances d’élément précédemment définies ou créées) - ce qui entraîne une sortie attendue :

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

Métadonnées d'auto-référencement d'élément au sein d'une cible

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

Le référencement des métadonnées dans ce cas conduit à un traitement par lots, ce qui génère potentiellement une sortie inattendue et non intentionnelle.

  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 l’élément MyPath est vide pour le premier élément et contient b.txt pour le deuxième élément). Dans le cas d’instances plus préexistantes, ce comportement entraîne la multiplication de l’instance d’élément actuelle (c’est pourquoi l’instance g/h.txt d’élément se produit deux fois dans la liste résultante).

Pour informer explicitement de ce comportement éventuellement involontaire, les versions ultérieures de MSBuild émettent le message 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 l’auto-référence est intentionnelle, vous avez quelques options en fonction du scénario réel et des besoins exacts :

  • Conserver le code et ignorer le message
  • Définir l’élément en dehors de la cible
  • Utiliser un élément d’assistance et l’opération de transformation

Utiliser un élément d’assistance et l’opération de transformation

Si vous souhaitez 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>
  <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>