Menjalankan tugas atau target dalam batch berdasarkan metadata item

MSBuild membagi daftar item menjadi kategori atau batch yang berbeda berdasarkan metadata item, dan menjalankan target atau tugas satu kali dengan setiap batch.

Pembuatan batch tugas

Pembuatan batch tugas memungkinkan Anda menyederhanakan file proyek dengan menyediakan cara untuk membagi daftar item menjadi beberapa batch yang berbeda dan meneruskan masing-masing batch tersebut ke dalam tugas secara terpisah. Artinya, tugas dan atribut satu file proyek hanya perlu dideklarasikan sekali, meskipun file tersebut dapat dijalankan beberapa kali.

Anda menentukan bahwa Anda ingin MSBuild melakukan pembuatan batch dengan tugas dengan menggunakan notasi %(ItemMetaDataName) di salah satu atribut tugas. Contoh berikut membagi daftar item Example menjadi beberapa batch berdasarkan nilai metadata item Color, dan meneruskan setiap batch ke tugas MyTask secara terpisah.

Catatan

Jika Anda tidak mereferensikan daftar item di tempat lain dalam atribut tugas, atau nama metadata mungkin bersifat ambigu, Anda dapat menggunakan notasi %(<ItemCollection.ItemMetaDataName>) untuk sepenuhnya memenuhi syarat nilai metadata item yang akan digunakan untuk pembuatan batch.

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

Untuk contoh pembuatan batch yang lebih spesifik, lihat Metadata item dalam pembuatan batch tugas.

Pembuatan batch target

MSBuild memeriksa apakah input dan output target sudah diperbarui sebelum target dijalankan. Jika input dan output sudah diperbarui, target akan dilewati. Jika tugas di dalam target menggunakan pembuatan batch, MSBuild perlu menentukan apakah input dan output untuk setiap batch item sudah diperbarui. Jika tidak, target dijalankan setiap kali ada kecocokan.

Contoh berikut menunjukkan elemen Target yang berisi atribut Outputs dengan notasi %(ItemMetadataName). MSBuild akan membagi daftar item Example menjadi beberapa batch berdasarkan Color metadata item, dan menganalisis tanda waktu file output untuk setiap batch. Jika output dari batch tidak diperbarui, target dijalankan. Jika tidak, target dilewati.

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

Untuk contoh lain dari batching target, lihat Metadata item dalam pembuatan batch target.

Mutasi item dan properti

Bagian ini menjelaskan cara memahami efek mengubah properti dan/atau metadata item, saat menggunakan pembuatan batch target atau pembuatan batch tugas.

Karena pembuatan batch target dan pembuatan batch tugas adalah dua operasi MSBuild yang berbeda, penting untuk memahami dengan tepat bentuk pembuatan batch yang digunakan MSBuild dalam setiap kasus. Saat sintaks pembuatan batch %(ItemMetadataName) muncul di tugas dalam target, tetapi tidak dalam atribut pada Target, MSBuild akan menggunakan pembuatan batch tugas. Satu-satunya cara untuk menentukan pembuatan batch target adalah dengan menggunakan sintaks pembuatan batch pada atribut Target, biasanya atribut Outputs.

Dengan pembuatan batch target dan pembuatan batch tugas, batch dapat dipertimbangkan untuk berjalan secara mandiri. Semua batch dimulai dengan salinan status awal nilai metadata properti dan item yang sama. Setiap mutasi nilai properti selama eksekusi batch tidak terlihat oleh batch lain. Pertimbangkan contoh berikut:

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

Outputnya adalah:

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

ItemGroup dalam target secara implisit adalah tugas, dan dengan %(Color) dalam atribut Condition, pembuatan batch tugas akan dilakukan. Ada dua batch: satu untuk merah dan lainnya untuk biru. Properti %(NeededColorChange) hanya diatur jika metadata %(Color) berwarna biru, dan pengaturan hanya memengaruhi item individual yang cocok dengan kondisi saat batch biru dijalankan. Atribut Text dari tugas Message tidak memicu pembuatan batch meskipun sintaksnya %(ItemMetadataName), karena atribut tersebut digunakan di dalam transformasi item.

Batch berjalan secara independen, tetapi tidak secara paralel. Perbedaan tampak saat Anda mengakses nilai metadata yang berubah dalam eksekusi batch. Dalam kasus di mana Anda mengatur properti berdasarkan beberapa metadata dalam eksekusi batch, properti akan mengambil nilai terakhir yang ditetapkan:

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

Setelah eksekusi batch, properti mempertahankan nilai akhir %(MetadataValue).

