Tutorial: Membuat tugas kustom untuk pembuatan kode

Dalam tutorial ini, Anda akan membuat tugas kustom di MSBuild di C# yang menangani pembuatan kode, lalu Anda akan menggunakan tugas dalam build. Contoh ini menunjukkan cara menggunakan MSBuild untuk menangani operasi yang bersih dan membangun operasi kembali. Contohnya juga menunjukkan cara mendukung build inkremental, sehingga kode hanya dihasilkan ketika file input telah berubah. Teknik yang ditunjukkan berlaku untuk berbagai skenario pembuatan kode. Langkah-langkah ini juga menunjukkan penggunaan NuGet untuk mengemas tugas distribusi, dan tutorial mencakup langkah opsional untuk menggunakan penampil BinLog untuk meningkatkan pengalaman pemecahan masalah.

Prasyarat

Anda harus memiliki pemahaman tentang konsep MSBuild seperti tugas, target, dan properti. Lihat konsep MSBuild.

Contohnya memerlukan MSBuild, yang diinstal dengan Visual Studio, tetapi juga dapat diinstal secara terpisah. Lihat Mengunduh MSBuild tanpa Visual Studio.

Pengantar contoh kode

Contoh mengambil file teks input yang berisi nilai yang akan diatur, dan membuat file kode C# dengan kode yang membuat nilai-nilai ini. Meskipun itu adalah contoh sederhana, teknik dasar yang sama dapat diterapkan ke skenario pembuatan kode yang lebih kompleks.

Dalam tutorial ini, Anda akan membuat tugas kustom MSBuild bernama AppSettingStronglyTyped. Tugas akan membaca sekumpulan file teks, dan setiap file dengan baris dengan format berikut:

propertyName:type:defaultValue

Kode menghasilkan kelas C# dengan semua konstanta. Masalah harus menghentikan build dan memberi pengguna informasi yang cukup untuk mendiagnosis masalah.

Kode sampel lengkap untuk tutorial ini ada di Tugas kustom - pembuatan kode dalam repositori sampel .NET pada GitHub.

Membuat proyek AppSettingStronglyTyped

Membuat Pustaka Kelas Standar .NET. Kerangka kerja harus .NET Standard 2.0.

Perhatikan perbedaan antara MSBuild penuh (yang Visual Studio gunakan) dan MSBuild portabel, yang dibundel di Baris Perintah .NET Core.

  • MSBuild Lengkap: Versi MSBuild ini biasanya berada di dalam Visual Studio. Berjalan pada .NET Framework. Visual Studio menggunakan ini saat Anda menjalankan Build pada solusi atau proyek Anda. Versi ini juga tersedia dari lingkungan baris perintah, seperti Visual Studio Developer Command Prompt, atau PowerShell.
  • .NET MSBuild: Versi MSBuild ini dibundel di Baris Perintah .NET Core. Ini berjalan pada .NET Core. Visual Studio tidak secara langsung memanggil versi MSBuild ini. Ini hanya mendukung proyek yang membangun menggunakan Microsoft.NET.Sdk.

jika Anda ingin berbagi kode antara .NET Framework dan implementasi .NET lainnya, seperti .NET Core, pustaka Anda harus menargetkan .NET Standard 2.0, dan Anda ingin menjalankan di dalam Visual Studio, yang berjalan pada .NET Framework. .NET Framework tidak mendukung .NET Standard 2.1.

Membuat tugas kustom AppSettingStronglyTyped MSBuild

