Compartilhar via


Executar tarefas ou destinos em lotes com base nos metadados do item

O MSBuild divide listas de itens em diferentes categorias ou lotes, com base em metadados de item, e executa um destino ou tarefa uma vez com cada lote.

Agrupamento de tarefas em lotes

O envio em lote de tarefas permite que você simplifique seus arquivos de projeto fornecendo uma maneira de dividir listas de itens em lotes diferentes e passar cada um desses lotes para uma tarefa separadamente. O envio em lote significa que um arquivo de projeto só precisa ter a tarefa e seus atributos declarados uma vez, embora possa ser executado várias vezes.

Você especifica que o MSBuild deve realizar o processamento em lote com uma tarefa usando a notação %(ItemMetaDataName) em um dos atributos da tarefa. O exemplo a seguir divide a Example lista de itens em lotes com base no valor dos metadados do Color item e passa cada um dos lotes para a MyTask tarefa separadamente.

Observação

Se você não fizer referência à lista de itens em outro lugar nos atributos da tarefa ou o nome dos metadados puder ser ambíguo, você poderá usar a notação %(<ItemCollection.ItemMetaDataName>) para qualificar totalmente o valor de metadados do item a ser usado para envio em lote.

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

Para obter exemplos de processamento em lote mais específicos, consulte metadados de item em envio em lote de tarefas.

Envio em lote de destino

O MSBuild verifica se as entradas e saídas de um destino estão atualizadas antes de executar o destino. Se as entradas e as saídas estiverem atualizadas, o alvo será ignorado. Se uma tarefa dentro de um destino usa o envio em lote, o MSBuild precisa determinar se as entradas e saídas de cada lote de itens estão atualizadas. Caso contrário, o alvo é executado toda vez que é atingido.

O exemplo a seguir mostra um Target elemento que contém um Outputs atributo com a %(ItemMetadataName) notação. O MSBuild divide a lista de itens Example em lotes com base nos metadados do item Color e analisa os timestamps dos arquivos de saída para cada lote. Se as saídas de um lote não estiverem atualizadas, o alvo é executado. Caso contrário, o alvo será pulado.

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

Para obter outro exemplo de agrupamento em lote de destino, consulte Metadados de item no agrupamento em lote de destino.

Mutações de item e propriedades

Esta seção descreve como entender os efeitos da alteração de propriedades e/ou metadados de item, ao usar o empacotamento de destino ou o processamento em lote de tarefas.

Como o envio em lote de destino e o envio em lote de tarefas são duas operações diferentes do MSBuild, é importante entender exatamente qual forma de envio em lote o MSBuild usa em cada caso. Quando a sintaxe de batching %(ItemMetadataName) aparece em uma tarefa em um Target, mas não em um atributo no Target, o MSBuild usa batching de tarefas. A única maneira de especificar o agrupamento de destino é usando a sintaxe de agrupamento em um atributo de destino, geralmente o atributo Outputs.

Com o lote de alvos e o lote de tarefas, os lotes podem ser considerados como executáveis de forma independente. Todos os lotes começam com uma cópia do mesmo estado inicial de valores de metadados de propriedade e item. Quaisquer mutações de valores de propriedade durante a execução de um processo em lote não são visíveis para outros processos. Considere o exemplo a seguir:

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

A saída é:

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

O ItemGroup no destino é implicitamente uma tarefa e, com o %(Color) no atributo Condition, o agrupamento de tarefas é executado. Há dois lotes: um para vermelho e outro para azul. A propriedade %(NeededColorChange) só será definida se o %(Color) metadado for azul, e a configuração afetará apenas o item individual que correspondeu à condição quando o lote azul foi realizado. O atributo Text da tarefa Message não aciona o envio em lote, apesar de a sintaxe %(ItemMetadataName) requerer, por ser usado dentro de uma transformação de itens.

Os lotes são executados independentemente, mas não em paralelo. Isso faz a diferença quando você acessa valores de metadados que são alterados na execução em lote. No caso em que você define uma propriedade com base em alguns metadados na execução em lote, a propriedade levaria o último conjunto de valores:

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

Após a execução em lote, a propriedade mantém o valor final de %(MetadataValue).