Meskipun batch berjalan secara independen, penting untuk mempertimbangkan perbedaan antara pembuatan batch target dan pembuatan batch tugas, serta mengetahui jenis mana yang berlaku untuk situasi Anda. Pertimbangkan contoh berikut untuk memahami lebih baik pentingnya perbedaan ini.

Tugas dapat bersifat implisit, bukan eksplisit, yang dapat membingungkan ketika pembuatan batch tugas terjadi dengan tugas implisit. Ketika elemen PropertyGroup atau ItemGroup muncul dalam Target, setiap deklarasi properti dalam grup diperlakukan secara implisit layaknya tugas CreateProperty atau CreateItem terpisah. Artinya, perilakunya berbeda ketika batch target dibuat, dibandingkan ketika batch target tidak dibuat (ketika tidak memiliki sintaks %(ItemMetadataName) dalam atribut Outputs). Ketika batch target dibuat, ItemGroup melakukan eksekusi sekali per target. Namun, ketika batch target tidak dibuat, tugas CreateItem atau CreateProperty implisit yang setara dikelompokkan menggunakan pembuatan batch tugas, sehingga target hanya dijalankan sekali. Setiap item atau properti dalam grup juga dikelompokkan secara terpisah menggunakan pembuatan batch tugas.

Contoh berikut mengilustrasikan pembuatan batch target vs. pembuatan batch tugas dalam kasus di mana metadata dimutasikan. Pertimbangkan situasi di mana Anda memiliki folder A dan B dengan beberapa file:

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

Sekarang lihat output dari dua proyek serupa ini.

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

Outputnya adalah:

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

Sekarang hapus atribut Outputs yang menentukan pembuatan batch target.

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

Outputnya adalah:

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

Perhatikan bahwa judul Test1 hanya dicetak sekali, tetapi judul dicetak dua kali dalam contoh sebelumnya. Artinya, batch target tidak dibuat. Hasilnya adalah output yang sangat berbeda.

Alasannya adalah ketika menggunakan dalam contoh sebelumnya target, setiap batch target menjalankan semua yang ada di target dengan salinan independennya sendiri dari semua properti dan item. Namun, ketika Anda menghilangkan atribut Outputs, baris individual dalam grup properti diperlakukan sebagai tugas yang berbeda dan berpotensi dibuatkan batch-nya. Dalam hal ini, batch tugas ComponentDir dibuat (menggunakan sintaks %(ItemMetadataName)) sehingga saat barus ComponentName dijalankan, kedua batch baris ComponentDir telah selesai dan batch kedua yang berjalan menentukan nilai seperti yang terlihat di baris kedua.

Fungsi properti menggunakan metadata

Pembuatan batch dapat dikontrol oleh fungsi properti yang menyertakan metadata. Contohnya,

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

menggunakan Combine untuk menggabungkan jalur folder pertama dengan jalur item kompilasi.

Fungsi properti mungkin tidak muncul dalam nilai metadata. Contohnya,

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

tidak diizinkan.

Untuk informasi selengkapnya tentang fungsi properti, lihat Fungsi properti.

Pembuatan batch item pada metadata referensi mandiri

Pertimbangkan contoh metadata referensi berikut dari dalam definisi 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>

Penting untuk dicatat bahwa perilaku berbeda ketika didefinisikan di luar target apa pun dan dalam target.

Metadata referensi mandiri item di luar target apa pun

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

Referensi metadata diselesaikan per instans item (tidak terpengaruh oleh instans item yang ditentukan atau dibuat sebelumnya) - yang mengarah ke output yang diharapkan:

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

Metadata referensi mandiri item di dalam target

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

Metadata yang merujuk dalam kasus ini menyebabkan batching, yang menghasilkan output yang mungkin tidak terduga dan tidak diinginkan:

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

Untuk setiap instans item, mesin menerapkan metadata semua instans item yang sudah ada sebelumnya (itulah sebabnya MyPath kosong untuk item pertama dan berisi b.txt untuk item kedua). Dalam kasus instans yang lebih sudah ada sebelumnya, ini menyebabkan perkalian instans item saat ini (itulah sebabnya g/h.txt instans item terjadi dua kali dalam daftar yang dihasilkan).

Untuk secara eksplisit menginformasikan hal ini, mungkin tidak diinginkan, perilaku, versi pesan masalah MSB4120MSBuild yang lebih baru :

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]

Jika referensi mandiri disengaja, Anda memiliki beberapa opsi tergantung pada skenario aktual dan kebutuhan yang tepat:

Menggunakan item pembantu dan transformasi

Jika Anda ingin mencegah perilaku batching yang diinduksi oleh referensi metadata, Anda dapat mencapainya dengan menentukan item terpisah lalu memanfaatkan operasi transformasi untuk membuat instans item dengan metadata yang diinginkan:

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