Öğretici: Kod oluşturma için özel görev oluşturma

Bu öğreticide, C# dilinde MSBuild'de kod oluşturmayı işleyen özel bir görev oluşturacak ve ardından görevi bir derlemede kullanacaksınız. Bu örnekte, temizleme ve yeniden oluşturma işlemlerini işlemek için MSBuild'in nasıl kullanılacağı gösterilmektedir. Örnek ayrıca artımlı derlemenin nasıl desteklendiğini de gösterir, böylece kod yalnızca giriş dosyaları değiştiğinde oluşturulur. Belirtilen teknikler, çok çeşitli kod oluşturma senaryoları için geçerlidir. Bu adımlarda, görevi dağıtım için paketlemek için NuGet kullanımı da gösterilir ve öğreticide, sorun giderme deneyimini geliştirmek için BinLog görüntüleyicisini kullanmak için isteğe bağlı bir adım yer alır.

Önkoşullar

Görevler, hedefler ve özellikler gibi MSBuild kavramlarını anlamanız gerekir. Bkz. MSBuild kavramları.

Örnekler, Visual Studio ile birlikte yüklenen ancak ayrı olarak da yüklenebilen MSBuild gerektirir. Bkz. Visual Studio olmadan MSBuild'i indirme.

Kod örneğine giriş

Örnek, ayarlanacak değerleri içeren bir giriş metin dosyası alır ve bu değerleri oluşturan koda sahip bir C# kod dosyası oluşturur. Bu basit bir örnek olsa da, aynı temel teknikler daha karmaşık kod oluşturma senaryolarına uygulanabilir.

Bu öğreticide AppSettingStronglyTyped adlı bir MSBuild özel görevi oluşturacaksınız. Görev, bir dizi metin dosyasını ve her dosyayı aşağıdaki biçime sahip satırlarla okur:

propertyName:type:defaultValue

Kod, tüm sabitleri içeren bir C# sınıfı oluşturur. Bir sorun derlemeyi durdurmalı ve kullanıcıya sorunu tanılamak için yeterli bilgi vermelidir.

Bu öğreticinin tam örnek kodu, GitHub'daki .NET örnekleri deposundaki Özel görev - kod oluşturma bölümündedir.

AppSettingStronglyTyped projesini oluşturma

.NET Standart Sınıf Kitaplığı oluşturun. Çerçeve .NET Standard 2.0 olmalıdır.

.NET Core Komut Satırı'nda paketlenmiş olan tam MSBuild (Visual Studio tarafından kullanılan) ile taşınabilir MSBuild arasındaki farka dikkat edin.

  • Tam MSBuild: MSBuild'in bu sürümü genellikle Visual Studio'da bulunur. .NET Framework üzerinde çalışır. Çözüm veya projenizde Derleme'yi yürütürken Visual Studio bunu kullanır. Bu sürüm, Visual Studio Geliştirici Komut İstemi veya PowerShell gibi bir komut satırı ortamında da kullanılabilir.
  • .NET MSBuild: MSBuild'in bu sürümü .NET Core Komut Satırı'nda paketlenmiştir. .NET Core üzerinde çalışır. Visual Studio bu MSBuild sürümünü doğrudan çağırmaz. Yalnızca Microsoft.NET.Sdk kullanılarak derleyen projeleri destekler.

.NET Framework ile .NET Core gibi başka bir .NET uygulaması arasında kod paylaşmak istiyorsanız, kitaplığınız .NET Standard 2.0'ı hedeflemeli ve .NET Framework üzerinde çalışan Visual Studio içinde çalıştırmak istiyorsunuz. .NET Framework, .NET Standard 2.1'i desteklemez.

AppSettingStronglyTyped MSBuild özel görevini oluşturma

