Поделиться через


Выполнение задач или целевых объектов в пакетах на основе метаданных элементов

MSBuild делит списки элементов на разные категории или пакеты на основе метаданных элементов и запускает целевой объект или задачу один раз с каждым пакетом.

Пакетная обработка задач

Пакетная обработка задач позволяет упростить файлы проекта, предоставив способ разделить списки элементов на разные пакеты и передать каждый из этих пакетов в задачу отдельно. Пакетная обработка означает, что в файле проекта задача и её атрибуты должны быть объявлены только один раз, хотя могут выполняться несколько раз.

Укажите, что MSBuild будет выполнять пакетную обработку с задачей с помощью %(ItemMetaDataName) нотации в одном из атрибутов задачи. В следующем примере список элементов Example разбивается на пакеты на основе значения метаданных элемента Color, и каждый из пакетов передается в MyTask задачу отдельно.

Замечание

Если вы не ссылаетесь на список элементов в другом месте атрибутов задачи или имя метаданных может быть неоднозначным, можно использовать нотацию %(<ItemCollection.ItemMetaDataName>), чтобы полностью определить значение метаданных элемента, используемое для пакетной обработки.

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

Дополнительные примеры пакетной обработки см. в разделе "Метаданные элемента" в пакетной обработке задач.

Целевая пакетная обработка

MSBuild проверяет, актуальны ли входные и выходные данные целевого объекта перед запуском целевого объекта. Если входные и выходные данные актуальны, целевой объект пропускается. Если задача внутри целевого объекта использует пакетную обработку, MSBuild должна определить, актуальны ли входные и выходные данные для каждого пакета элементов. В противном случае целевой элемент выполняется каждый раз, когда он задействован.

В следующем примере показан элемент Target, который содержит атрибут Outputs с нотацией %(ItemMetadataName). MSBuild делит Example список элементов на пакеты, опираясь на Color метаданные элемента, и анализирует временные метки выходных файлов для каждого отдельного пакета. Если выходные данные из пакета не актуальны, целевой объект выполняется. В противном случае целевой объект пропускается.

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

Другой пример пакетной обработки для целевого объекта см. в разделе "Метаданные элемента" в целевом пакетном режиме.

Изменения элементов и свойств

В этом разделе описывается, как понять последствия изменения свойств и /или метаданных элементов при использовании целевой пакетной обработки или пакетной обработки задач.

Так как целевая пакетная обработка и пакетная обработка задач являются двумя разными операциями MSBuild, важно понимать, какая форма пакетной обработки MSBuild используется в каждом случае. Если синтаксис %(ItemMetadataName) пакетной обработки отображается в задаче в целевом объекте, но не в атрибуте целевого объекта, MSBuild использует пакетную обработку задач. Единственным способом указать целевую пакетную обработку является использование синтаксиса пакетной обработки на атрибуте Target, обычно атрибуте Outputs.

При использовании целевой и задачи пакетной обработки, пакеты можно рассматривать как выполняющиеся независимо друг от друга. Все пакеты начинаются с копии одного и того же начального состояния свойств и значений метаданных элемента. Любые изменения значений свойств во время выполнения пакета не видны другим пакетам. Рассмотрим следующий пример:

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

Результат выглядит так:

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

В ItemGroup целевом объекте неявно выполняется задача, а с %(Color)Condition атрибутом выполняется пакетная обработка задач. Существует два пакета: один для красного и другого для синего. Свойство %(NeededColorChange) задано только в том случае, если %(Color) метаданные синими, а параметр влияет только на отдельный элемент, соответствующий условию при запуске синего пакета. Message Атрибут задачи Text не запускает пакетную обработку, несмотря на %(ItemMetadataName) синтаксис, так как он используется внутри преобразования элемента.

Пакеты выполняются независимо, но не параллельно. Это делает разницу при доступе к значениям метаданных, которые изменяются в пакетном выполнении. В случае, когда вы задаете свойство на основе некоторых метаданных в пакетном выполнении, свойство принимает последний набор значений:

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

После пакетного выполнения свойство сохраняет окончательное значение %(MetadataValue).

Хотя пакеты выполняются независимо, важно учитывать разницу между целевым пакетом и пакетной обработкой задач и знать, какой тип применяется к вашей ситуации. Рассмотрим следующий пример, чтобы лучше понять важность этого различия.

