Ejecución de tareas o destinos en lotes basados en los metadatos de elementos

MSBuild divide listas de elementos en distintas categorías, o lotes, según los metadatos de esos elementos y, luego, ejecuta un destino o una tarea una vez con cada lote.

Procesamiento por lotes de tareas

El procesamiento por lotes de tareas le permite simplificar los archivos del proyecto porque proporciona una manera de dividir las listas de elementos en lotes diferentes y pasar cada uno de estos lotes a una tarea por separado. Esto significa que un archivo de proyecto solo necesita que la tarea y sus atributos se declaren una vez, aunque se puede ejecutar varias veces.

Para especificar que quiere que MSBuild realice el procesamiento por lotes en una tarea, debe usar la notación %(ItemMetaDataName) en uno de los atributos de la tarea. En el ejemplo siguiente, la lista de elementos Example se divide en lotes de acuerdo con el valor de los metadatos del elemento Color y pasa cada uno de los lotes a la tarea MyTask por separado.

Nota

Si no hace referencia a la lista de elementos en ninguna otra parte de los atributos de la tarea, o si el nombre de los metadatos puede ser ambiguo, puede usar la notación %(<ItemCollection.ItemMetaDataName>) para calificar totalmente el valor de los metadatos del elemento que se utilizará para el procesamiento por 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 obtener ejemplos más específicos de procesamiento por lotes, vea Metadatos de elementos en el procesamiento por lotes de tareas.

Procesamiento por lotes de destinos

MSBuild comprueba si las entradas y las salidas de un destino están actualizadas antes de ejecutar el destino. Si las entradas y salidas están actualizadas, el destino se omite. Si una tarea dentro de un destino usa el procesamiento por lotes, MSBuild debe determinar si las entradas y las salidas de cada lote de elementos están actualizadas. En caso contrario, el destino se ejecuta cada vez que se le llama.

En el ejemplo siguiente se muestra un elemento Target que contiene un atributo Outputs con la notación %(ItemMetadataName). MSBuild dividirá la lista de elementos Example en lotes de acuerdo con los metadatos del elemento Color y analizará las marcas de tiempo de los archivos de salida para cada lote. Si las salidas de un lote no están actualizadas, se ejecuta el destino. En caso contrario, se omite el destino.

<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 obtener otro ejemplo de procesamiento por lotes de destinos, vea Metadatos de elementos en el procesamiento por lotes de destinos.

Mutaciones de elemento y propiedad

En esta sección se describe cómo entender los efectos de cambiar las propiedades y los metadatos de elemento cuando se usa el procesamiento por lotes de destino o el procesamiento por lotes de tareas.

Como el procesamiento por lotes de destino y el procesamiento por lotes de tareas son dos operaciones de MSBuild distintas, es importante entender exactamente qué forma de procesamiento por lotes MSBuild usa en cada caso. Cuando la sintaxis de procesamiento por lotes %(ItemMetadataName) aparece en una tarea en un destino, pero no en un atributo en el destino, MSBuild usa el procesamiento por lotes de tareas. La única manera de especificar el procesamiento por lotes de destino es usar la sintaxis de procesamiento por lotes en un atributo de destino, habitualmente el atributo Outputs.

Con el procesamiento por lotes de destino y el procesamiento por lotes de tareas, se puede considerar que los lotes se ejecutan de forma independiente. Todos los lotes comienzan con una copia del mismo estado inicial de los valores de metadatos de elemento y de propiedad. Cualquier mutación de los valores de propiedad durante la ejecución por lotes no es visible para otros lotes. Considere el ejemplo siguiente:

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

El resultado es el siguiente:

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

El elemento ItemGroup en el destino es implícitamente una tarea y con %(Color) en el atributo Condition, se realiza el procesamiento por lotes de tareas. Hay dos lotes: uno para rojo y otro para azul. La propiedad %(NeededColorChange) solo se establece si los metadatos %(Color) son azul y el valor solo afecta al elemento individual que coincidió con la condición cuando se ejecutó el lote azul. El atributo Text de la tarea Message no desencadena el procesamiento por lotes, a pesar de la sintaxis %(ItemMetadataName), porque se usa dentro de una transformación de elemento.

Los lotes se ejecutan de forma independiente, pero no en paralelo. Esto marca una diferencia cuando se accede a los valores de metadatos que cambian en la ejecución por lotes. En el caso de que se establezca una propiedad basada en algunos metadatos de la ejecución por lotes, la propiedad tomaría el último valor establecido:

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

Después de la ejecución del lote, la propiedad conserva el valor final de %(MetadataValue).

