Eseguire attività o destinazioni in batch in base ai metadati degli elementi

MSBuild divide gli elenchi di elementi in categorie diverse, o batch, in base ai metadati degli elementi ed esegue una destinazione o un'attività una volta con ogni batch.

Suddivisione in batch delle attività

Suddividere le attività in batch consente di semplificare i file di progetto, dividendo gli elenchi di elementi in diversi batch che vengono poi passati separatamente in un'attività. Ciò significa che per un file di progetto è necessario dichiarare l'attività e i relativi attributi solo una volta, anche se può essere eseguito più volte.

Si specifica che si vuole che MSBuild esegua l'invio in batch con un'attività usando la %(ItemMetaDataName) notazione in uno degli attributi dell'attività. L'esempio seguente suddivide l'elenco di elementi Example in batch in base al valore dei metadati degli elementi Color e passa ogni batch all'attività MyTask separatamente.

Nota

Se non viene fatto riferimento all'elenco di elementi altrove negli attributi dell'attività o il nome dei metadati è ambiguo, è possibile usare la notazione %(<ItemCollection.ItemMetaDataName>) per qualificare completamente il valore dei metadati degli elementi da usare per l'esecuzione in batch.

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

Per esempi più specifici della suddivisione in batch, vedere Metadati degli elementi nella suddivisione in batch delle attività.

Suddivisione in batch della destinazione

MSBuild verifica se gli input e gli output di una destinazione sono aggiornati prima di eseguire la destinazione. Se entrambi gli input e gli output sono aggiornati, la destinazione viene ignorata. Se un'attività all'interno di una destinazione usa l'invio in batch, MSBuild deve determinare se gli input e gli output per ogni batch di elementi sono aggiornati. In caso contrario, la destinazione viene eseguita ogni volta che viene raggiunto.

Nell'esempio seguente viene illustrato un Target elemento che contiene un Outputs attributo con la %(ItemMetadataName) notazione . MSBuild dividerà l'elenco Example di elementi in batch in base ai metadati dell'elemento Color e analizzerà i timestamp dei file di output per ogni batch. Se gli output di un batch non sono aggiornati, la destinazione viene eseguita. In caso contrario, la destinazione viene ignorata.

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

Per un altro esempio di suddivisione in batch della destinazione, vedere Metadati degli elementi nell'esecuzione in batch delle destinazioni.

Mutazioni di elementi e proprietà

Questa sezione descrive come comprendere gli effetti della modifica delle proprietà e/o dei metadati degli elementi quando si usano batch di destinazione o batch di attività.

Poiché l'invio in batch di destinazione e l'invio in batch di attività sono due operazioni MSBuild diverse, è importante comprendere esattamente quale forma di invio in batch viene usata da MSBuild in ogni caso. Quando la sintassi %(ItemMetadataName) di invio in batch viene visualizzata in un'attività in una destinazione, ma non in un attributo nella destinazione, MSBuild usa l'invio in batch di attività. L'unico modo per specificare l'invio in batch di destinazione consiste nell'usare la sintassi batch in un attributo Target, in genere l'attributo Outputs .

Con l'invio in batch di destinazione e l'invio in batch di attività, è possibile considerare che i batch vengano eseguiti in modo indipendente. Tutti i batch iniziano con una copia dello stesso stato iniziale dei valori dei metadati della proprietà e dell'elemento. Eventuali mutazioni dei valori delle proprietà durante l'esecuzione del batch non sono visibili ad altri batch. Si consideri l'esempio seguente:

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

L'output è il seguente:

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

L'oggetto ItemGroup nella destinazione è implicitamente un'attività e con %(Color) nell'attributo viene eseguita l'invio Condition in batch di attività. Esistono due batch: uno per il rosso e l'altro per il blu. La proprietà %(NeededColorChange) viene impostata solo se i %(Color) metadati sono blu e l'impostazione influisce solo sul singolo elemento che corrisponde alla condizione quando è stato eseguito il batch blu. L'attributo Message dell'attività non attiva l'invio in batch, nonostante la %(ItemMetadataName) sintassi, perché viene usata all'interno di Text una trasformazione di elemento.

I batch vengono eseguiti in modo indipendente, ma non in parallelo. Ciò fa differenza quando si accede ai valori dei metadati che cambiano nell'esecuzione in batch. Nel caso in cui si imposta una proprietà in base ad alcuni metadati nell'esecuzione in batch, la proprietà accetta l'ultimo valore impostato:

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

Dopo l'esecuzione del batch, la proprietà mantiene il valore finale di %(MetadataValue).

