根據項目中繼資料批次處理工作或目標
MSBuild 會根據項目中繼資料,將項目清單分割成不同的類別或批次,然後針對每個批次一次執行一個目標或工作。
工作批次處理
工作批次處理可讓您將項目清單分割成不同的批次,並分別將各批次傳遞給工作,以簡化專案檔案。 這表示即使工作可能要執行若干次,專案檔也只需要宣告一次工作和其屬性。
您可以在其中一個工作屬性中使用 %(ItemMetaDataName)
標記法,以指定要讓 MSBuild 執行工作的批次處理。 下列範例會根據 Color
項目中繼資料值,將 Example
項目清單分割成批次,並將每個批次個別傳遞給 MyTask
工作。
注意
如果您不需參考工作屬性中其他位置的項目清單,或中繼資料名稱可能模稜兩可,您可以使用 %(<ItemCollection.ItemMetaDataName>) 標記法,來完整限定要用於批次處理的項目中繼資料值。
<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>
如需更具體的批次處理範例,請參閱工作批次處理中的項目中繼資料。
目標批次處理
MSBuild 會在執行目標之前檢查目標的輸入和輸出是否都為最新狀態。 如果輸入和輸出都是最新的,則會略過目標。 如果在目標內的工作會使用批次處理,MSBuild 需要判斷每個批次項目的輸入和輸出是否都為最新狀態。 否則,每次叫用目標時均會執行。
下列範例示範包含 Outputs
屬性與 %(ItemMetadataName)
標記法的 Target
元素。 MSBuild 會根據 Color
項目中繼資料,將 Example
項目清單分割成批次,並分析每個批次的輸出檔時間戳記。 如果批次的輸出不是最新狀態,則會執行目標。 否則,會略過目標。
<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>
如需目標批次處理的其他範例,請參閱目標批次處理中的項目中繼資料。
項目和屬性變動
本節說明在使用目標批次處理或工作批次處理時,如何了解變更屬性和/或項目中繼資料的效果。
由於目標批次處理和工作批次處理是兩個不同的 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
是隱含的工作,而且使用 Condition
屬性中的 %(Color)
,會執行工作批次處理。 有兩個批次:一個是紅色,另一個是藍色。 只有當 %(Color)
中繼資料為藍色時,才會設定屬性 %(NeededColorChange)
,而且設定只會影響執行藍色批次時符合條件的個別項目。 儘管是 %(ItemMetadataName)
語法,Message
工作的 Text
屬性不會觸發批次處理,因為它會在項目轉換內使用。
批次會獨立執行,但是不會平行執行。 當您存取在批次執行中變更的中繼資料值時,這會有所差異。 如果您根據批次執行中的某些中繼資料來設定屬性,屬性會採用最後一個設定值:
<PropertyGroup>
<SomeProperty>%(SomeItem.MetadataValue)</SomeProperty>
</PropertyGroup>
批次執行之後,屬性會保留 %(MetadataValue)
的最終值。
雖然批次獨立執行,但是請務必考慮目標批次處理與工作批次處理之間的差異,並且了解哪種類型適用於您的情況。 請考慮下列範例,以進一步了解此區別的重要性。
工作可以是隱含的,而不是明確的,當工作批次處理與隱含工作一起發生時,可能會造成混淆。 當 PropertyGroup
或 ItemGroup
元素出現在 Target
時,群組中的每個屬性宣告都會隱含地視為個別 createProperty 或 CreateItem 工作。 這表示當目標進行批次處理時與目標未進行批次處理時的行為會不同 (也就是說,當它缺少 Outputs
屬性中的 %(ItemMetadataName)
語法時)。 當目標進行批次處理時,ItemGroup
會針對每個目標執行一次,但是當目標未進行批次處理時,會使用工作批次處理來批次處理 CreateItem
或 CreateProperty
工作的隱含對等項目,因此目標只會執行一次,而且群組中的每個項目或屬性會分別使用工作批次處理來進行批次處理。
下列範例說明在中繼資料變動的情況下,目標批次處理與工作批次處理。 請考慮您有資料夾 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 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>
中繼資料參考會針對每個項目執行個體進行解析 (不受任何先前已定義或已建立的項目執行個體影響) - 導致預期的輸出:
i=[a/b.txt;c/d.txt;g/h.txt]
i->MyPath=[b.txt;d.txt;h.txt]
項目自我參考目標內的中繼資料
<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>
在此案例中的中繼資料參考會導致批次處理,這會產生可能非預期和意料之外的輸出:
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 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>