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 Stáhnout 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ý z nich bude obsahovat řádky v následujícím formátu:

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řte projekt 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á, když provedete příkaz Sestavení na vašem ř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, vaše knihovna by měla cílit na .NET Standard 2.0, a chcete spustit Visual Studio, které běží na rozhraní .NET Framework. .NET Framework nepodporuje .NET Standard 2.1.

Zvolte verzi rozhraní MSBuild API, na které chcete odkazovat.

Při kompilaci vlastní úlohy byste měli odkazovat na verzi rozhraní MSBuild API (Microsoft.Build.*), která odpovídá minimální verzi sady Visual Studio nebo sadě .NET SDK, kterou očekáváte. Pokud například chcete podporovat uživatele sady Visual Studio 2019, měli byste sestavovat pomocí MSBuild verze 16.11.

Vytvořte vlastní úlohu MSBuild AppSettingStronglyTyped

Prvním krokem je vytvoření vlastní úlohy MSBuild. Informace o tom, jak napsat vlastní úlohu MSBuild, vám mohou pomoci pochopit následující kroky. Vlastní úloha MSBuild je třída, která implementuje rozhraní ITask.

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

  2. 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; }
    
  3. Při vytváření vlastní úkolu dědíte z Microsoft.Build.Utilities.Task. K implementaci úlohy překryjete metodu Execute(). Metoda Execute vrátí true, pokud je úloha úspěšná, a false jinak. Task implementuje Microsoft.Build.Framework.ITask a poskytuje výchozí implementace některých členů ITask a navíc poskytuje některé funkce protokolování. Je důležité zaznamenávat stav do protokolu pro diagnostiku a řešení problémů s úkolem, zejména pokud dojde k problému a úkol 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 booleovského kódu a zalogovat 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)

Při zachycení výjimek v úloze použijte metodu TaskLoggingHelper.LogErrorFromException. Tím se zlepší výstup chyby, například tím, že se získá zásobník volání, kde byla výjimka vyvolána.

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ář.

Vygenerujte konzolovou aplikaci a použijte vlastní úlohu

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.

  1. 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 o .props a .targets přímo do souboru projektu aplikace a pak se při distribuci úkolu ostatním přesunout do formátu NuGet.

  2. Upravte soubor projektu tak, aby spotřebovala ú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 jsou registrovány pomocí UsingTask element (MSBuild). Element UsingTask zaregistruje úlohu; oznamuje MSBuild název úlohy a jak najít a spouštět sestavení, které obsahuje třídu úlohy. Cesta sestavení je relativní vzhledem k souboru projektu.

    PropertyGroup obsahuje 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 sestavy.

    Chcete-li vyvolat úkol, přidejte úkol do příslušného cíle, v tomto případě GenerateSetting.

    Cíl ForceGenerateOnRebuild se postará o operace vyčištění a opětovného sestavení tak, že odstraní vygenerovaný soubor. Je nastavena tak, že se spustí po cíli CoreClean tím, že nastavíte atribut AfterTargets na CoreClean.

    <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 je CoreClean, tento kód používá alternativní 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ů.

    Atributy Inputs a Outputs pomáhají nástroji MSBuild efektivněji tím, že poskytují informace o přírůstkových sestaveních. 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í.

  3. Vytvořte vstupní textový soubor s příponou, která má být zjištěna. Pomocí výchozího rozšíření vytvořte MyValues.mysettings v kořenovém adresáři s následujícím obsahem:

    Greeting:string:Hello World!
    
  4. Znovu sestavte a vygenerovaný soubor by se měl vytvořit a sestavit. Zkontrolujte složku projektu pro soubor MySetting.generated.cs.

  5. Třída MySetting je v nesprávném oboru názvů, takže teď proveďte změnu pro použití oboru názvů naší aplikace. Otevřete soubor projektu a přidejte následující kód:

    <PropertyGroup>
        <SettingNamespace>MSBuildConsoleExample</SettingNamespace>
    </PropertyGroup>
    
  6. Znovu sestavte a zjistěte, že třída je v oboru názvů MSBuildConsoleExample. 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í (RootFolder) z nich, pokud chcete.

  7. 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 záznamu použijete možnost -bl (binární záznam). 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 protokolu msbuild.binlog, který lze otevřít pomocí MSBuild Binary a Structured Log Viewer. Možnost /t:rebuild znamená opětovné sestavení cíle. Bude nutit 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 MSBuild mají několik klíčových rozdílů od balíčků knihoven NuGet:

  • Musí seskupit vlastní závislosti sestavení, místo aby tyto závislosti vystavovaly spotřebě projektu.
  • Nezabalí žádná požadovaná sestavení do složky lib/<target framework>, protože by to způsobilo, že NuGet zahrne sestavení do libovolného balíčku, který danou úlohu využívá.
  • Potřebují pouze kompilovat proti sestavením Microsoft.Build – během provozu je poskytne samotné jádro MSBuild, takže nemusí být součástí balíčku.
  • 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

Doporučuje se vytvořit balíček NuGet pro distribuci vlastní úlohy 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 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>
    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
    ...
</PropertyGroup>