Langkah pertama adalah membuat tugas kustom MSBuild. Informasi tentang cara menulis tugas kustom MSBuild mungkin membantu Anda memahami langkah-langkah berikut. Tugas kustom MSBuild adalah kelas yang mengimplementasikan antarmuka ITask.

  1. Tambahkan referensi ke paket NuGet Microsoft.Build.Utilities.Core, lalu buat kelas bernama AppSettingStronglyTyped yang berasal dari Microsoft.Build.Utilities.Task.

  2. Tambahkan tiga properti. Properti ini menentukan parameter tugas yang ditetapkan pengguna saat mereka menggunakan tugas dalam proyek klien:

     //The name of the class which is going to be generated
     [Required]
     public string SettingClassName { get; set; }
    
     //The name of the namespace where the class is going to be generated
     [Required]
     public string SettingNamespaceName { get; set; }
    
     //List of files which we need to read with the defined format: 'propertyName:type:defaultValue' per line
     [Required]
     public ITaskItem[] SettingFiles { get; set; }
    

    Tugas memproses SettingFiles dan menghasilkan kelas SettingNamespaceName.SettingClassName. Kelas yang dihasilkan akan memiliki sekumpulan konstanta berdasarkan konten file teks.

    Output tugas harus berupa string yang memberikan nama file kode yang dihasilkan:

     // The filename where the class was generated
     [Output]
     public string ClassNameFile { get; set; }
    
  3. Saat Anda membuat tugas kustom, Anda mewarisi dari Microsoft.Build.Utilities.Task. Untuk mengimplementasikan tugas, Anda mengambil alih metode Execute(). Metode Execute mengembalikan true jika tugas berhasil, dan false sebaliknya. Task mengimplementasikanMicrosoft.Build.Framework.ITask dan menyediakan implementasi default dari beberapa anggota ITask dan juga, menyediakan beberapa fungsionalitas pengelogan. Penting untuk mengeluarkan status ke log untuk mendiagnosis dan memecahkan masalah tugas, terutama jika masalah terjadi dan tugas harus mengembalikan hasil kesalahan (false). Jika terjadi kesalahan, kelas memberi sinyal kesalahan dengan memanggil TaskLoggingHelper.LogError.

     public override bool Execute()
     {
     	//Read the input files and return a IDictionary<string, object> with the properties to be created. 
     	//Any format error it will return false and log an error
     	var (success, settings) = ReadProjectSettingFiles();
     	if (!success)
     	{
     			return !Log.HasLoggedErrors;
     	}
     	//Create the class based on the Dictionary
     	success = CreateSettingClass(settings);
    
     	return !Log.HasLoggedErrors;
     }
    

    Task API memungkinkan pengembalian false, menunjukkan kegagalan, tanpa menunjukkan kepada pengguna apa yang salah. Yang terbaik adalah mengembalikan !Log.HasLoggedErrors alih-alih kode boolean, dan mencatat kesalahan ketika terjadi kesalahan.

Kesalahan log

Praktik terbaik saat mencatat kesalahan adalah memberikan detail seperti nomor baris dan kode kesalahan yang berbeda saat mencatat kesalahan. Kode berikut mengurai file input teks dan menggunakan metode TaskLoggingHelper.LogError dengan nomor baris dalam file teks yang menghasilkan kesalahan.

private (bool, IDictionary<string, object>) ReadProjectSettingFiles()
{
	var values = new Dictionary<string, object>();
	foreach (var item in SettingFiles)
	{
		int lineNumber = 0;

		var settingFile = item.GetMetadata("FullPath");
		foreach (string line in File.ReadLines(settingFile))
		{
			lineNumber++;

			var lineParse = line.Split(':');
			if (lineParse.Length != 3)
			{
				Log.LogError(subcategory: null,
							 errorCode: "APPS0001",
							 helpKeyword: null,
							 file: settingFile,
							 lineNumber: lineNumber,
							 columnNumber: 0,
							 endLineNumber: 0,
							 endColumnNumber: 0,
							 message: "Incorrect line format. Valid format prop:type:defaultvalue");
							 return (false, null);
			}
			var value = GetValue(lineParse[1], lineParse[2]);
			if (!value.Item1)
			{
				return (value.Item1, null);
			}

			values[lineParse[0]] = value.Item2;
		}
	}
	return (true, values);
}

Menggunakan teknik yang ditunjukkan dalam kode sebelumnya, kesalahan dalam sintaks file input teks muncul sebagai kesalahan build dengan informasi diagnostik yang bermanfaat:

Microsoft (R) Build Engine version 17.2.0 for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.

Build started 2/16/2022 10:23:24 AM.
Project "S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild" on node 1 (default targets).
S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\error-prop.setting(1): error APPS0001: Incorrect line format. Valid format prop:type:defaultvalue [S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild]
Done Building Project "S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild" (default targets) -- FAILED.

Build FAILED.

"S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild" (default target) (1) ->
(generateSettingClass target) ->
  S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\error-prop.setting(1): error APPS0001: Incorrect line format. Valid format prop:type:defaultvalue [S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild]

	 0 Warning(s)
	 1 Error(s)

Saat Anda menangkap pengecualian dalam tugas Anda, gunakan metode TaskLoggingHelper.LogErrorFromException. Ini akan meningkatkan output kesalahan, misalnya dengan mendapatkan tumpukan panggilan tempat pengecualian diberikan.

catch (Exception ex)
{
	// This logging helper method is designed to capture and display information
	// from arbitrary exceptions in a standard way.
	Log.LogErrorFromException(ex, showStackTrace: true);
	return false;
}