Anche se i batch vengono eseguiti in modo indipendente, è importante considerare la differenza tra l'invio in batch di destinazione e l'invio in batch di attività e sapere quale tipo si applica alla situazione. Si consideri l'esempio seguente per comprendere meglio l'importanza di questa distinzione.

Le attività possono essere implicite, anziché esplicite, che possono generare confusione quando si verifica l'invio in batch di attività con attività implicite. Quando un PropertyGroup elemento o ItemGroup viene visualizzato in un oggetto Target, ogni dichiarazione di proprietà nel gruppo viene trattata in modo implicito come un'attività CreateProperty o CreateItem separata. Ciò significa che il comportamento è diverso quando la destinazione viene in batch, rispetto a quando la destinazione non è in batch, ovvero quando manca la %(ItemMetadataName) sintassi nell'attributo Outputs . Quando la destinazione viene in batch, l'oggetto ItemGroup viene eseguito una volta per ogni destinazione, ma quando la destinazione non viene in batch, gli equivalenti impliciti delle CreateItem attività vengono CreateProperty inseriti in batch usando l'invio in batch delle attività, quindi la destinazione viene eseguita una sola volta e ogni elemento o proprietà nel gruppo viene eseguito in batch separatamente usando l'invio in batch delle attività.

L'esempio seguente illustra l'invio in batch di destinazione e l'invio in batch di attività nel caso in cui i metadati vengano modificati. Si consideri una situazione in cui sono presenti cartelle A e B con alcuni file:

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

Esaminare ora l'output di questi due progetti simili.

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

L'output è il seguente:

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

Rimuovere ora l'attributo che ha specificato l'invio Outputs in batch di destinazione.

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

L'output è il seguente:

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

Si noti che l'intestazione Test1 viene stampata una sola volta, ma nell'esempio precedente è stata stampata due volte. Ciò significa che la destinazione non è in batch. Di conseguenza, l'output è leggermente diverso.

Il motivo è che quando si usa l'invio in batch di destinazione, ogni batch di destinazione esegue tutti gli elementi nella destinazione con la propria copia indipendente di tutte le proprietà e gli elementi, ma quando si omette l'attributo Outputs , le singole righe del gruppo di proprietà vengono considerate come attività distinte, potenzialmente in batch. In questo caso, l'attività ComponentDir viene in batch (usa la %(ItemMetadataName) sintassi), in modo che al momento dell'esecuzione della ComponentName riga, entrambi i batch della ComponentDir riga siano stati completati e il secondo che ha determinato il valore come illustrato nella seconda riga.

Funzioni delle proprietà che usano i metadati

La suddivisione in batch può essere controllata usando funzioni delle proprietà che includono i metadati. ad esempio:

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

usa Combine per combinare un percorso di cartella radice con un percorso di elemento Compile.

Le funzioni delle proprietà possono non apparire all'interno dei valori dei metadati. ad esempio:

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

non è consentito.

Per altre informazioni sulle funzioni delle proprietà, vedere Funzioni delle proprietà.

Invio in batch di elementi nei metadati con riferimento automatico

Si consideri l'esempio seguente di riferimento ai metadati dall'interno di una definizione di elemento:

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

È importante notare che il comportamento è diverso se definito all'esterno di qualsiasi destinazione e all'interno di una destinazione.

Metadati di riferimento automatico degli elementi all'esterno di qualsiasi destinazione

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

Il riferimento ai metadati viene risolto per ogni istanza dell'elemento (non interessata da istanze di elementi definite in precedenza o create), con conseguente output previsto:

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

Metadati a cui si fa riferimento self-referencing di elementi all'interno di una destinazione

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

Il riferimento ai metadati in questo caso comporta l'invio in batch, che produce un output probabilmente imprevisto e imprevisto:

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

Per ogni istanza dell'elemento, il motore applica i metadati di tutte le istanze di elementi preesistenti( per questo motivo è MyPath vuoto per il primo elemento e contiene b.txt per il secondo elemento). Nel caso di istanze preesistenti, questo comporta la moltiplicazione dell'istanza dell'elemento corrente(ecco perché l'istanza g/h.txt dell'elemento si verifica due volte nell'elenco risultante).

Per informare in modo esplicito su questo problema, possibilmente imprevisto, comportamento, versioni successive del messaggio MSB4120di problema di 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]

Se il self-reference è intenzionale, sono disponibili alcune opzioni a seconda dello scenario effettivo e delle esigenze esatte:

Uso dell'elemento helper e della trasformazione

Se si vuole impedire il comportamento di invio in batch indotto dal riferimento ai metadati, è possibile farlo definendo un elemento separato e quindi sfruttando l'operazione di trasformazione per creare istanze di elemento con i metadati desiderati:

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