Partilhar via


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

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

Envio de tarefas em lote

O lote de tarefas permite que você simplifique os arquivos de projeto ao fornecer uma maneira de dividir as listas de itens em lotes diferentes e passar cada um desses lotes para uma tarefa separadamente. Isso significa que um arquivo de projeto só precisa ter a tarefa e seus atributos declarados uma vez, mesmo que seja executado várias vezes.

Especifique que deseja que o MSBuild execute o envio em lote com uma tarefa usando a notação %(ItemMetaDataName) em um dos atributos de tarefa. O exemplo a seguir divide a lista de itens Example em lotes com base no valor do item de metadados Color e passa cada um dos lotes para a tarefa MyTask separadamente.

Observação

Se não houver referência à lista de itens em outro lugar nos atributos de tarefa ou se o nome dos metadados for ambíguo, você pode usar a notação %(<ItemCollection.ItemMetaDataName>) para qualificar totalmente o valor de metadados do item a ser usado para processamento em lotes.

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

Para obter exemplos mais específicos do envio em lote, confira Metadados de item no envio de tarefas em lote.

Envio em lote de destinos

O MSBuild verifica se as entradas e saídas de um destino estão atualizadas antes de executar o destino. Se as entradas e saídas estiverem atualizadas, o destino será ignorado. Se uma tarefa dentro de um destino usar processamento em lote, MSBuild precisa determinar se as entradas e saídas para cada lote de itens estão atualizadas. Caso contrário, o destino será executado sempre que for atingido.

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

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

Para obter outro exemplo de envio de destinos em lote, confira Metadados de item no envio de destinos em lote.

Mutações de item e propriedade

Esta seção descreve como entender os efeitos de alterar propriedades e/ou metadados de item ao usar o envio em lote de destino ou o envio 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 envio em lote %(ItemMetadataName) aparece em uma tarefa em um destino, mas não em um atributo no Destino, o MSBuild usa o envio em lote de tarefas. A única maneira de especificar o envio em lote de destino é usando a sintaxe de envio em lote em um atributo de Destino, geralmente o atributo Outputs.

Com o envio em lote de destino e o envio em lote de tarefas, os lotes podem ser considerados como em execução independente. Todos os lotes começam com uma cópia do mesmo estado inicial de valores de metadados de propriedade e item. Mutações de valores de propriedade durante a execução em lote não são visíveis para outros lotes. 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, envio em lote de tarefas é executado. Há dois lotes: um para vermelho e outro para azul. A propriedade %(NeededColorChange) só será definida se os metadados %(Color) forem azuis, e a configuração afeta apenas o item individual correspondente à condição quando o lote azul foi executado. O atributo Text da tarefa Message não dispara o envio em lote, apesar da sintaxe %(ItemMetadataName), porque é usado dentro de uma transformação de item.

Os lotes são executados de modo independente, mas não em paralelo. Isso faz diferença quando você acessa valores de metadados que são alterados na execução em lote. Se você definisse uma propriedade com base em alguns metadados na execução em lote, a propriedade adotaria o último valor definido:

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

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

Embora os lotes sejam executados de modo 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 elemento PropertyGroup ou ItemGroup aparece em um Target, cada declaração de propriedade no grupo é tratada implicitamente como uma tarefa CreateProperty ou CreateItem separada. Isso significa que o comportamento é diferente quando o destino é agrupado em lote, em comparação com quando o destino não está em lote (ou seja, quando ele não tem a sintaxe %(ItemMetadataName) no Outputs atributo). Quando o destino está em lote, o ItemGroup é executado uma vez por destino, mas quando o destino não está em lote, os equivalentes implícitos das tarefas CreateItem ou CreateProperty são agrupados em lote usando o envio em lote de tarefas, portanto, o destino é executado apenas uma vez, e cada item ou propriedade no grupo é agrupado separadamente usando o envio em lote de tarefas.

O exemplo a seguir ilustra o envio em lote de destino versus o envio em lote de tarefas quando os metadados são modificados. Considere uma situação em que você tem as 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á em lote. E, como resultado, a saída é diferente, o que é confuso.

O motivo é que, ao usar o envio em lote de destino, cada lote de destino executa tudo no destino com a própria cópia independente de todas as propriedades e itens, mas quando você omite o atributo Outputs, as linhas individuais no grupo de propriedades são tratadas como tarefas distintas e potencialmente em lote. Nesse caso, a tarefa ComponentDir é colocada em lote (usa a sintaxe %(ItemMetadataName)), de modo que, quando a linha ComponentName é executada, os dois lotes da linha ComponentDir foram concluídos e o segundo que foi executado determinou o valor, como 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 do item de compilação.

Funções de propriedade podem não aparecer em valores de metadados. Por exemplo,

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

não é permitido.

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

Envio de itens em lote em metadados de autorreferência

Considere o seguinte exemplo de referência a 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 há diferenças de comportamento ao realizar a definição fora de qualquer destino e dentro do destino.

Metadados de autorreferência de item fora de qualquer destino

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

A referência de metadados é resolvida por instância de item (ela não é afetada por nenhuma instância de item definida ou criada anteriormente), resultando na seguinte 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 destino

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

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 (é devido a isso que MyPath está vazio para o primeiro item e contém b.txt para o segundo). No caso de mais instâncias pré-existentes, isso leva à multiplicação da instância de item atual (é devido a isso que a instância de item g/h.txt ocorre duas vezes na lista resultante).

Para oferecer explicitamente informações sobre esse comportamento possivelmente não intencional, as versões mais recentes do MSBuild emitem a seguinte 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 autorreferência for intencional, você terá poucas opções dependendo do cenário real e das necessidades específicas:

Utilizar o item auxiliar e a transformação

Para impedir o comportamento de envio em lote induzido pela referência de metadados, defina um item separado e utilize a operação de transformação a fim de criar instâncias de item com os metadados desejados:

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