Implementasi metode lain yang menggunakan input ini untuk membangun teks untuk file kode yang dihasilkan tidak ditampilkan di sini; lihat AppSettingStronglyTyped.cs di repositori sampel.

Kode contoh menghasilkan kode C# selama proses build. Tugas ini seperti kelas C# lainnya, jadi ketika Anda selesai dengan tutorial ini, Anda dapat menyesuaikannya dan menambahkan fungsionalitas apa pun yang diperlukan untuk skenario Anda sendiri.

Membuat aplikasi konsol dan menggunakan tugas kustom

Di bagian ini, Anda akan membuat Aplikasi Konsol .NET Core standar yang menggunakan tugas tersebut.

Penting

Penting untuk menghindari pembuatan tugas kustom MSBuild dalam proses MSBuild yang sama yang akan menggunakannya. Proyek baru harus berada dalam solusi Visual Studio yang berbeda, atau proyek baru menggunakan dll yang telah dibuat sebelumnya dan ditempatkan kembali dari output standar.

  1. Buat proyek Konsol .NET MSBuildConsoleExample di Solusi Visual Studio baru.

    Cara normal untuk mendistribusikan tugas adalah melalui paket NuGet, tetapi selama pengembangan dan penelusuran kesalahan, Anda dapat menyertakan semua informasi pada .props dan .targets secara langsung dalam file proyek aplikasi Anda, lalu pindah ke format NuGet saat Anda mendistribusikan tugas kepada orang lain.

  2. Ubah file proyek untuk menggunakan tugas pembuatan kode. Daftar kode di bagian ini menunjukkan file proyek yang dimodifikasi setelah merujuk tugas, mengatur parameter input untuk tugas, dan menulis target untuk menangani operasi bersih dan membangun kembali operasi sehingga file kode yang dihasilkan dihapus seperti yang Anda harapkan.

    Tugas didaftarkan menggunakan elemen UsingTask (MSBuild). Elemen UsingTask mendaftarkan tugas; ini memberi tahu MSBuild nama tugas dan cara menemukan dan menjalankan perakitan yang berisi kelas tugas. Jalur perakitan relatif terhadap file proyek.

    PropertyGroup Berisi definisi properti yang sesuai dengan properti yang ditentukan dalam tugas. Properti ini diatur menggunakan atribut, dan nama tugas digunakan sebagai nama elemen.

    TaskName adalah nama tugas yang akan dirujuk dari perakitan. Atribut ini harus selalu menggunakan namespace yang ditentukan sepenuhnya. AssemblyFile adalah jalur file dari perakitan.

    Untuk memanggil tugas, tambahkan tugas ke target yang sesuai, dalam hal ini GenerateSetting.

    Target ForceGenerateOnRebuild menangani operasi bersih dan bangun kembali dengan menghapus file yang dihasilkan. Ini diatur untuk berjalan setelah target CoreClean dengan mengatur atribut ke AfterTargetsCoreClean.

     <Project Sdk="Microsoft.NET.Sdk">
     	<UsingTask TaskName="AppSettingStronglyTyped.AppSettingStronglyTyped" AssemblyFile="..\..\AppSettingStronglyTyped\AppSettingStronglyTyped\bin\Debug\netstandard2.0\AppSettingStronglyTyped.dll"/>
    
     	<PropertyGroup>
     		<OutputType>Exe</OutputType>
     		<TargetFramework>net6.0</TargetFramework>
     		<RootFolder>$(MSBuildProjectDirectory)</RootFolder>
     		<SettingClass>MySetting</SettingClass>
     		<SettingNamespace>MSBuildConsoleExample</SettingNamespace>
     		<SettingExtensionFile>mysettings</SettingExtensionFile>
     	</PropertyGroup>
    
     	<ItemGroup>
     		<SettingFiles Include="$(RootFolder)\*.mysettings" />
     	</ItemGroup>
    
     	<Target Name="GenerateSetting" BeforeTargets="CoreCompile" Inputs="@(SettingFiles)" Outputs="$(RootFolder)\$(SettingClass).generated.cs">
     		<AppSettingStronglyTyped SettingClassName="$(SettingClass)" SettingNamespaceName="$(SettingNamespace)" SettingFiles="@(SettingFiles)">
     		<Output TaskParameter="ClassNameFile" PropertyName="SettingClassFileName" />
     		</AppSettingStronglyTyped>
     		<ItemGroup>
     			<Compile Remove="$(SettingClassFileName)" />
     			<Compile Include="$(SettingClassFileName)" />
     		</ItemGroup>
     	</Target>
    
     	<Target Name="ForceReGenerateOnRebuild" AfterTargets="CoreClean">
     		<Delete Files="$(RootFolder)\$(SettingClass).generated.cs" />
     	</Target>
     </Project>
    

    Catatan

    Alih-alih menimpa target seperti CoreClean, kode ini menggunakan cara lain untuk memesan target (BeforeTarget dan AfterTarget). Proyek bergaya SDK memiliki impor implisit target setelah baris terakhir file proyek; ini berarti Anda tidak dapat mengambil alih target default kecuali Anda menentukan impor Anda secara manual. Lihat Mengambil alih target yang telah ditentukan sebelumnya.

    Atribut Inputs dan Outputs membantu MSBuild menjadi lebih efisien dengan memberikan informasi untuk build inkremental. Tanggal input dibandingkan dengan output untuk melihat apakah target perlu dijalankan, atau apakah output dari build sebelumnya dapat digunakan kembali.

  3. Buat file teks input dengan ekstensi yang ditentukan untuk ditemukan. Menggunakan ekstensi default, buat MyValues.mysettings di root, dengan konten berikut:

     Greeting:string:Hello World!
    
  4. Bangun lagi, dan file yang dihasilkan harus dibuat dan dibangun. Periksa folder proyek untuk file MySetting.generated.cs.

  5. Kelas MySetting berada di namespace layanan yang salah, jadi sekarang buat perubahan untuk menggunakan namespace layanan aplikasi kami. Buka file proyek dan tambahkan kode berikut:

     <PropertyGroup>
     	<SettingNamespace>MSBuildConsoleExample</SettingNamespace>
     </PropertyGroup>
    
  6. Bangun kembali, dan amati bahwa kelas berada di MSBuildConsoleExample namespace. Dengan cara ini, Anda dapat menentukan ulang nama kelas yang dihasilkan (SettingClass), file ekstensi teks (SettingExtensionFile) yang akan digunakan sebagai input, dan lokasi (RootFolder) jika Anda mau.

  7. Buka Program.cs dan ubah hardcode 'Halo Dunia!!' ke konstanta yang ditentukan pengguna:

     static void Main(string[] args)
     {
     	Console.WriteLine(MySetting.Greeting);
     }
    