Vlastnost CopyLocalLockFileAssemblies je nutná k zajištění zkopírování závislostí do výstupního adresáře.

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čit svá sestavení jako soukromá a fyzicky je vložit do vygenerovaného balíčku. V tomto příkladu předpokládáme, že váš úkol závisí na Microsoft.Extensions.DependencyInjection, aby fungoval, takže přidejte PackageReference k Microsoft.Extensions.DependencyInjection ve verzi 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 úkolů, a to jak PackageReference, tak ProjectReference, atributem PrivateAssets="all". Tím NuGetu řekne, aby tyto závislosti vůbec nezpřístupnil pro použití v projektech. 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 běhové prostředky našich závislostí. Existují dvě části: cíl nástroje MSBuild, který přidá naše závislosti do skupiny položek BuildOutputInPackage ItemGroup a několik vlastností, které řídí rozložení těchto položek BuildOutputInPackage. 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>
    <!-- Suppress NuGet warning NU5128. -->
    <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
    ...
</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>

Neseskupujte 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 ExcludeAssets="Runtime" přidáte atribut PackageReference.

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

Generová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 MSBuild, aby se soubor vygeneroval, protože není generován 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

Abychom získali pozadí k této části, přečtěte si 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 soubory umístíte do formuláře <package_id>.targets nebo <package_id>.props do složky build v projektu.

Soubory v kořenovém adresáři projektu sestavení složky jsou považovány za vhodné pro všechny cílové architektury.

V této části připojíte implementaci úkolů v souborech .props a .targets, které budou zahrnuty v našem balíčku NuGet a automaticky načteny z odkazujícího projektu.

  1. Do souboru projektu úkolu AppSettingStronglyTyped.csprojpřidejte 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>
    
  2. Vytvořte složku sestavení a přidejte do ní dva textové soubory: AppSettingStronglyTyped.props a AppSettingStronglyTyped.targets. AppSettingStronglyTyped.props je importován brzy v Microsoft.Common.props, a proto mu později definované vlastnosti nejsou k dispozici. Vyhněte se tedy odkazům na vlastnosti, které ještě nejsou definovány; vyhodnocují se jako prázdné.

    Directory.Build.targets se naimportují z Microsoft.Common.targets po importu souborů .targets 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 zahrnuje úlohu a definuje některé vlastnosti s výchozími hodnotami:

    <?xml version="1.0" encoding="utf-8" ?>
    <Project>
    <!--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. Při instalaci balíčku se soubor AppSettingStronglyTyped.props automaticky zahrne. Pak má klient k dispozici úlohu a některé výchozí hodnoty. Nikdy se ale nepoužívá. Pokud chcete tento kód umístit do akce, definujte některé cíle v souboru AppSettingStronglyTyped.targets, který se také automaticky zahrne při instalaci balíčku:

    <?xml version="1.0" encoding="utf-8" ?>
    <Project>
    
    <!--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í ItemGroup, který představuje textové soubory (může to být více než jeden) ke čtení a bude to část našeho parametru ú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 MSBuild. Rozšiřujeme proces MSBuild, a přepisujeme předdefinované cíle.

    • BeforeCompile: Cílem je volat vlastní úlohu pro vygenerování třídy a zahrnout třídu, jež má být zkompilována. Úkoly v tomto cíli jsou vloženy před dokončením jádrové kompilace. Vstupní a výstupní pole souvisejí s přírůstkovým sestavením. Pokud jsou všechny výstupní položky aktuální, nástroj MSBuild přeskočí úlohu. Toto inkrementální sestavení cílového prostředí může výrazně zlepšit výkon vašich sestavení. Položka se považuje za up-to-date, pokud má výstupní soubor stejný věk nebo novější než jeho vstupní soubor nebo soubory.

    • AfterClean: Cílem je odstranit vygenerovaný soubor třídy po obecném vyčištění. Úkoly v tomto cílovém úkolu jsou vloženy po vyvolání základní funkce čištění. Vynutí opakování kroku generování kódu, když se provádí cíl Znovu sestavit.

Vygenerování balíčku NuGet

Chcete-li vygenerovat balíček NuGet, můžete použít Sadu Visual Studio (klikněte pravým tlačítkem myši na uzel projektu v průzkumníku řešení a vyberte Pack). 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 jsou ve složce build. Soubor .dll je ve složce lib\netstandard2.0\. Soubor AppSettingStronglyTyped.nuspec je na kořenové úrovni.

(Volitelné) Podpora multitargetingu

Měli byste zvážit podporu distribuce msBuildu Full (.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, bude spuštěno sestavení pro „TargetFrameworkMonikers“ a celkové výsledky mohou být zabaleny 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, protože je v .NET 5.0 a novějších tolik nových rozhraní API, dává smysl nastavit více cílových platforem jak pro zdrojový kód úloh MSBuild pro různé TargetFrameworkMonikers, tak i pro cílovou logiku MSBuild pro různé typy runtime MSBuild.

Změny vyžadované pro více cílení

Cílení na více objektů TargetFrameworkMonikers (TFM):

  1. Změňte soubor projektu tak, aby používal net472 a net6.0 TFM (druhý soubor se může změnit na základě toho, na jakou úroveň sady SDK chcete cílit). Možná budete chtít cílit na netcoreapp3.1, dokud nebude podpora .NET Core 3.1. Když to uděláte, struktura složek balíčku se změní z tasks/ na tasks/<TFM>/.

    <TargetFrameworks>net472;net6.0</TargetFrameworks>
    
  2. Aktualizujte soubory .targets, abyste k načtení úloh použili správné TFM. Požadovaný TFM se změní podle toho, který .NET TFM jste vybrali výše, ale u projektu, který cílí na net472 a net6.0, budete mít takovou vlastnost jako:

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

Tento kód používá vlastnost MSBuildRuntimeType jako proxy pro aktivní hostitelské prostředí. Jakmile je tato vlastnost nastavená, můžete ji použít v UsingTask k načtení správného 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.