İlk adım, MSBuild özel görevini oluşturmaktır. MSBuild özel görevi yazma hakkında bilgiler aşağıdaki adımları anlamanıza yardımcı olabilir. MSBuild özel görevi, arabirimini uygulayan bir sınıftır ITask .

  1. Microsoft.Build.Utilities.Core NuGet paketine bir başvuru ekleyin ve ardından Microsoft.Build.Utilities.Task'ten türetilen AppSettingStronglyTyped adlı bir sınıf oluşturun.

  2. Üç özellik ekleyin. Bu özellikler, kullanıcıların görevi bir istemci projesinde kullanırken ayarladıkları görevin parametrelerini tanımlar:

     //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; }
    

    Görev SettingFiles'ı işler ve bir sınıfı SettingNamespaceName.SettingClassNameoluşturur. Oluşturulan sınıf, metin dosyasının içeriğine göre bir dizi sabite sahip olur.

    Görev çıkışı, oluşturulan kodun dosya adını veren bir dize olmalıdır:

     // The filename where the class was generated
     [Output]
     public string ClassNameFile { get; set; }
    
  3. Özel bir görev oluşturduğunuzda, öğesinden Microsoft.Build.Utilities.Taskdevralırsınız. Görevi uygulamak için yöntemini geçersiz kılarsınız Execute() . Yöntem, Execute görev başarılı olursa ve false aksi takdirde döndürürtrue. TaskMicrosoft.Build.Framework.ITask bazı ITask üyelerin varsayılan uygulamalarını uygular ve sağlar ve ayrıca bazı günlüğe kaydetme işlevleri sağlar. Özellikle bir sorun oluştuğunda ve görevin bir hata sonucu (false) döndürmesi gerektiğinde, görevi tanılamak ve gidermek için günlüğe durum çıkışı yapmak önemlidir. hatasında sınıfı çağrısı TaskLoggingHelper.LogErroryaparak hataya işaret eder.

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

    Görev API'si, kullanıcıya neyin yanlış gittiğini belirtmeden hatalı döndürülmesine izin verir. Boole kodu yerine geri dönmek !Log.HasLoggedErrors ve bir sorun olduğunda hata kaydetmek en iyisidir.

Günlük hataları

Hataları günlüğe kaydetmenin en iyi yöntemi, hata günlüğe kaydetme sırasında satır numarası ve ayrı bir hata kodu gibi ayrıntıları sağlamaktır. Aşağıdaki kod, metin giriş dosyasını ayrıştırıyor ve yöntemini hatayı oluşturan metin dosyasındaki satır numarasıyla birlikte kullanıyor TaskLoggingHelper.LogError .

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);
}

Önceki kodda gösterilen teknikleri kullanarak, metin giriş dosyasının söz dizimindeki hatalar yararlı tanılama bilgileriyle derleme hataları olarak gösterilir:

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)

Görevinizdeki özel durumları yakaladığınızda yöntemini kullanın TaskLoggingHelper.LogErrorFromException . Bu, örneğin özel durumun oluşturulduğu çağrı yığınını alarak hata çıkışını geliştirir.

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

Oluşturulan kod dosyasının metnini oluşturmak için bu girişleri kullanan diğer yöntemlerin uygulanması burada gösterilmez; bkz . örnek depodaki AppSettingStronglyTyped.cs .

Örnek kod, derleme işlemi sırasında C# kodu oluşturur. Görev diğer tüm C# sınıflarında olduğu gibi, bu öğreticiyi tamamladığınızda özelleştirebilir ve kendi senaryonuz için gereken işlevleri ekleyebilirsiniz.

Konsol uygulaması oluşturma ve özel görevi kullanma

Bu bölümde, görevi kullanan standart bir .NET Core Konsol Uygulaması oluşturacaksınız.

Önemli