Embora os lotes sejam executados de forma independente, é importante considerar a diferença entre o envio em lote de destino e o envio em lote de tarefas e saber qual tipo se aplica à sua situação. Considere o exemplo a seguir para entender melhor a importância dessa distinção.

As tarefas podem ser implícitas, em vez de explícitas, o que pode ser confuso quando o envio em lote de tarefas ocorre com tarefas implícitas. Quando um PropertyGroup ou ItemGroup elemento aparece em um Target, cada declaração de propriedade no grupo é implicitamente tratada como se fosse uma tarefa CreateProperty ou CreateItem separada. Esse comportamento significa que a execução do build é diferente quando o alvo é processado em lote, em comparação com quando o alvo não é processado em lote (ou seja, quando não possui a sintaxe %(ItemMetadataName) no atributo Outputs). Quando o alvo é agrupado, o ItemGroup executa uma vez por alvo, mas quando o alvo não é agrupado, os equivalentes implícitos das tarefas CreateItem ou CreateProperty são agrupados utilizando o agrupamento de tarefas, de modo que o alvo é executado apenas uma vez, e cada item ou propriedade no grupo é agrupado separadamente utilizando o agrupamento de tarefas.

O exemplo a seguir ilustra o envio em lote de destino versus o envio em lote de tarefas no caso em que os metadados são modificados. Considere uma situação em que você tenha pastas A e B com alguns arquivos:

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

Agora, examine a saída desses dois projetos semelhantes.

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

A saída é:

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

Agora remova o atributo Outputs que especificou o envio em lote de destino.

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

A saída é:

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

Observe que o título Test1 é impresso apenas uma vez, mas no exemplo anterior, ele foi impresso duas vezes. Isso significa que o destino não está sendo processado em lote. E, como resultado, a saída é confusamente diferente.

O motivo é que, ao usar o processamento em lotes de destino, cada lote de destino executa todas as operações no destino com sua própria cópia independente de todas as propriedades e itens, mas quando ocorre a omissão do atributo Outputs, as linhas individuais no grupo de propriedades são tratadas como tarefas distintas, potencialmente agrupadas em lotes. Nesse caso, a ComponentDir tarefa é processada em lote (usa a %(ItemMetadataName) sintaxe), de modo que, quando a ComponentName linha é executada, ambos os lotes da ComponentDir linha foram concluídos, e o segundo que foi executado determinou o valor conforme visto na segunda linha.

Funções de propriedade usando metadados

O envio em lote pode ser controlado por funções de propriedade que incluem metadados. Por exemplo

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

usa Combine para combinar um caminho de pasta raiz com um caminho de item compile.

As funções de propriedade podem não aparecer dentro de valores de metadados. Por exemplo

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

não é permitido.

Para obter mais informações sobre funções de propriedade, consulte Funções de propriedade.

Processamento em lote de itens em metadados autorreferentes

Considere o exemplo a seguir de referenciar metadados de dentro de uma definição de item:

<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 observar que o comportamento difere quando definido fora de qualquer destino e dentro do destino.

Metadados de auto-referência de item fora de qualquer alvo

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

A referência de metadados é resolvida por instância de item (não afetada por instâncias de item definidas ou criadas anteriormente) – levando à saída esperada:

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

Metadados de autorreferência de item dentro de um alvo

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

A referência de metadados nesse caso leva ao envio em lote, o que gera uma saída possivelmente inesperada e não intencional:

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

Para cada instância de item, o mecanismo aplica metadados de todas as instâncias de item pré-existentes (é por isso que o MyPath item está vazio para o primeiro item e contém b.txt para o segundo item). No caso de mais instâncias pré-existentes, esse comportamento leva à multiplicação da instância do item atual (é por isso que a g/h.txt instância do item ocorre duas vezes na lista resultante).

Para informar explicitamente sobre esse comportamento possivelmente não intencional, versões posteriores do MSBuild emitem a mensagem 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]

Se a autoreferência for intencional, você tem algumas opções, dependendo do cenário real e das necessidades exatas.

  • Mantenha o código e ignore a mensagem
  • Definir o item fora do alvo
  • Usar um item auxiliar e a operação de transformação

Usar um item auxiliar e a operação de transformação

Se você quiser impedir o comportamento de envio em lote induzido pela referência de metadados, poderá conseguir isso definindo um item separado e, em seguida, usando a operação de transformação para criar instâncias de item com os metadados desejados:

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