Задачи могут быть неявными, а не явными, что может быть запутано при пакетной обработке задач с неявными задачами. Когда элемент PropertyGroup или ItemGroup отображается в Target, каждое объявление свойства в группе неявно рассматривается как отдельная задача CreateProperty или CreateItem. Это означает, что процесс сборки отличается при пакетировании цели, чем при непакетированной цели (т. е. при отсутствии %(ItemMetadataName) синтаксиса в атрибуте Outputs). При пакетном ItemGroup выполнении целевого объекта выполняется один раз на целевой объект, но если целевой объект не пакетен, неявные эквиваленты CreateItemCreateProperty задач пакетируются с помощью пакетной обработки задач, поэтому целевой объект выполняется только один раз, и каждый элемент или свойство в группе выполняется отдельно с помощью пакетирования задач.

В следующем примере показана целевая пакетная обработка и пакетная обработка задач в случае, когда метаданные мутируются. Рассмотрим ситуацию, когда у вас есть папки A и B с некоторыми файлами:

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

Теперь посмотрите на выходные данные этих двух аналогичных проектов.

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

Результат выглядит так:

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

Теперь удалите Outputs атрибут, который указал целевую пакетную обработку.

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

Результат выглядит так:

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

Обратите внимание, что заголовок Test1 печатается только один раз, но в предыдущем примере он был напечатан дважды. Это означает, что целевой объект не пакетирован. И в результате выходные данные запутанно отличаются.

Причина в том, что при использовании целевой пакетной обработки каждый целевой пакет выполняет все в целевом объекте с собственной независимой копией всех свойств и элементов, но при пропуске Outputs атрибута отдельные строки в группе свойств рассматриваются как отдельные, потенциально пакетные задачи. В этом случае ComponentDir задача выполняется пакетно с использованием %(ItemMetadataName) синтаксиса, так что к моменту выполнения ComponentName строки оба пакета ComponentDir строки завершили выполнение, а второй пакет, который работал, определил значение, как показано во второй строке.

Функции свойств с помощью метаданных

Пакетная обработка может управляться функциями свойств, включающими метаданные. Например

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

Combine используется для объединения пути к корневой папке с путем элемента компиляции.

Функции свойств могут не отображаться в значениях метаданных. Например

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

не допускается.

Дополнительные сведения о функциях свойств см. в разделе "Функции свойств".

Группировка элементов в самоссылающихся метаданнах

Рассмотрим следующий пример ссылки на метаданные из определения элемента:

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

Важно отметить, что поведение отличается при определении вне любого целевого объекта и в пределах целевого объекта.

Самоссылающийся объект метаданных вне любого целевого объекта

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

Ссылки на метаданные разрешаются для каждого отдельного экземпляра элемента (не оказывая влияния на ранее определенные или созданные экземпляры элементов), что приводит к ожидаемому результату:

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

Самоссылочный элемент метаданных внутри целевого элемента

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

Ссылки на метаданные в этом случае приводят к пакетной обработке, что приводит к неожиданному и непреднамеренному выводу:

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

Для каждого экземпляра элемента подсистема применяет метаданные всех существующих экземпляров элементов (поэтому MyPath является пустым для первого элемента и содержит b.txt для второго элемента). В случае с большим числом уже существующих экземпляров это поведение приводит к увеличению количества текущего экземпляра элемента (в результате чего экземпляр элемента g/h.txt появляется дважды в результирующем списке).

Чтобы явно сообщить об этом, возможно, непреднамеренном поведении, более поздние версии MSBuild выдают сообщение 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]

Если ссылка на самого себя предусмотрена, у вас есть несколько вариантов в зависимости от фактического сценария и точных потребностей:

  • Сохранение кода и пропуск сообщения
  • Определение элемента за пределами целевого объекта
  • Использование вспомогательного элемента и операции преобразования

Использование вспомогательного элемента и операции преобразования

Если вы хотите предотвратить поведение пакетной обработки, вызванное ссылкой на метаданные, можно добиться этого, определив отдельный элемент, а затем с помощью операции преобразования для создания экземпляров элементов с требуемыми метаданными:

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