Jalankan program; tindakan ini akan menciptakan salam dari kelas yang dihasilkan.

(Opsional) Mencatat peristiwa selama proses build

Dimungkinkan untuk mengompilasi menggunakan perintah baris perintah. Navigasi ke folder proyek. Anda akan menggunakan opsi (log biner) -bl untuk menghasilkan log biner. Log biner akan memiliki informasi yang berguna untuk mengetahui apa yang terjadi selama proses build.

# Using dotnet MSBuild (run core environment)
dotnet build -bl

# or full MSBuild (run on net framework environment; this is used by Visual Studio)
msbuild -bl

Kedua perintah menghasilkan file log msbuild.binlog, yang dapat dibuka dengan MSBuild Binary dan Structured Log Viewer. Opsi /t:rebuild ini berarti menjalankan target pembangunan ulang. Ini akan memaksa regenerasi file kode yang dihasilkan.

Selamat! Anda telah membangun tugas yang menghasilkan kode, dan menggunakannya dalam build.

Mengemas tugas untuk distribusi

Jika Anda hanya perlu menggunakan tugas kustom Anda dalam beberapa proyek atau dalam satu solusi, mengonsumsi tugas sebagai rakitan mentah mungkin semua yang Anda butuhkan, tetapi cara terbaik untuk menyiapkan tugas Anda untuk menggunakannya di tempat lain atau membagikannya dengan orang lain adalah sebagai paket NuGet.

Paket Tugas MSBuild memiliki beberapa perbedaan utama dari paket NuGet pustaka:

  • Mereka harus menggabungkan dependensi perakitan mereka sendiri, alih-alih mengekspos dependensi tersebut ke proyek yang mengonsumsi
  • Mereka tidak mengemas rakitan yang diperlukan ke folder lib/<target framework>, karena itu akan menyebabkan NuGet menyertakan rakitan dalam paket apa pun yang menggunakan tugas
  • Mereka hanya perlu mengompilasi terhadap rakitan Microsoft.Build - pada runtime ini akan disediakan oleh mesin MSBuild yang sebenarnya dan karena itu tidak perlu disertakan dalam paket
  • Mereka menghasilkan file khusus .deps.json yang membantu MSBuild memuat dependensi Tugas (terutama dependensi asli) secara konsisten

Untuk mencapai semua tujuan ini, Anda harus membuat beberapa perubahan pada file proyek standar di atas dan di luar yang mungkin Anda kenal.

