Kurz: Vytvoření vlastní úlohy pro generování kódu
V tomto kurzu vytvoříte vlastní úlohu v nástroji MSBuild v jazyce C#, která zpracovává generování kódu, a pak použijete úlohu v sestavení. Tento příklad ukazuje, jak pomocí nástroje MSBuild zpracovat operace čištění a opětovného sestavení. Příklad také ukazuje, jak podporovat přírůstkové sestavení, aby se kód vygeneroval pouze v případě, že se změnily vstupní soubory. Popsané techniky jsou použitelné pro širokou škálu scénářů generování kódu. Kroky také ukazují použití NuGetu k zabalení úlohy pro distribuci a kurz obsahuje volitelný krok pro použití prohlížeče BinLog ke zlepšení prostředí pro řešení potíží.
Požadavky
Měli byste znát koncepty nástroje MSBuild, jako jsou úkoly, cíle a vlastnosti. Viz koncepty nástroje MSBuild.
Příklady vyžadují nástroj MSBuild, který je nainstalován se sadou Visual Studio, ale lze je nainstalovat také samostatně. Viz Stažení nástroje MSBuild bez sady Visual Studio.
Úvod do příkladu kódu
Příklad přebírá vstupní textový soubor obsahující hodnoty, které se mají nastavit, a vytvoří soubor kódu jazyka C# s kódem, který tyto hodnoty vytvoří. I když je to jednoduchý příklad, stejné základní techniky lze použít ve složitějších scénářích generování kódu.
V tomto kurzu vytvoříte vlastní úlohu MSBuild s názvem AppSettingStronglyTyped. Úkol přečte sadu textových souborů a každý soubor s řádky s následujícím formátem:
propertyName:type:defaultValue
Kód vygeneruje třídu jazyka C# se všemi konstantami. Problém by měl zastavit sestavení a dát uživateli dostatek informací k diagnostice problému.
Kompletní ukázkový kód pro tento kurz je na vlastní úloze – generování kódu v úložišti ukázek .NET na GitHubu.
Vytvoření projektu AppSettingStronglyTyped
Vytvořte knihovnu tříd .NET Standard. Architektura by měla být .NET Standard 2.0.
Všimněte si rozdílu mezi úplným nástrojem MSBuild (ten, který Sada Visual Studio používá) a přenosným nástrojem MSBuild, který je součástí příkazového řádku .NET Core.
- Full MSBuild: Tato verze nástroje MSBuild se obvykle nachází v sadě Visual Studio. Spouští se v rozhraní .NET Framework. Visual Studio to používá při spuštění sestavení v řešení nebo projektu. Tato verze je dostupná také v prostředí příkazového řádku, jako je visual Studio Developer Command Prompt nebo PowerShell.
- .NET MSBuild: Tato verze nástroje MSBuild je součástí příkazového řádku .NET Core. Běží na .NET Core. Visual Studio přímo nevyvolá tuto verzi nástroje MSBuild. Podporuje pouze projekty, které se sestavují pomocí sady Microsoft.NET.SDK.
Pokud chcete sdílet kód mezi rozhraním .NET Framework a jakoukoli jinou implementací .NET, jako je .NET Core, měla by vaše knihovna cílit na .NET Standard 2.0 a chcete spustit v sadě Visual Studio, která běží na rozhraní .NET Framework. .NET Framework nepodporuje .NET Standard 2.1.
Vytvoření vlastní úlohy MsBuild AppSettingStronglyTyped
Prvním krokem je vytvoření vlastní úlohy NÁSTROJE MSBuild. Informace o tom, jak napsat vlastní úlohu NÁSTROJE MSBuild, vám můžou pomoct pochopit následující kroky. Vlastní úloha MSBuild je třída, která implementuje ITask rozhraní.
Přidejte odkaz na balíček NuGet Microsoft.Build.Utilities.Core a pak vytvořte třídu s názvem AppSettingStronglyTyped odvozenou z Microsoft.Build.Utilities.Task.
Přidejte tři vlastnosti. Tyto vlastnosti definují parametry úkolu, který uživatelé nastavují při použití úkolu v klientském projektu:
//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; }
Úloha zpracuje SettingFiles a vygeneruje třídu
SettingNamespaceName.SettingClassName
. Vygenerovaná třída bude mít sadu konstant na základě obsahu textového souboru.Výstupem úlohy by měl být řetězec s názvem vygenerovaného kódu:
// The filename where the class was generated [Output] public string ClassNameFile { get; set; }
Při vytváření vlastního úkolu dědíte z Microsoft.Build.Utilities.Task. K implementaci úlohy přepíšete metodu Execute() . Metoda
Execute
vrátítrue
, pokud je úkol úspěšný, afalse
jinak.Task
implementuje Microsoft.Build.Framework.ITask a poskytuje výchozí implementace některýchITask
členů a navíc poskytuje některé funkce protokolování. Pro diagnostiku a řešení potíží s úlohou je důležité výstupní stav protokolu, zejména pokud dojde k problému a úloha musí vrátit výsledek chyby (false
). Při chybě třída signalizuje chybu voláním 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; }
Rozhraní API úloh umožňuje vrátit hodnotu false, což značí selhání bez toho, aby uživatele indikoval, co se nepovedlo. Nejlepší je vrátit
!Log.HasLoggedErrors
místo logického kódu a protokolovat chybu, když se něco nepovede.
Chyby protokolu
Osvědčeným postupem při protokolování chyb je poskytnutí podrobností, jako je číslo řádku a jedinečný kód chyby při protokolování chyby. Následující kód parsuje textový vstupní soubor a používá metodu TaskLoggingHelper.LogError s číslem řádku v textovém souboru, který chybu vytvořil.
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);
}
Pomocí technik zobrazených v předchozím kódu se chyby v syntaxi textového vstupního souboru zobrazují jako chyby sestavení s užitečnými diagnostickými informacemi:
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)
Když zachytíte výjimky v úloze, použijte metodu TaskLoggingHelper.LogErrorFromException . Tím se zlepší výstup chyby, například získáním zásobníku volání, kde byla vyvolán výjimka.
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;
}
Implementace ostatních metod, které tyto vstupy používají k sestavení textu pro vygenerovaný soubor kódu, se zde nezobrazuje; viz AppSettingStronglyTyped.cs v ukázkovém úložišti.
Ukázkový kód vygeneruje kód jazyka C# během procesu sestavení. Úkol je stejný jako jakákoli jiná třída jazyka C#, takže až budete s tímto kurzem hotovi, můžete ho přizpůsobit a přidat jakékoli funkce potřebné pro váš vlastní scénář.
Vygenerování konzolové aplikace a použití vlastní úlohy
V této části vytvoříte standardní konzolovou aplikaci .NET Core, která tuto úlohu používá.
Důležité
Je důležité se vyhnout generování vlastní úlohy MSBuild ve stejném procesu MSBuild, který ho bude využívat. Nový projekt by měl být v kompletním jiném řešení sady Visual Studio nebo nový projekt používá předem vygenerovanou knihovnu DLL a znovu umístěnou ze standardního výstupu.
V novém řešení sady Visual Studio vytvořte projekt konzoly .NET MSBuildConsoleExample.
Normální způsob distribuce úkolu je prostřednictvím balíčku NuGet, ale během vývoje a ladění můžete zahrnout všechny informace přímo
.props
do souboru projektu aplikace a.targets
pak přejít do formátu NuGet při distribuci úkolu ostatním.Upravte soubor projektu tak, aby spotřebovávat úlohu generování kódu. Výpis kódu v této části zobrazuje upravený soubor projektu po odkazování na úkol, nastavení vstupních parametrů pro úkol a zápis cílů pro zpracování operací čištění a opětovného sestavení tak, aby se vygenerovaný soubor kódu odebral tak, jak byste očekávali.
Úlohy se registrují pomocí elementu UsingTask (MSBuild). Element
UsingTask
zaregistruje úlohu; informuje MSBuild název úlohy a jak vyhledat a spustit sestavení, které obsahuje třídu úlohy. Cesta sestavení je relativní vzhledem k souboru projektu.Obsahuje
PropertyGroup
definice vlastností, které odpovídají vlastnostem definovaným v úloze. Tyto vlastnosti jsou nastaveny pomocí atributů a název úlohy se používá jako název elementu.TaskName
je název úlohy, na který se má odkazovat ze sestavení. Tento atribut by měl vždy používat plně zadané obory názvů.AssemblyFile
je cesta k souboru sestavení.Chcete-li vyvolat úkol, přidejte úkol do příslušného cíle v tomto případě
GenerateSetting
.Cíl
ForceGenerateOnRebuild
zpracovává operace vyčištění a opětovného sestavení odstraněním vygenerovaného souboru. Nastaví se tak, aby běžel zaCoreClean
cílem nastavením atributuAfterTargets
naCoreClean
hodnotu .<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>
Poznámka:
Místo přepsání cíle, jako
CoreClean
je například , tento kód používá jiný způsob řazení cílů (BeforeTarget a AfterTarget). Projekty ve stylu sady SDK mají implicitní import cílů za posledním řádkem souboru projektu; to znamená, že nemůžete přepsat výchozí cíle, pokud ručně nezadáte import. Viz Přepsání předdefinovaných cílů.Outputs
AtributyInputs
pomáhají nástroji MSBuild efektivněji tím, že poskytují informace pro přírůstková sestavení. Data vstupů se porovnávají s výstupy, abyste zjistili, jestli je potřeba spustit cíl, nebo jestli je možné znovu použít výstup předchozího sestavení.Vytvořte vstupní textový soubor s příponou definovanou, která se má zjistit. Pomocí výchozího rozšíření vytvořte
MyValues.mysettings
v kořenovém adresáři následující obsah:Greeting:string:Hello World!
Znovu sestavte a vygenerovaný soubor by se měl vytvořit a sestavit. Zkontrolujte složku projektu pro soubor MySetting.generated.cs .
Třída MySetting je v nesprávném oboru názvů, takže teď proveďte změnu pro použití oboru názvů aplikace. Otevřete soubor projektu a přidejte následující kód:
<PropertyGroup> <SettingNamespace>MSBuildConsoleExample</SettingNamespace> </PropertyGroup>
Znovu sestavte a všimněte si, že třída je v
MSBuildConsoleExample
oboru názvů. Tímto způsobem můžete předefinovat vygenerovaný název třídy (SettingClass
), textové přípony souborů (SettingExtensionFile
), které se mají použít jako vstup, a umístění () z nich,RootFolder
pokud chcete.Otevřete Program.cs a změňte pevně zakódovaný kód Hello World!! na uživatelem definovanou konstantu:
static void Main(string[] args) { Console.WriteLine(MySetting.Greeting); }
Spusťte program; vytiskne pozdrav z vygenerované třídy.
(Volitelné) Protokolování událostí během procesu sestavení
Je možné zkompilovat pomocí příkazu příkazového řádku. Přejděte do složky projektu. K vygenerování binárního protokolu použijete -bl
možnost (binární protokol). Binární protokol bude mít užitečné informace, abyste věděli, co se děje během procesu sestavení.
# 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
Oba příkazy generují soubor msbuild.binlog
protokolu, který lze otevřít pomocí msBuild Binary a Strukturovaného prohlížeče protokolů. Tato možnost /t:rebuild
znamená spuštění cíle opětovného sestavení. Vynutí regeneraci vygenerovaného souboru kódu.
Blahopřejeme! Vytvořili jste úlohu, která generuje kód a používá ho v sestavení.
Zabalení úlohy pro distribuci
Pokud potřebujete použít vlastní úkol jenom v několika projektech nebo v jednom řešení, může být potřeba použít úkol jako nezpracované sestavení, ale nejlepší způsob, jak ho připravit na použití jinde nebo ho sdílet s ostatními, je jako balíček NuGet.
Balíčky úloh NÁSTROJE MSBuild mají několik klíčových rozdílů mezi balíčky NuGet knihovny:
- Musí seskupit vlastní závislosti sestavení, místo aby tyto závislosti vystavovaly spotřebě projektu.
- Nezabalí žádná požadovaná sestavení do
lib/<target framework>
složky, protože by to způsobilo zahrnutí sestavení NuGet do jakéhokoli balíčku, který využívá úlohu. - Stačí je zkompilovat pouze s sestaveními Microsoft.Build – za běhu je poskytne skutečný modul MSBuild, takže není nutné do balíčku zahrnout.
- Vygenerují speciální
.deps.json
soubor, který msBuildu pomáhá načíst závislosti úlohy (zejména nativní závislosti) konzistentním způsobem.
Abyste dosáhli všech těchto cílů, musíte provést několik změn standardního souboru projektu nad rámec těch, které možná znáte.
Vytvoření balíčku NuGet
Vytvoření balíčku NuGet se doporučuje distribuovat vlastní úlohu ostatním.
Příprava na vygenerování balíčku
Chcete-li se připravit na vygenerování balíčku NuGet, proveďte některé změny v souboru projektu, abyste zadali podrobnosti popisované balíček. Počáteční projektový soubor, který jste vytvořili, se podobá následujícímu kódu:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.0.0" />
</ItemGroup>
</Project>
Pokud chcete vygenerovat balíček NuGet, přidejte následující kód, který nastaví vlastnosti balíčku. Úplný seznam podporovaných vlastností nástroje MSBuild najdete v dokumentaci k sadě Pack:
<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>
Označení závislostí jako soukromých
Závislosti úlohy MSBuild musí být zabaleny uvnitř balíčku; nemohou být vyjádřeny jako normální odkazy na balíčky. Balíček nezpřístupní externím uživatelům žádné běžné závislosti. Provede se to dvěma kroky: označí sestavení jako soukromá a ve skutečnosti je vloží do vygenerovaného balíčku. V tomto příkladu předpokládáme, že váš úkol závisí na Microsoft.Extensions.DependencyInjection
práci, takže přidejte PackageReference
do Microsoft.Extensions.DependencyInjection
verze 6.0.0
.
<ItemGroup>
<PackageReference
Include="Microsoft.Build.Utilities.Core"
Version="17.0.0" />
<PackageReference
Include="Microsoft.Extensions.DependencyInjection"
Version="6.0.0" />
</ItemGroup>
Teď označte každou závislost tohoto projektu úkolu, a ProjectReference
to jak s atributem, tak PackageReference
i s atributemPrivateAssets="all"
. To nuGetu řekne, aby tyto závislosti nezpřístupnil vůbec pro využívání projektů. Další informace o řízení prostředků závislostí najdete v dokumentaci 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>
Sbalte závislosti do balíčku.
Do balíčku úloh musíte také vložit prostředky modulu runtime našich závislostí. Existují dvě části: cíl nástroje MSBuild, který přidá naše závislosti do BuildOutputInPackage
skupiny ItemGroup, a několik vlastností, které řídí rozložení těchto BuildOutputInPackage
položek. Další informace o tomto procesu najdete v dokumentaci 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>
Nezkompilujte sestavení Microsoft.Build.Utilities.Core
Jak je popsáno výše, tato závislost bude poskytována samotným nástrojem MSBuild za běhu, takže ji nemusíme sbalit do balíčku. Uděláte to tak, že do PackageReference
něj přidáte ExcludeAssets="Runtime"
atribut.
...
<PackageReference
Include="Microsoft.Build.Utilities.Core"
Version="17.0.0"
PrivateAssets="all"
ExcludeAssets="Runtime"
/>
...
Vygenerování a vložení souboru deps.json
Nástroj MSBuild může použít soubor deps.json k zajištění načtení správných verzí závislostí. Budete muset přidat některé vlastnosti NÁSTROJE MSBuild, aby se soubor vygeneroval, protože se negeneruje ve výchozím nastavení pro knihovny. Pak přidejte cíl, který chcete zahrnout do výstupu balíčku, podobně jako jste to udělali u závislostí balíčku.
<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>
Zahrnutí vlastností a cílů nástroje MSBuild do balíčku
Pro pozadí této části si přečtěte o vlastnostech a cílech a potom, jak zahrnout vlastnosti a cíle do balíčku NuGet.
V některých případech můžete chtít přidat vlastní cíle sestavení nebo vlastnosti v projektech, které využívají váš balíček, například spuštění vlastního nástroje nebo procesu během sestavování. Uděláte to tak, že umístíte soubory do formuláře <package_id>.targets
nebo <package_id>.props
do build
složky v projektu.
Soubory v kořenové složce sestavení projektu jsou považovány za vhodné pro všechny cílové architektury.
V této části připojíte implementaci úkolů a .props
.targets
soubory, které budou zahrnuty v našem balíčku NuGet a automaticky načteny z odkazujícího projektu.
V souboru projektu úkolu přidejte do souboru projektu AppSettingStronglyTyped.csproj následující kód:
<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>
Vytvořte složku sestavení a v této složce přidejte dva textové soubory: AppSettingStronglyTyped.props a AppSettingStronglyTyped.targets. AppSettingStronglyTyped.props se naimportuje dříve v Microsoft.Common.props a vlastnosti definované později nejsou pro něj dostupné. Vyhněte se tedy odkazům na vlastnosti, které ještě nejsou definovány; vyhodnocují se jako prázdné.
Adresář.Build.targets se naimportuje z Microsoft.Common.targets po importu
.targets
souborů z balíčků NuGet. Může tedy přepsat vlastnosti a cíle definované ve většině logiky sestavení nebo nastavit vlastnosti pro všechny projekty bez ohledu na to, co jednotlivé projekty nastaví. Viz pořadí importu.AppSettingStronglyTyped.props obsahuje úlohu a definuje některé vlastnosti s výchozími hodnotami:
<?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>
Při instalaci balíčku se automaticky zahrne soubor AppSettingStronglyTyped.props . Pak má klient k dispozici úlohu a některé výchozí hodnoty. Nikdy se ale nepoužívá. Chcete-li vložit tento kód do akce, definujte některé cíle v souboru AppSettingStronglyTyped.targets , který bude také automaticky zahrnut při instalaci balíčku:
<?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>
Prvním krokem je vytvoření skupiny ItemGroup, která představuje textové soubory (může to být více než jeden) ke čtení a bude to některý z našich parametrů úkolu. Existují výchozí hodnoty pro umístění a rozšíření, kde hledáme, ale můžete přepsat hodnoty, které definují vlastnosti v klientském souboru projektu MSBuild.
Pak definujte dva cíle NÁSTROJE MSBuild. Proces MSBuild rozšiřujeme a přepisujeme předdefinované cíle:
BeforeCompile
: Cílem je volat vlastní úlohu pro vygenerování třídy a zahrnout třídu, která se má zkompilovat. Úkoly v tomto cíli se vloží před dokončení kompilace jádra. Vstupní a výstupní pole souvisejí s přírůstkovým sestavením. Jsou-li všechny výstupní položky aktuální, je cíl nástrojem MSBuild vynechán. Toto přírůstkové sestavení cíle může výrazně zlepšit výkon sestavení. Položka je považována za aktuální, pokud je její výstupní soubor stejně starý nebo novější než její vstupní soubor(y).AfterClean
: Cílem je odstranit vygenerovaný soubor třídy po obecném vyčištění. Úkoly v tomto cíli se vloží po vyvolání základních čistých funkcí. Vynutí opakování kroku generování kódu při spuštění cíle Znovu sestavit.
Vygenerování balíčku NuGet
Pokud chcete vygenerovat balíček NuGet, můžete použít Visual Studio (klikněte pravým tlačítkem na uzel projektu v Průzkumník řešení a vyberte Balíček). Můžete to také provést pomocí příkazového řádku. Přejděte do složky, kde je k dispozici soubor projektu úkolu AppSettingStronglyTyped.csproj , a spusťte následující příkaz:
// -o is to define the output; the following command chooses the current folder.
dotnet pack -o .
Blahopřejeme! Vygenerovali jste balíček NuGet s názvem \AppSettingStronglyTyped\AppSettingStronglyTyped\AppSettingStronglyTyped.1.0.0.nupkg.
Balíček má příponu .nupkg
a je komprimovaný soubor ZIP. Můžete ho otevřít pomocí nástroje zip. Soubory .target
a .props
soubory jsou ve build
složce. Soubor .dll
je ve lib\netstandard2.0\
složce. Soubor AppSettingStronglyTyped.nuspec
je na kořenové úrovni.
(Volitelné) Podpora cílení na více verzí
Měli byste zvážit podporu distribuce Full
MSBuildu (.NET Framework) i Core
(včetně .NET 5 a novějších), aby podporovaly nejširší možnou uživatelskou základnu.
U "normálních" projektů sady .NET SDK znamená multitargeting nastavení více targetFrameworks v souboru projektu. Když to uděláte, sestavení se aktivují pro TargetFrameworkMonikers a celkové výsledky se dají zabalit jako jeden artefakt.
To není celý příběh pro MSBuild. MSBuild má dvě primární přepravní vozidla: Visual Studio a sadu .NET SDK. Jedná se o velmi různá prostředí runtime; jeden běží v modulu runtime rozhraní .NET Framework a další běží na CoreCLR. To znamená, že zatímco váš kód může cílit na netstandard2.0, logika úlohy může mít rozdíly na základě toho, jaký typ modulu runtime MSBuild se právě používá. Prakticky platí, že existuje tolik nových rozhraní API v .NET 5.0 a novějších, dává smysl jak multitargetovat zdrojový kód úlohy MSBuild pro více cílových objektů TargetFrameworkMonikers, tak i multitarget cílovou logiku NÁSTROJE MSBuild pro více typů modulu runtime MSBuild.
Změny vyžadované pro více cílení
Cílení na více objektů TargetFrameworkMonikers (TFM):
Změňte soubor projektu tak, aby používal soubory
net472
net6.0
TFM (druhá možnost se může změnit na základě toho, na jakou úroveň sady SDK chcete cílit). Možná budete chtít cílitnetcoreapp3.1
, dokud nebude podpora .NET Core 3.1. Když to uděláte, struktura složek balíčku se změní ztasks/
natasks/<TFM>/
.<TargetFrameworks>net472;net6.0</TargetFrameworks>
Aktualizujte
.targets
soubory tak, aby k načtení úloh používaly správnou sadu TFM. Požadovaný TFM se změní na základě toho, jaký .NET TFM jste zvolili výše, ale pro cílenínet472
na projekt anet6.0
, budete mít vlastnost jako:
<AppSettingStronglyTyped_TFM Condition=" '$(MSBuildRuntimeType)' != 'Core' ">net472</AppSettingStronglyTyped_TFM>
<AppSettingStronglyTyped_TFM Condition=" '$(MSBuildRuntimeType)' == 'Core' ">net6.0</AppSettingStronglyTyped_TFM>
Tento kód používá MSBuildRuntimeType
vlastnost jako proxy pro aktivní hostitelské prostředí. Jakmile je tato vlastnost nastavená, můžete ji použít k UsingTask
načtení správné AssemblyFile
:
<UsingTask
AssemblyFile="$(MSBuildThisFileDirectory)../tasks/$(AppSettingStronglyTyped_TFM)/AppSettingStronglyTyped.dll"
TaskName="AppSettingStrongTyped.AppSettingStronglyTyped" />
Další kroky
Mnoho úloh zahrnuje volání spustitelného souboru. V některých scénářích můžete použít Úlohu Exec, ale pokud jsou omezení úlohy Exec problém, můžete také vytvořit vlastní úlohu. Následující kurz vás provede oběma možnostmi s realističtějším scénářem generování kódu: vytvoření vlastní úlohy pro generování klientského kódu pro rozhraní REST API.
Nebo se dozvíte, jak otestovat vlastní úlohu.