Bunu kullanacak aynı MSBuild işleminde bir MSBuild özel görevi oluşturmaktan kaçınmak önemlidir. Yeni proje tamamen farklı bir Visual Studio çözümünde olmalıdır veya yeni proje önceden oluşturulmuş ve standart çıktıdan yeniden yerleştirilmiş bir dll kullanmalıdır.

  1. Yeni bir Visual Studio Çözümünde .NET Konsol projesi MSBuildConsoleExample oluşturun.

    Görevi dağıtmanın normal yolu NuGet paketinden geçer, ancak geliştirme ve hata ayıklama sırasında tüm bilgileri .props.targets doğrudan uygulamanızın proje dosyasına ekleyebilir ve ardından görevi başkalarına dağıttığınızda NuGet biçimine geçebilirsiniz.

  2. Kod oluşturma görevini kullanmak için proje dosyasını değiştirin. Bu bölümdeki kod listesi, göreve başvurduktan, görevin giriş parametrelerini ayarladıktan ve oluşturulan kod dosyasının beklediğiniz gibi kaldırılması için temiz ve yeniden derleme işlemlerini işlemek için hedefleri yazdıktan sonra değiştirilen proje dosyasını gösterir.

    Görevler UsingTask öğesi (MSBuild) kullanılarak kaydedilir. UsingTask öğesi görevi kaydeder; MSBuild'e görevin adını ve görev sınıfını içeren derlemenin nasıl bulunup çalıştırıldığını bildirir. Derleme yolu proje dosyasına göredir.

    , PropertyGroup görevde tanımlanan özelliklere karşılık gelen özellik tanımlarını içerir. Bu özellikler öznitelikler kullanılarak ayarlanır ve görev adı öğe adı olarak kullanılır.

    TaskName , derlemeden başvurulacak görevin adıdır. Bu öznitelik her zaman tam olarak belirtilen ad alanlarını kullanmalıdır. AssemblyFile derlemenin dosya yoludur.

    Görevi çağırmak için, bu durumda GenerateSettinggörevi uygun hedefe ekleyin.

    Hedef ForceGenerateOnRebuild , oluşturulan dosyayı silerek temizleme ve yeniden derleme işlemlerini işler. özniteliği olarak ayarlanarak AfterTargets hedef sonrasında CoreClean çalıştırılacak şekilde CoreCleanayarlanır.

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

    Not

    Gibi CoreCleanbir hedefi geçersiz kılma yerine, bu kod hedefleri (BeforeTarget ve AfterTarget) sıralamak için başka bir yol kullanır. SDK stilindeki projeler, proje dosyasının son satırından sonra hedefleri örtük olarak içeri aktarır; bu, içeri aktarmalarınızı el ile belirtmediğiniz sürece varsayılan hedefleri geçersiz kılamadığınız anlamına gelir. Bkz. Önceden tanımlanmış hedefleri geçersiz kılma.

    Inputs ve Outputs öznitelikleri, artımlı derlemeler için bilgi sağlayarak MSBuild'in daha verimli olmasını sağlar. Girişlerin tarihleri, hedefin çalıştırılması gerekip gerekmediğini veya önceki derlemenin çıkışının yeniden kullanılıp kullanılamayabileceğini görmek için çıkışlarla karşılaştırılır.

  3. Bulunması için tanımlanan uzantıyla giriş metin dosyasını oluşturun. Varsayılan uzantıyı kullanarak kökte aşağıdaki içerikle oluşturun MyValues.mysettings :

     Greeting:string:Hello World!
    
  4. Yeniden derleyin ve oluşturulan dosyanın oluşturulup derlenmesi gerekir. MySetting.generated.cs dosyasının proje klasörünü denetleyin.

  5. MySetting sınıfı yanlış ad alanında olduğundan uygulama ad alanımızı kullanmak için bir değişiklik yapın. Proje dosyasını açın ve aşağıdaki kodu ekleyin:

     <PropertyGroup>
     	<SettingNamespace>MSBuildConsoleExample</SettingNamespace>
     </PropertyGroup>
    
  6. Yeniden derleyin ve sınıfının ad alanında MSBuildConsoleExample olduğunu gözlemleyin. Bu şekilde, oluşturulan sınıf adını (SettingClass ), giriş olarak kullanılacak metin uzantısı dosyalarını (SettingExtensionFile) ve isterseniz bunların konumunu (RootFolder) yeniden tanımlayabilirsiniz.

  7. Program.cs açın ve sabit kodlanmış 'Merhaba Dünya!!' kullanıcı tanımlı sabite:

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

Programı yürütür; oluşturulan sınıftan karşılamayı yazdırır.

(İsteğe bağlı) Derleme işlemi sırasında olayları günlüğe kaydetme

Bir komut satırı komutu kullanarak derlemek mümkündür. Proje klasörüne gidin. İkili günlük oluşturmak için (ikili günlük) seçeneğini kullanacaksınız -bl . İkili günlük, derleme işlemi sırasında neler olduğunu bilmek için yararlı bilgilere sahip olacaktır.

# 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

Her iki komut da MSBuild İkili ve Yapılandırılmış Günlük Görüntüleyicisi ile açılabilir bir günlük dosyası msbuild.binlogoluşturur. seçeneği /t:rebuild , yeniden oluşturma hedefini çalıştırma anlamına gelir. Oluşturulan kod dosyasının yeniden oluşturulmasını zorlar.

Tebrikler! Kod oluşturan bir görev oluşturdunuz ve bunu bir derlemede kullandınız.