Membuat paket NuGet

Membuat paket NuGet adalah cara yang disarankan untuk mendistribusikan tugas kustom Anda kepada orang lain.

Bersiap untuk menghasilkan paket

Untuk bersiap menghasilkan paket NuGet, buat beberapa perubahan pada file proyek untuk menentukan detail yang menjelaskan paket. File proyek awal yang Anda buat menyerupai kode berikut:

<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<TargetFramework>netstandard2.0</TargetFramework>
	</PropertyGroup>

	<ItemGroup>
		<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.0.0" />
	</ItemGroup>

</Project>

Untuk menghasilkan paket NuGet, tambahkan kode berikut untuk mengatur properti untuk paket. Anda dapat melihat daftar lengkap properti MSBuild yang didukung dalam dokumentasi Paket:

<PropertyGroup>
	... 
	<IsPackable>true</IsPackable>
	<Version>1.0.0</Version>
	<Title>AppSettingStronglyTyped</Title>
	<Authors>Your author name</Authors>
	<Description>Generates a strongly typed setting class base on a text file.</Description>
	<PackageTags>MyTags</PackageTags>
	<Copyright>Copyright ©Contoso 2022</Copyright>
	...
</PropertyGroup>

Menandai dependensi sebagai privat

Dependensi tugas MSBuild Anda harus dibungkus di dalam paket; tidak dapat diekspresikan sebagai referensi paket normal. Paket tidak akan mengekspos dependensi reguler apa pun kepada pengguna eksternal. Ini membutuhkan dua langkah untuk diselesaikan: menandai rakitan Anda sebagai privat dan benar-benar menyematkannya dalam paket yang dihasilkan. Untuk contoh ini, kami akan berasumsi bahwa tugas Anda bergantung pada Microsoft.Extensions.DependencyInjection pekerjaan, jadi tambahkan PackageReference ke Microsoft.Extensions.DependencyInjection pada versi 6.0.0.

<ItemGroup>
	<PackageReference 
		Include="Microsoft.Build.Utilities.Core"
		Version="17.0.0" />
	<PackageReference
		Include="Microsoft.Extensions.DependencyInjection"
		Version="6.0.0" />
</ItemGroup>

Sekarang, tandai setiap dependensi proyek Tugas ini, baik PackageReference dan ProjectReference dengan atribut PrivateAssets="all". Ini akan memberi tahu NuGet untuk tidak mengekspos dependensi ini untuk mengonsumsi proyek sama sekali. Anda dapat membaca selengkapnya tentang mengontrol aset dependensi di dokumentasi NuGet.

<ItemGroup>
	<PackageReference 
		Include="Microsoft.Build.Utilities.Core"
		Version="17.0.0"
		PrivateAssets="all"
	/>
	<PackageReference
		Include="Microsoft.Extensions.DependencyInjection"
		Version="6.0.0"
		PrivateAssets="all"
	/>
</ItemGroup>

Bundel dependensi ke dalam paket

Anda juga harus menyematkan aset runtime dependensi kami ke dalam paket Tugas. Ada dua bagian untuk ini: target MSBuild yang menambahkan dependensi kami ke BuildOutputInPackage ItemGroup, dan beberapa properti yang mengontrol tata letak BuildOutputInPackage item tersebut. Anda dapat mempelajari selengkapnya tentang proses ini di dokumentasi NuGet.

<PropertyGroup>
	...
	<!-- This target will run when MSBuild is collecting the files to be packaged, and we'll implement it below. This property controls the dependency list for this packaging process, so by adding our custom property we hook ourselves into the process in a supported way. -->
	<TargetsForTfmSpecificBuildOutput>
		$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage
	</TargetsForTfmSpecificBuildOutput>
	<!-- This property tells MSBuild where the root folder of the package's build assets should be. Because we are not a library package, we should not pack to 'lib'. Instead, we choose 'tasks' by convention. -->
	<BuildOutputTargetFolder>tasks</BuildOutputTargetFolder>
	<!-- NuGet does validation that libraries in a package are exposed as dependencies, but we _explicitly_ do not want that behavior for MSBuild tasks. They are isolated by design. Therefore we ignore this specific warning. -->
	<NoWarn>NU5100</NoWarn>
	...
</PropertyGroup>