Aunque los lotes se ejecutan de forma independiente, es importante tener en cuenta la diferencia entre el procesamiento por lotes de destino y el procesamiento por lotes de tareas y saber qué tipo se aplica a su situación. Considere el ejemplo para entender mejor la importancia de esta distinción.

Las tareas pueden ser implícitas en lugar de explícitas, lo que puede ser confuso cuando el procesamiento por lotes de tareas se produce con tareas implícitas. Cuando aparece un elemento PropertyGroup o ItemGroup en un Target, cada declaración de propiedad del grupo se trata implícitamente como una tarea CreateProperty o CreateItem independiente. Esto significa que el comportamiento es diferente cuando el destino se procesa por lotes en comparación con cuando no es así (es decir, cuando carece de la sintaxis %(ItemMetadataName) en el atributo Outputs). Cuando el destino se procesa por lotes, el ItemGroup se ejecuta una vez por cada destino, pero cuando no se procesa por lotes, los equivalentes implícitos de las tareas CreateItem o CreateProperty se procesan por lotes mediante el procesamiento por lotes de tareas, por lo que el destino solo se ejecuta una vez y cada elemento o propiedad del grupo se procesa por lotes de manera independiente mediante el procesamiento por lotes de tareas.

En el ejemplo siguiente se muestra el procesamiento por lotes de destino en comparación con el procesamiento por lotes de tareas en el caso de que hayan mutado los metadatos. Considere una situación en la que tiene las carpetas A y B con algunos archivos:

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

Ahora fíjese en la salida de estos dos proyectos similares.

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

El resultado es el siguiente:

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

Ahora, quite el atributo Outputs que especificó el procesamiento por lotes 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>

El resultado es el siguiente:

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

Observe que el encabezado Test1 solo se imprime una vez, pero en el ejemplo anterior, se imprimió dos veces. Esto significa que el destino no se procesa por lotes. Y, como resultado, la salida es confusamente diferente.

La razón es que, cuando se usa el procesamiento por lotes de destino, cada lote de destino ejecuta todo en el destino con su propia copia independiente de todas las propiedades y elementos, pero cuando se omite el atributo Outputs, las líneas individuales del grupo de propiedades se tratan como tareas distintas y potencialmente procesadas por lotes. En este caso, la tarea ComponentDir se procesa por lotes (usa la sintaxis %(ItemMetadataName)), de modo que, en el momento en que se ejecuta la línea ComponentName, se han completado los dos lotes de la línea ComponentDir y la segunda que se ejecutó determinó el valor, tal como se muestra en la segunda línea.

Funciones de propiedad con metadatos

El procesamiento por lotes puede controlarse mediante funciones de propiedad que incluyen metadatos. Por ejemplo,

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

utiliza Combine para combinar una ruta de acceso de directorio raíz con una ruta de acceso de elemento de compilación.

Es posible que las funciones de propiedad no aparezcan en los valores de los metadatos. Por ejemplo,

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

no se permite.

Para obtener más información sobre las funciones de propiedad, vea Funciones de propiedad.

Procesamiento por lotes de elementos en metadatos de referencia automática

Considere el siguiente ejemplo de referencia a metadatos desde dentro de una definición de artículo:

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

Es importante tener en cuenta que el comportamiento varía cuando se define fuera de cualquier destino y dentro del destino.

Metadatos de referencia automática de elementos fuera de cualquier 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>

Las referencias de metadatos se resuelven por instancia de elemento (no se ven afectadas por ninguna instancia de elemento previamente definida o creada), lo que conduce a la salida esperada:

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

Metadatos de referencia automática de elementos dentro de un 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>

Los metadatos que hacen referencia en este caso conducen al procesamiento por lotes, lo que produce posiblemente resultados inesperados e imprevistos:

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

Para cada instancia de elemento, el motor aplica metadatos de todas las instancias de elemento preexistentes (por eso el elemento MyPath está vacío para el primer elemento y contiene b.txt para el segundo elemento). En el caso de más instancias preexistentes, esto conduce a la multiplicación de la instancia de elemento actual (por eso la instancia de elemento g/h.txt aparece dos veces en la lista resultante).

Para informar explícitamente sobre este comportamiento, posiblemente no intencionado, las versiones posteriores de MSBuild emiten el mensaje 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 referencia a sí mismo es intencionada, tiene pocas opciones en función del escenario real y las necesidades exactas:

Uso del elemento auxiliar y la transformación

Si desea evitar el comportamiento de procesamiento por lotes provocado por la referencia de metadatos, puede lograrlo definiendo un elemento independiente y aprovechando la operación de transformación para crear instancias de elemento con los metadatos deseados:

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