Görevi dağıtım için paketleme

Özel görevinizi yalnızca birkaç projede veya tek bir çözümde kullanmanız gerekiyorsa, görevi ham derleme olarak kullanmak tek ihtiyacınız olan şey olabilir, ancak görevinizi başka bir yerde kullanmak veya başkalarıyla paylaşmak için hazırlamanın en iyi yolu NuGet paketidir.

MSBuild Görev paketleri, NuGet kitaplık paketlerinden birkaç önemli farka sahiptir:

  • Bu bağımlılıkları tüketen projeye ifşa etmek yerine kendi derleme bağımlılıklarını paketlemeleri gerekir
  • NuGet'in görevi kullanan herhangi bir pakete derlemeleri eklemesine neden olacağından gerekli derlemeleri bir lib/<target framework> klasöre paketlemez
  • Yalnızca Microsoft.Build derlemelerine karşı derlenmeleri gerekir. Çalışma zamanında bunlar gerçek MSBuild altyapısı tarafından sağlanacaktır ve bu nedenle pakete dahil edilmesi gerekmez
  • MSBuild'in Görevin bağımlılıklarını (özellikle yerel bağımlılıkları) tutarlı bir şekilde yüklemesine yardımcı olan özel .deps.json bir dosya oluştururlar

Bu hedeflerin tümünü gerçekleştirmek için, standart proje dosyasında aşina olabileceğiniz değerlerin üzerinde ve ötesinde birkaç değişiklik yapmanız gerekir.

NuGet paketi oluşturma

NuGet paketi oluşturmak, özel görevinizi başkalarına dağıtmanın önerilen yoludur.

Paketi oluşturmaya hazırlanma

NuGet paketi oluşturmaya hazırlanmak için proje dosyasında bazı değişiklikler yapıp paketi açıklayan ayrıntıları belirtin. Oluşturduğunuz ilk proje dosyası aşağıdaki koda benzer:

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

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

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

</Project>

NuGet paketi oluşturmak için aşağıdaki kodu ekleyerek paketin özelliklerini ayarlayın. Desteklenen MSBuild özelliklerinin tam listesini Paket belgelerinde görebilirsiniz:

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

Bağımlılıkları özel olarak işaretleme

MSBuild görevinizin bağımlılıkları paket içinde paketlenmelidir; bunlar normal paket başvuruları olarak ifade edilemez. Paket, dış kullanıcılara normal bağımlılıklar sunmaz. Bunu yapmak için iki adım gerekir: derlemelerinizi özel olarak işaretleme ve bunları oluşturulan pakete ekleme. Bu örnekte, görevinizin işe bağlı Microsoft.Extensions.DependencyInjection olduğunu varsayacağız, bu nedenle sürümünde 6.0.0öğesine PackageReferenceMicrosoft.Extensions.DependencyInjection ekleyin.

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

Şimdi, bu Görev projesinin her bağımlılığını hem hem de PackageReferenceProjectReference özniteliğiyle PrivateAssets="all" işaretleyin. Bu, NuGet'e bu bağımlılıkları proje tüketenlere hiç göstermemelerini söyler. Bağımlılık varlıklarını denetleme hakkında daha fazla bilgiyi NuGet belgelerinden okuyabilirsiniz.

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

Pakete bağımlılıkları paketleme

Bağımlılıklarımızın çalışma zamanı varlıklarını Görev paketine de eklemelisiniz. Bunun iki bölümü vardır: Bağımlılıklarımızı ItemGroup'a BuildOutputInPackage ekleyen bir MSBuild hedefi ve bu BuildOutputInPackage öğelerin düzenini denetleyen birkaç özellik. NuGet belgelerinde bu işlem hakkında daha fazla bilgi edinebilirsiniz.

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

Microsoft.Build.Utilities.Core derlemesini paketlemeyin

Yukarıda açıklandığı gibi, bu bağımlılık çalışma zamanında MSBuild tarafından sağlanacaktır, bu nedenle paketi paketlememiz gerekmez. Bunu yapmak için özniteliğini ExcludeAssets="Runtime"PackageReference öğesine ekleyin

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

deps.json dosyası oluşturma ve ekleme