...
<!-- This is the target we defined above. It's purpose is to add all of our PackageReference and ProjectReference's runtime assets to our package output.  -->
<Target
	Name="CopyProjectReferencesToPackage"
	DependsOnTargets="ResolveReferences">
	<ItemGroup>
		<!-- The TargetPath is the path inside the package that the source file will be placed. This is already precomputed in the ReferenceCopyLocalPaths items' DestinationSubPath, so reuse it here. -->
		<BuildOutputInPackage
			Include="@(ReferenceCopyLocalPaths)"
			TargetPath="%(ReferenceCopyLocalPaths.DestinationSubPath)" />
	</ItemGroup>
</Target>

Jangan bundel rakitan Microsoft.Build.Utilities.Core

Sebagaimana sudah dibahas sebelumnya, dependensi ini akan disediakan oleh MSBuild sendiri saat runtime, jadi kita tidak perlu membundelnya ke dalam paket. Untuk melakukannya, tambahkan atribut ExcludeAssets="Runtime" ke PackageReference untuk atribut tersebut

...
<PackageReference 
	Include="Microsoft.Build.Utilities.Core"
	Version="17.0.0"
	PrivateAssets="all"
	ExcludeAssets="Runtime"
/>
...

Membuat dan menyematkan file deps.json

File deps.json dapat digunakan oleh MSBuild untuk memastikan versi dependensi Anda yang benar dimuat. Anda harus menambahkan beberapa properti MSBuild untuk menyebabkan file dihasilkan, karena tidak dihasilkan secara default untuk pustaka. Kemudian, tambahkan target untuk menyertakannya dalam output paket kami, mirip dengan bagaimana Anda melakukannya untuk dependensi paket kami.

<PropertyGroup>
	...
	<!-- Tell the SDK to generate a deps.json file -->
	<GenerateDependencyFile>true</GenerateDependencyFile>
	...
</PropertyGroup>

...
<!-- This target adds the generated deps.json file to our package output -->
<Target
		Name="AddBuildDependencyFileToBuiltProjectOutputGroupOutput"
		BeforeTargets="BuiltProjectOutputGroup"
		Condition=" '$(GenerateDependencyFile)' == 'true'">

	 <ItemGroup>
		<BuiltProjectOutputGroupOutput
			Include="$(ProjectDepsFilePath)"
			TargetPath="$(ProjectDepsFileName)"
			FinalOutputPath="$(ProjectDepsFilePath)" />
	</ItemGroup>
</Target>

Menyertakan properti dan target MSBuild dalam paket

Untuk latar belakang di bagian ini, baca tentang properti dan target lalu cara menyertakan properti dan target dalam paket NuGet.

Dalam beberapa kasus, Anda mungkin ingin menambahkan target atau properti build kustom dalam proyek yang menggunakan paket Anda, seperti menjalankan alat atau proses kustom selama build. Anda melakukan ini dengan menempatkan file dalam formulir <package_id>.targets atau <package_id>.props di dalam folder build dalam proyek.

File dalam folder build akar proyek dianggap cocok untuk semua kerangka kerja target.