deps.json dosyası, bağımlılıklarınızın doğru sürümlerinin yüklendiğinden emin olmak için MSBuild tarafından kullanılabilir. Kitaplıklar için varsayılan olarak oluşturulmadığından, dosyanın oluşturulmasına neden olması için bazı MSBuild özellikleri eklemeniz gerekir. Ardından bunu paket çıkışımıza eklemek için paket bağımlılıklarımız için yaptığınıza benzer bir hedef ekleyin.

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

MsBuild özelliklerini ve hedeflerini bir pakete ekleme

Bu bölümün arka planı için özellikler ve hedefler ve ardından NuGet paketine özellikleri ve hedefleri ekleme hakkında bilgi edinin.

Bazı durumlarda, paketinizi kullanan projelere özel derleme hedefleri veya özellikler eklemek isteyebilirsiniz; örneğin derleme sırasında özel bir araç veya işlem çalıştırma. Bunu, dosyaları forma <package_id>.targets veya <package_id>.props projedeki build klasörün içine yerleştirerek yaparsınız.

Proje kök derleme klasöründeki dosyalar tüm hedef çerçeveler için uygun kabul edilir.

Bu bölümde, nuget paketimize dahil edilecek ve başvuran bir projeden otomatik olarak yüklenen görev uygulamasını .props ve .targets dosyalarına bağlayacaksınız.

  1. Görevin appSettingStronglyTyped.csproj proje dosyasına aşağıdaki kodu ekleyin:

     <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. Bir derleme klasörü oluşturun ve bu klasöre iki metin dosyası ekleyin: AppSettingStronglyTyped.props ve AppSettingStronglyTyped.targets. AppSettingStronglyTyped.props, Microsoft.Common.props'un başlarında içeri aktarılır ve daha sonra tanımlanan özellikler kullanılamaz. Bu nedenle, henüz tanımlanmamış özelliklere başvurmaktan kaçının; boş olarak değerlendirilir.

    Directory.Build.targets, NuGet paketlerinden dosya içeri aktarıldıktan .targets sonra Microsoft.Common.targets'tan içeri aktarılır. Bu nedenle, derleme mantığının çoğunda tanımlanan özellikleri ve hedefleri geçersiz kılabilir veya tek tek projelerin ayarladığından bağımsız olarak tüm projeleriniz için özellikleri ayarlayabilir. Bkz. içeri aktarma siparişi.

    AppSettingStronglyTyped.props görevi içerir ve varsayılan değerlerle bazı özellikleri tanımlar:

     <?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. Paket yüklendiğinde AppSettingStronglyTyped.props dosyası otomatik olarak eklenir. Ardından, istemcide kullanılabilir görev ve bazı varsayılan değerler bulunur. Ancak, hiçbir zaman kullanılmaz. Bu kodu uygulamaya koymak için, appSettingStronglyTyped.targets dosyasında bazı hedefler tanımlayın; paket yüklendiğinde otomatik olarak da eklenecektir:

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

    İlk adım, okunacak metin dosyalarını (birden fazla olabilir) temsil eden bir ItemGroup oluşturmaktır ve görev parametremizden bazıları olacaktır. Arama yaptığımız konum ve uzantı için varsayılan değerler vardır, ancak istemci MSBuild proje dosyasındaki özellikleri tanımlayan değerleri geçersiz kılabilirsiniz.

    Ardından iki MSBuild hedefi tanımlayın. ÖNCEDEN tanımlanmış hedefleri geçersiz kılarak MSBuild işlemini genişletiyoruz:

    • BeforeCompile: Amaç, sınıfı oluşturmak ve derlenecek sınıfı dahil etmek için özel görevi çağırmaktır. Bu hedefteki görevler çekirdek derleme yapılmadan önce eklenir. Giriş ve Çıkış alanları artımlı derlemeyle ilgilidir. Tüm çıkış öğeleri güncelse, MSBuild hedefi atlar. Hedefin bu artımlı derlemesi, derlemelerinizin performansını önemli ölçüde artırabilir. Çıkış dosyası giriş dosyası veya dosyalarından aynı yaş veya daha yeniyse, öğe güncel olarak kabul edilir.

    • AfterClean: Amaç, genel bir temizleme işlemi olduktan sonra oluşturulan sınıf dosyasını silmektir. Bu hedefteki görevler, çekirdek temizleme işlevi çağrıldıktan sonra eklenir. Yeniden oluşturma hedefi yürütürken kod oluşturma adımının yinelenmesine zorlar.