Di bagian ini, Anda akan menyambungkan implementasi tugas dalam file .props dan .targets, yang akan disertakan dalam paket NuGet kami dan secara otomatis dimuat dari proyek referensi.

  1. Dalam file proyek tugas, AppSettingStronglyTyped.csproj, tambahkan kode berikut:

     <ItemGroup>
     	<!-- these lines pack the build props/targets files to the `build` folder in the generated package.
     		by convention, the .NET SDK will look for build\<Package Id>.props and build\<Package Id>.targets
     		for automatic inclusion in the build. -->
     	<Content Include="build\AppSettingStronglyTyped.props" PackagePath="build\" />
     	<Content Include="build\AppSettingStronglyTyped.targets" PackagePath="build\" />
     </ItemGroup>
    
  2. Buat folder build dan di folder tersebut, tambahkan dua file teks: AppSettingStronglyTyped.props dan AppSettingStronglyTyped.targets. AppSettingStronglyTyped.props diimpor lebih awal di Microsoft.Common.props, dan properti yang ditentukan nanti tidak tersedia untuk itu. Jadi, hindari mengacu pada properti yang belum ditentukan; mereka akan mengevaluasi ke kosong.

    Directory.Build.targets diimpor dari Microsoft.Common.targets setelah mengimpor file .targets dari paket NuGet. Jadi, ia dapat mengambil alih properti dan target yang ditentukan di sebagian besar logika build, atau mengatur properti untuk semua proyek Anda terlepas dari apa yang ditetapkan oleh masing-masing proyek. Lihat pesanan impor.

    AppSettingStronglyTyped.props menyertakan tugas dan menentukan beberapa properti dengan nilai default:

     <?xml version="1.0" encoding="utf-8" ?>
     <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
     <!--defining properties interesting for my task-->
     <PropertyGroup>
     	<!--The folder where the custom task will be present. It points to inside the nuget package. -->
     	<_AppSettingsStronglyTyped_TaskFolder>$(MSBuildThisFileDirectory)..\tasks\netstandard2.0</_AppSettingsStronglyTyped_TaskFolder>
     	<!--Reference to the assembly which contains the MSBuild Task-->
     	<CustomTasksAssembly>$(_AppSettingsStronglyTyped_TaskFolder)\$(MSBuildThisFileName).dll</CustomTasksAssembly>
     </PropertyGroup>
    
     <!--Register our custom task-->
     <UsingTask TaskName="$(MSBuildThisFileName).AppSettingStronglyTyped" AssemblyFile="$(CustomTasksAssembly)"/>
    
     <!--Task parameters default values, this can be overridden-->
     <PropertyGroup>
     	<RootFolder Condition="'$(RootFolder)' == ''">$(MSBuildProjectDirectory)</RootFolder>
     	<SettingClass Condition="'$(SettingClass)' == ''">MySetting</SettingClass>
     	<SettingNamespace Condition="'$(SettingNamespace)' == ''">example</SettingNamespace>
     	<SettingExtensionFile Condition="'$(SettingExtensionFile)' == ''">mysettings</SettingExtensionFile>
     </PropertyGroup>
     </Project>
    
  3. File AppSettingStronglyTyped.props secara otomatis disertakan saat paket diinstal. Kemudian, klien memiliki tugas yang tersedia dan beberapa nilai default. Namun, itu tidak pernah digunakan. Untuk menerapkan kode ini, tentukan beberapa target dalam file AppSettingStronglyTyped.targets, yang juga akan secara otomatis disertakan ketika paket diinstal:

     <?xml version="1.0" encoding="utf-8" ?>
     <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    
     <!--Defining all the text files input parameters-->
     <ItemGroup>
     	<SettingFiles Include="$(RootFolder)\*.$(SettingExtensionFile)" />
     </ItemGroup>
    
     <!--A target that generates code, which is executed before the compilation-->
     <Target Name="BeforeCompile" Inputs="@(SettingFiles)" Outputs="$(RootFolder)\$(SettingClass).generated.cs">
     	<!--Calling our custom task-->
     	<AppSettingStronglyTyped SettingClassName="$(SettingClass)" SettingNamespaceName="$(SettingNamespace)" SettingFiles="@(SettingFiles)">
     		<Output TaskParameter="ClassNameFile" PropertyName="SettingClassFileName" />
     	</AppSettingStronglyTyped>
     	<!--Our generated file is included to be compiled-->
     	<ItemGroup>
     		<Compile Remove="$(SettingClassFileName)" />
     		<Compile Include="$(SettingClassFileName)" />
     	</ItemGroup>
     </Target>
    
     <!--The generated file is deleted after a general clean. It will force the regeneration on rebuild-->
     <Target Name="AfterClean">
     	<Delete Files="$(RootFolder)\$(SettingClass).generated.cs" />
     </Target>
     </Project>
    

    Langkah pertama adalah pembuatan ItemGroup, yang mewakili file teks (bisa lebih dari satu) untuk dibaca dan itu akan menjadi beberapa parameter tugas kami. Ada nilai default untuk lokasi dan ekstensi tempat kami mencari, tetapi Anda dapat mengambil alih nilai yang menentukan properti dalam file proyek MSBuild klien.

    Kemudian tentukan dua target MSBuild. Kami memperluas proses MSBuild, mengganti target yang telah ditentukan sebelumnya:

    • BeforeCompile: Tujuannya adalah untuk memanggil tugas kustom untuk menghasilkan kelas dan menyertakan kelas yang akan dikompilasi. Tugas dalam target ini dimasukkan sebelum kompilasi inti selesai. Bidang Input dan Output terkait dengan build inkremental. Jika semua item output sudah diperbarui, MSBuild melewati target. Build inkremental target ini dapat secara signifikan meningkatkan performa build Anda. Item dianggap terbaru jika file outputnya memiliki usia yang sama atau lebih baru dari file atau file inputnya.

    • AfterClean: Tujuannya adalah untuk menghapus file kelas yang dihasilkan setelah pembersihan umum terjadi. Tugas dalam target ini dimasukkan setelah fungsionalitas bersih inti dipanggil. Tindakan ini memaksa langkah pembuatan kode diulang ketika target Pembangunan kembali dijalankan.

Menghasilkan paket NuGet