NuGet paketini oluşturma

NuGet paketini oluşturmak için Visual Studio'yu kullanabilirsiniz (Çözüm Gezgini'da proje düğümüne sağ tıklayın ve Paketle'yi seçin). Bunu komut satırını kullanarak da yapabilirsiniz. AppSettingStronglyTyped.csproj görev proje dosyasının bulunduğu klasöre gidin ve aşağıdaki komutu yürütür:

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

Tebrikler! \AppSettingStronglyTyped\AppSettingStronglyTyped\AppSettingStronglyTyped.1.0.0.nupkg adlı bir NuGet paketi oluşturdunuz.

Paketin bir uzantısı .nupkg vardır ve sıkıştırılmış bir zip dosyasıdır. Bir zip aracıyla açabilirsiniz. .target ve .props dosyaları klasöründedirbuild. Dosya .dll klasöründedir lib\netstandard2.0\ . Dosya AppSettingStronglyTyped.nuspec kök düzeyindedir.

(İsteğe bağlı) Çoklu hedeflemeyi destekleme

Mümkün olan en geniş kullanıcı tabanını desteklemek için hem (.NET Framework) hem Core de Full (.NET 5 ve üzeri dahil) MSBuild dağıtımlarını desteklemeyi düşünmelisiniz.

'Normal' .NET SDK projeleri için çoklu hedefleme, proje dosyanızda birden çok TargetFrameworks ayarlanması anlamına gelir. Bunu yaptığınızda, her iki TargetFrameworkMonikers için derlemeler tetiklenir ve genel sonuçlar tek bir yapıt olarak paketlenebilir.

MSBuild için tam hikaye bu değildir. MSBuild'in iki birincil sevkiyat aracı vardır: Visual Studio ve .NET SDK. Bunlar çok farklı çalışma zamanı ortamlarıdır; biri .NET Framework çalışma zamanında, diğer çalıştırmalar ise CoreCLR'de çalışır. Bunun anlamı, kodunuz netstandard2.0'ı hedefleyebilirken, görev mantığınızda şu anda kullanılan MSBuild çalışma zamanı türüne bağlı olarak farklılıklar olabileceği anlamına gelir. Pratik olarak, .NET 5.0 ve sonrasında çok sayıda yeni API olduğundan, birden çok TargetFrameworkMoniker için HEM MSBuild görev kaynak kodunuzu birden çok hedefli hale getirmek hem de birden çok MSBuild çalışma zamanı türü için MSBuild hedef mantığınızı çoklu hedeflemek mantıklıdır.

Çoklu hedef için gereken değişiklikler

Birden çok TargetFrameworkMoniker'i (TFM) hedeflemek için:

  1. proje dosyanızı ve net6.0 TFM'lerini kullanacak net472 şekilde değiştirin (ikincisi hedeflemek istediğiniz SDK düzeyine göre değişebilir). .NET Core 3.1 destekten çıkana kadar hedeflemek netcoreapp3.1 isteyebilirsiniz. Bunu yaptığınızda, paket klasörü yapısı olarak tasks/tasks/<TFM>/değişir.

    <TargetFrameworks>net472;net6.0</TargetFrameworks>
    
  2. Dosyalarınızı, görevlerinizi .targets yüklemek için doğru TFM'yi kullanacak şekilde güncelleştirin. Gerekli TFM, yukarıda hangi .NET TFM'yi seçtiğinize göre değişir, ancak bir proje hedeflemesi net472 ve net6.0için aşağıdaki gibi bir özelliğiniz olur:

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

Bu kod, özelliğini etkin barındırma ortamı için ara sunucu olarak kullanır MSBuildRuntimeType . Bu özellik ayarlandıktan sonra, doğru AssemblyFileyüklemek için içinde UsingTask kullanabilirsiniz:

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

Sonraki adımlar

Birçok görev yürütülebilir dosyayı çağırmayı içerir. Bazı senaryolarda, Exec görevini kullanabilirsiniz, ancak Exec görevinin sınırlamaları bir sorunsa, özel bir görev de oluşturabilirsiniz. Aşağıdaki öğretici, daha gerçekçi bir kod oluşturma senaryosuyla her iki seçenekte de yol gösterir: REST API için istemci kodu oluşturmak üzere özel bir görev oluşturma.

İsterseniz özel bir görevi test etmeyi de öğrenebilirsiniz.