Untuk menghasilkan paket NuGet, Anda dapat menggunakan Visual Studio (klik kanan pada simpul proyek di Penjelajah Solusi, dan pilih Kemas). Anda juga dapat melakukannya dengan menggunakan baris perintah. Navigasi ke folder tempat file proyek tugas AppSettingStronglyTyped.csproj berada, dan jalankan perintah berikut:

// -o is to define the output; the following command chooses the current folder.
dotnet pack -o .

Selamat! Anda telah membuat paket NuGet bernama \AppSettingStronglyTyped\AppSettingStronglyTyped\AppSettingStronglyTyped.1.0.0.nupkg.

Paket ini memiliki ekstensi .nupkg dan merupakan file zip terkompresi. Anda dapat membukanya dengan alat zip. File .target dan .props ada di folder build. File .dll berada di folder lib\netstandard2.0\. File AppSettingStronglyTyped.nuspec berada di tingkat akar.

(Opsional) Mendukung multitargeting

Anda harus mempertimbangkan Full untuk mendukung distribusi MSBuild (.NET Framework) dan Core (termasuk .NET 5 dan yang lebih baru) untuk mendukung basis pengguna yang paling luas.

Untuk proyek .NET SDK 'normal', multitarget berarti mengatur beberapa TargetFrameworks dalam file proyek Anda. Ketika Anda melakukan ini, build akan dipicu untuk TargetFrameworkMonikers, dan hasil keseluruhan dapat dikemas sebagai artefak tunggal.

Itu bukan cerita lengkap untuk MSBuild. MSBuild memiliki dua kendaraan pengiriman utama: Visual Studio dan .NET SDK. Ini adalah lingkungan runtime yang sangat berbeda; satu berjalan pada runtime .NET Framework, dan lainnya berjalan pada CoreCLR. Artinya, meskipun kode Anda dapat menargetkan netstandard2.0, logika tugas Anda mungkin memiliki perbedaan berdasarkan jenis runtime MSBuild yang saat ini digunakan. Praktis, karena ada begitu banyak API baru di .NET 5.0 dan yang lebih baru, masuk akal untuk melakukan multitarget kode sumber tugas MSBuild Anda untuk beberapa TargetFrameworkMonikers serta multitarget logika target MSBuild Anda untuk beberapa jenis runtime MSBuild.

Perubahan yang diperlukan untuk multitarget

Untuk menargetkan beberapa TargetFrameworkMonikers (TFM):

  1. Ubah file proyek Anda untuk menggunakan net472 TFM dan net6.0 (yang terakhir dapat berubah berdasarkan tingkat SDK mana yang ingin Anda targetkan). Anda mungkin ingin menargetkan netcoreapp3.1 sampai .NET Core 3.1 tidak didukung. Ketika Anda melakukan ini, struktur folder paket berubah dari tasks/ ke tasks/<TFM>/.

    <TargetFrameworks>net472;net6.0</TargetFrameworks>
    
  2. Perbarui file Anda .targets untuk menggunakan TFM yang benar untuk memuat tugas Anda. TFM yang diperlukan akan berubah berdasarkan TFM .NET yang Anda pilih di atas, tetapi untuk penargetan net472 proyek dan net6.0, Anda akan memiliki properti seperti:

<AppSettingStronglyTyped_TFM Condition=" '$(MSBuildRuntimeType)' != 'Core' ">net472</AppSettingStronglyTyped_TFM>
<AppSettingStronglyTyped_TFM Condition=" '$(MSBuildRuntimeType)' == 'Core' ">net6.0</AppSettingStronglyTyped_TFM>

Kode ini menggunakan MSBuildRuntimeType properti sebagai proksi untuk lingkungan hosting aktif. Setelah properti ini diatur, Anda dapat menggunakannya di UsingTask untuk memuat yang benar AssemblyFile:

<UsingTask
    AssemblyFile="$(MSBuildThisFileDirectory)../tasks/$(AppSettingStronglyTyped_TFM)/AppSettingStronglyTyped.dll"
    TaskName="AppSettingStrongTyped.AppSettingStronglyTyped" />

Langkah berikutnya

Banyak tugas melibatkan pemanggilan yang dapat dieksekusi. Dalam beberapa skenario, Anda bisa menggunakan tugas Exec, tetapi jika batasan tugas Exec adalah masalah, Anda juga dapat membuat tugas kustom. Tutorial berikut berjalan melalui kedua opsi dengan skenario pembuatan kode yang lebih realistis: membuat tugas kustom untuk menghasilkan kode klien untuk REST API.

Atau, pelajari cara menguji tugas kustom.