Zelfstudie: Een aangepaste taak maken voor het genereren van code
In deze zelfstudie maakt u een aangepaste taak in MSBuild in C# die het genereren van code afhandelt en vervolgens gebruikt u de taak in een build. In dit voorbeeld ziet u hoe u MSBuild gebruikt om de schone en herbouwbewerkingen af te handelen. In het voorbeeld ziet u ook hoe u incrementele build ondersteunt, zodat de code alleen wordt gegenereerd wanneer de invoerbestanden zijn gewijzigd. De technieken die worden gedemonstreerd, zijn van toepassing op een breed scala aan scenario's voor het genereren van code. In de stappen wordt ook het gebruik van NuGet getoond om de taak voor distributie te verpakken. De zelfstudie bevat een optionele stap om de BinLog-viewer te gebruiken om de probleemoplossingservaring te verbeteren.
Voorwaarden
U moet inzicht hebben in MSBuild-concepten, zoals taken, doelen en eigenschappen. Zie MSBuild-concepten.
Voor de voorbeelden is MSBuild vereist. Deze is geïnstalleerd met Visual Studio, maar kan ook afzonderlijk worden geïnstalleerd. Zie MSBuild downloaden zonder Visual Studio.
Inleiding tot het codevoorbeeld
In het voorbeeld wordt een invoertekstbestand met waarden ingesteld en wordt een C#-codebestand gemaakt met code waarmee deze waarden worden gemaakt. Hoewel dit een eenvoudig voorbeeld is, kunnen dezelfde basistechnieken worden toegepast op complexere scenario's voor het genereren van code.
In deze zelfstudie maakt u een aangepaste MSBuild-taak met de naam AppSettingStronglyTyped. De taak leest een set tekstbestanden en elk bestand met regels met de volgende indeling:
propertyName:type:defaultValue
De code genereert een C#-klasse met alle constanten. Een probleem moet de build stoppen en de gebruiker voldoende informatie geven om het probleem vast te stellen.
De volledige voorbeeldcode voor deze zelfstudie bevindt zich in aangepaste taak - codegeneratie in de .NET-voorbeeldenopslagplaats op GitHub.
Maak het project AppSettingStronglyTyped aan
Maak een .NET Standard-klassebibliotheek. Het framework moet .NET Standard 2.0 zijn.
Let op het verschil tussen volledige MSBuild (de versie die Visual Studio gebruikt) en portable MSBuild, de versie die is gebundeld in de .NET Core-opdrachtregel.
- Full MSBuild: Deze versie van MSBuild bevindt zich meestal in Visual Studio. Wordt uitgevoerd op .NET Framework. Visual Studio gebruikt dit wanneer u Build- uitvoert op uw oplossing of project. Deze versie is ook beschikbaar in een opdrachtregelomgeving, zoals de Visual Studio Developer-opdrachtprompt of PowerShell.
- .NET MSBuild: Deze versie van MSBuild is gebundeld in de .NET Core-opdrachtregel. Het wordt uitgevoerd op .NET Core. Visual Studio roept deze versie van MSBuild niet rechtstreeks aan. Het ondersteunt alleen projecten die bouwen met behulp van Microsoft.NET.Sdk.
Als u code wilt delen tussen .NET Framework en een andere .NET-implementatie, zoals .NET Core, moet uw bibliotheek zich richten op .NET Standard 2.0en u wilt uitvoeren in Visual Studio, dat wordt uitgevoerd op .NET Framework. .NET Framework biedt geen ondersteuning voor .NET Standard 2.1.
Kies de MSBuild API-versie waarnaar moet worden verwezen
Bij het compileren van een aangepaste taak moet u verwijzen naar de versie van de MSBuild-API (Microsoft.Build.*
) die overeenkomt met de minimale versie van Visual Studio en/of de .NET SDK die u verwacht te ondersteunen. Als u bijvoorbeeld gebruikers in Visual Studio 2019 wilt ondersteunen, moet u bouwen op basis van MSBuild 16.11.
De aangepaste taak AppSettingStronglyTyped MSBuild maken
De eerste stap is het maken van de aangepaste MSBuild-taak. Informatie over het schrijven van een aangepaste MSBuild-taak kan u helpen de volgende stappen te begrijpen. Een aangepaste MSBuild-taak is een klasse die de ITask-interface implementeert.
Voeg een verwijzing toe naar het Microsoft.Build.Utilities.Core NuGet-pakket en maak vervolgens een klasse met de naam AppSettingStronglyTyped afgeleid van Microsoft.Build.Utilities.Task.
Voeg drie eigenschappen toe. Deze eigenschappen definiëren de parameters van de taak die gebruikers instellen wanneer ze de taak in een clientproject gebruiken:
//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; }
De taak verwerkt de SettingFiles en genereert een klasse-
SettingNamespaceName.SettingClassName
. De gegenereerde klasse heeft een set constanten op basis van de inhoud van het tekstbestand.De taakuitvoer moet een tekenreeks zijn die de bestandsnaam van de gegenereerde code geeft:
// The filename where the class was generated [Output] public string ClassNameFile { get; set; }
Wanneer u een aangepaste taak maakt, neemt u over van Microsoft.Build.Utilities.Task. Als u de taak wilt implementeren, overschrijft u de methode Execute(). De methode
Execute
retourneerttrue
als de taak slaagt en andersfalse
.Task
implementeert Microsoft.Build.Framework.ITask en biedt standaard implementaties van sommigeITask
leden en biedt daarnaast enkele functionaliteit voor logboekregistratie. Het is belangrijk om de status van het logboek uit te voeren om de taak vast te stellen en op te lossen, met name als er een probleem optreedt en de taak een foutresultaat (false
) moet retourneren. Bij fout geeft de klasse de fout aan door TaskLoggingHelper.LogErroraan te roepen.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; }
Met de taak-API kan onwaar worden geretourneerd, wat een fout aangeeft, zonder aan de gebruiker aan te geven wat er fout is gegaan. Het is het beste om
!Log.HasLoggedErrors
te retourneren in plaats van een Booleaanse code en een fout te loggen wanneer er iets misgaat.
Logboekfouten
De aanbevolen procedure bij logboekregistratiefouten is om details op te geven, zoals het regelnummer en een afzonderlijke foutcode bij het vastleggen van een fout. Met de volgende code wordt het tekstbestand geparseerd en wordt de methode TaskLoggingHelper.LogError gebruikt met het regelnummer in het tekstbestand dat de fout heeft veroorzaakt.
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);
}
Met behulp van de technieken die in de vorige code worden weergegeven, worden fouten in de syntaxis van het tekstbestand weergegeven als buildfouten met nuttige diagnostische informatie:
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)
Wanneer u uitzonderingen in uw taak onderschept, gebruikt u de TaskLoggingHelper.LogErrorFromException methode. Hierdoor wordt de foutuitvoer verbeterd, bijvoorbeeld door de aanroepstack te achterhalen waarin de uitzondering is opgetreden.
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;
}
De implementatie van de andere methoden die deze invoer gebruiken om de tekst voor het gegenereerde codebestand te bouwen, wordt hier niet weergegeven; zie AppSettingStronglyTyped.cs in de voorbeeldopslagplaats.
Met de voorbeeldcode wordt C#-code gegenereerd tijdens het buildproces. De taak is net als elke andere C#-klasse, dus wanneer u klaar bent met deze zelfstudie, kunt u deze aanpassen en de functionaliteit toevoegen die nodig is voor uw eigen scenario.
Een console-app genereren en de aangepaste taak gebruiken
In deze sectie maakt u een standaard .NET Core Console-app die gebruikmaakt van de taak.
Belangrijk
Het is belangrijk om te voorkomen dat u een aangepaste MSBuild-taak genereert in hetzelfde MSBuild-proces dat het gaat gebruiken. Het nieuwe project moet zich in een totaal andere Visual Studio-oplossing bevinden, of het kan gebruikmaken van een dll die vooraf is gegenereerd en is verplaatst vanuit de standaarduitvoer.
Maak het .NET Console-project MSBuildConsoleExample in een nieuwe Visual Studio-oplossing.
De normale manier om een taak te distribueren is via een NuGet-pakket, maar tijdens de ontwikkeling en foutopsporing kunt u alle informatie over
.props
en.targets
rechtstreeks in het projectbestand van uw toepassing opnemen en vervolgens naar de NuGet-indeling gaan wanneer u de taak naar anderen distribueert.Wijzig het projectbestand om de taak voor het genereren van code te benutten. In de codevermelding in deze sectie ziet u het gewijzigde projectbestand nadat u naar de taak hebt verwezen, de invoerparameters voor de taak hebt ingesteld en de doelen worden geschreven voor het verwerken van opschoon- en herbouwbewerkingen, zodat het gegenereerde codebestand wordt verwijderd zoals u zou verwachten.
Taken worden geregistreerd met behulp van het UsingTask-element (MSBuild). Het element
UsingTask
registreert de taak; het vertelt MSBuild de naam van de taak en hoe de assembly die de taakklasse bevat te zoeken en uit te voeren. Het assemblypad is gerelateerd aan het projectbestand.De
PropertyGroup
bevat de eigenschapsdefinities die overeenkomen met de eigenschappen die in de taak zijn gedefinieerd. Deze eigenschappen worden ingesteld met behulp van kenmerken en de taaknaam wordt gebruikt als de elementnaam.TaskName
is de naam van de taak waarnaar vanuit de assemblage moet worden verwezen. Dit kenmerk moet altijd volledig opgegeven naamruimten gebruiken.AssemblyFile
is het bestandspad van de assembly.Als u de taak wilt aanroepen, voegt u de taak toe aan het juiste doel, in dit geval
GenerateSetting
.Het doelwit
ForceGenerateOnRebuild
handelt de opschoon- en herbouwbewerkingen af door het gegenereerde bestand te verwijderen. Het is ingesteld om te worden uitgevoerd na hetCoreClean
doel door het kenmerkAfterTargets
in te stellen opCoreClean
.<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>
Notitie
In plaats van een doel zoals
CoreClean
te overschrijven, gebruikt deze code een andere manier om de doelen te orden (BeforeTarget en AfterTarget). SDK-projecten hebben een impliciete import van doelen na de laatste regel van het projectbestand; Dit betekent dat u geen standaarddoelen kunt overschrijven, tenzij u de importbewerkingen handmatig opgeeft. Zie Vooraf gedefinieerde doelen overschrijven.De kenmerken
Inputs
enOutputs
helpen MSBuild efficiënter te zijn door informatie te verstrekken voor incrementele builds. De datums van de invoer worden vergeleken met de uitvoer om te zien of het doel moet worden uitgevoerd of als de uitvoer van de vorige build opnieuw kan worden gebruikt.Maak het invoertekstbestand met de extensie die is gedefinieerd om te worden gedetecteerd. Gebruik de standaardextensie en maak
MyValues.mysettings
in de root met de volgende inhoud:Greeting:string:Hello World!
Bouw opnieuw en het gegenereerde bestand moet worden gemaakt en gebouwd. Controleer de projectmap op het MySetting.generated.cs-bestand.
De klasse MySetting- zich in de verkeerde naamruimte bevindt, dus breng nu een wijziging aan om onze app-naamruimte te gebruiken. Open het projectbestand en voeg de volgende code toe:
<PropertyGroup> <SettingNamespace>MSBuildConsoleExample</SettingNamespace> </PropertyGroup>
Bouw opnieuw en kijk of de klasse zich in de
MSBuildConsoleExample
naamruimte bevindt. Op deze manier kunt u de gegenereerde klassenaam (SettingClass
), de tekstbestanden (SettingExtensionFile
) opnieuw definiëren die als invoer moeten worden gebruikt en de locatie (RootFolder
) van deze bestanden als u wilt.Open
Program.cs
en wijzig de hardcoded 'Hallo Wereld!!' aan de door de gebruiker gedefinieerde constante:static void Main(string[] args) { Console.WriteLine(MySetting.Greeting); }
Voer het programma uit; hiermee wordt de begroeting van de gegenereerde klasse afgedrukt.
(Optioneel) Gebeurtenissen registreren tijdens het buildproces
Het is mogelijk om te compileren met behulp van een opdrachtregelopdracht. Ga naar de projectmap. U gebruikt de optie -bl
(binair logboek) om een binair logboek te genereren. Het binaire logboek bevat nuttige informatie om te weten wat er gebeurt tijdens het buildproces.
# 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
Beide opdrachten genereren een logboekbestand msbuild.binlog
, dat kan worden geopend met MSBuild Binary en Structured Log Viewer. De optie /t:rebuild
betekent dat het herbouwtarget wordt uitgevoerd. Hiermee wordt de regeneratie van het gegenereerde codebestand afgedwongen.
Gefeliciteerd! U hebt een taak gemaakt waarmee code wordt gegenereerd en deze in een build wordt gebruikt.
De taak voor distributie verpakken
Als u alleen uw aangepaste taak in een paar projecten of in één oplossing hoeft te gebruiken, is het gebruik van de taak als onbewerkte assembly mogelijk alles wat u nodig hebt, maar de beste manier om uw taak voor te bereiden om deze ergens anders te gebruiken of met anderen te delen, is als een NuGet-pakket.
MSBuild Task-pakketten hebben enkele belangrijke verschillen met bibliotheek NuGet-pakketten:
- Ze moeten hun eigen assemblyafhankelijkheden bundelen, in plaats van die afhankelijkheden bloot te stellen aan het verbruikende project
- Ze verpakken geen vereiste assembly's in een
lib/<target framework>
map, omdat dat ertoe zou leiden dat NuGet de assembly's in een pakket bevat dat de taak verbruikt - Ze hoeven alleen te compileren op basis van de Microsoft.Build-assembly's. Deze worden tijdens runtime geleverd door de werkelijke MSBuild-engine en hoeven dus niet in het pakket te worden opgenomen
- Ze genereren een speciaal
.deps.json
-bestand waarmee MSBuild de afhankelijkheden van de taak (met name systeemeigen afhankelijkheden) op een consistente manier kan laden
Als u al deze doelen wilt bereiken, moet u een paar wijzigingen aanbrengen in het standaardprojectbestand boven en buiten de standaardprojectbestanden waarmee u mogelijk bekend bent.
Een NuGet-pakket maken
Het maken van een NuGet-pakket is de aanbevolen manier om uw aangepaste taak naar anderen te distribueren.
Voorbereiden om het pakket te genereren
Als u wilt voorbereiden op het genereren van een NuGet-pakket, moet u enkele wijzigingen aanbrengen in het projectbestand om de details op te geven die het pakket beschrijven. Het oorspronkelijke projectbestand dat u hebt gemaakt, lijkt op de volgende code:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.0.0" />
</ItemGroup>
</Project>
Als u een NuGet-pakket wilt genereren, voegt u de volgende code toe om de eigenschappen voor het pakket in te stellen. U ziet een volledige lijst met ondersteunde MSBuild-eigenschappen in de documentatie 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>
De eigenschap CopyLocalLockFileAssemblies is nodig om ervoor te zorgen dat afhankelijkheden worden gekopieerd naar de uitvoermap.
Afhankelijkheden markeren als privé
De afhankelijkheden van uw MSBuild-taak moeten in het pakket worden verpakt; ze kunnen niet worden uitgedrukt als normale pakketverwijzingen. Het pakket maakt geen reguliere afhankelijkheden beschikbaar voor externe gebruikers. Dit doet u in twee stappen: uw assembly's markeren als privé en deze daadwerkelijk insluiten in het gegenereerde pakket. In dit voorbeeld wordt ervan uitgegaan dat uw taak afhankelijk is van Microsoft.Extensions.DependencyInjection
om te werken, dus voeg een PackageReference
toe aan Microsoft.Extensions.DependencyInjection
bij versie 6.0.0
.
<ItemGroup>
<PackageReference
Include="Microsoft.Build.Utilities.Core"
Version="17.0.0" />
<PackageReference
Include="Microsoft.Extensions.DependencyInjection"
Version="6.0.0" />
</ItemGroup>
Markeer nu elke afhankelijkheid van dit taakproject, zowel PackageReference
als ProjectReference
met het kenmerk PrivateAssets="all"
. Dit vertelt NuGet dat deze afhankelijkheden helemaal niet zichtbaar moeten zijn voor gebruikende projecten. Meer informatie over het beheren van afhankelijkheidsassets vindt u in de NuGet-documentatie.
<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>
Afhankelijkheden bundelen in het pakket
U moet ook de runtime-assets van onze afhankelijkheden insluiten in het taakpakket. Er zijn twee onderdelen: een MSBuild-doel waarmee onze afhankelijkheden worden toegevoegd aan de BuildOutputInPackage
ItemGroup en enkele eigenschappen die de indeling van die BuildOutputInPackage
items bepalen. Meer informatie over dit proces vindt u in de NuGet-documentatie.
<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>
Bundel de Microsoft.Build.Utilities.Core-assembly niet
Zoals hierboven is besproken, wordt deze afhankelijkheid tijdens runtime geleverd door MSBuild zelf, dus we hoeven deze niet te bundelen in het pakket. Voeg hiervoor het kenmerk ExcludeAssets="Runtime"
toe aan de PackageReference
...
<PackageReference
Include="Microsoft.Build.Utilities.Core"
Version="17.0.0"
PrivateAssets="all"
ExcludeAssets="Runtime"
/>
...
Een deps.json-bestand genereren en insluiten
Het deps.json
-bestand kan worden gebruikt door MSBuild om ervoor te zorgen dat de juiste versies van uw afhankelijkheden worden geladen. U moet enkele MSBuild-eigenschappen toevoegen om ervoor te zorgen dat het bestand wordt gegenereerd, omdat het niet standaard wordt gegenereerd voor bibliotheken. Voeg vervolgens een doel toe om het op te nemen in onze pakketuitvoer, op dezelfde manier als voor onze pakketafhankelijkheden.
<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-eigenschappen en -doelen opnemen in een pakket
Lees voor achtergrondinformatie over deze sectie over eigenschappen en doelen en hoe u eigenschappen en doelen vervolgens in een NuGet-pakket kuntopnemen.
In sommige gevallen wilt u misschien aangepaste builddoelen of eigenschappen toevoegen aan projecten die uw pakket gebruiken, zoals het uitvoeren van een aangepast hulpprogramma of proces tijdens de build. U doet dit door bestanden in het formulier <package_id>.targets
of <package_id>.props
in de map build
in het project te plaatsen.
Bestanden in de hoofdmap van het project build- map worden als geschikt beschouwd voor alle doelframeworks.
In deze sectie gaat u de taakimplementatie instellen in de bestanden .props
en .targets
, die worden opgenomen in ons NuGet-pakket en automatisch worden geladen door een verwijzingsproject.
Voeg in het projectbestand van de taak AppSettingStronglyTyped.csprojde volgende code toe:
<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>
Maak een build map en voeg in die map twee tekstbestanden toe:
AppSettingStronglyTyped.props
en AppSettingStronglyTyped.targets.AppSettingStronglyTyped.props
wordt vroeg in Microsoft.Common.propsgeïmporteerd en de eigenschappen die later zijn gedefinieerd, zijn niet beschikbaar. Vermijd dus te verwijzen naar eigenschappen die nog niet zijn gedefinieerd; ze zouden leeg zijn.Directory.Build.targets wordt geïmporteerd uit Microsoft.Common.targets na het importeren van
.targets
bestanden uit NuGet-pakketten. Het kan dus eigenschappen en doelen overschrijven die zijn gedefinieerd in de meeste buildlogica of eigenschappen instellen voor al uw projecten, ongeacht wat de afzonderlijke projecten hebben ingesteld. Zie importorder.AppSettingStronglyTyped.props bevat de taak en definieert enkele eigenschappen met standaardwaarden:
<?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>
Het
AppSettingStronglyTyped.props
-bestand wordt automatisch opgenomen wanneer het pakket is geïnstalleerd. Vervolgens heeft de client de taak beschikbaar en enkele standaardwaarden. Het wordt echter nooit gebruikt. Als u deze code in actie wilt zetten, definieert u enkele doelen in hetAppSettingStronglyTyped.targets
-bestand, dat ook automatisch wordt opgenomen wanneer het pakket wordt geïnstalleerd:<?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>
De eerste stap is het maken van een ItemGroup, die de tekstbestanden vertegenwoordigt (dit kan meer dan één zijn) om te lezen en het is een deel van de taakparameter. Er zijn standaardwaarden voor de locatie en de extensie waarnaar we zoeken, maar u kunt de waarden die de eigenschappen definiëren in het msBuild-projectbestand van de client overschrijven.
Definieer vervolgens twee MSBuild-doelen. We het MSBuild-proces uitbreiden, waarbij vooraf gedefinieerde doelen worden overschreven:
BeforeCompile
: Het doel is om de aangepaste taak aan te roepen om de klasse te genereren en de klasse te laten opnemen voor compilatie. Taken in dit doel worden ingevoegd voordat de kerncompilatie wordt uitgevoerd. Invoer- en uitvoervelden zijn gerelateerd aan incrementele build van. Als alle uitvoeritems up-to-date zijn, slaat MSBuild het doeltarget over. Deze incrementele build van het doel kan de prestaties van uw builds aanzienlijk verbeteren. Een item wordt beschouwd als up-to-datum als het uitvoerbestand dezelfde leeftijd of nieuwer is dan het invoerbestand of de bijbehorende invoerbestanden.AfterClean
: het doel is om het gegenereerde klassebestand te verwijderen nadat een algemene opschoonbewerking is uitgevoerd. Taken worden in deze target ingevoegd nadat de kernfunctionaliteit voor reiniging is aangeroepen. Het dwingt dat de stap voor het genereren van code wordt herhaald wanneer het Rebuild-target wordt uitgevoerd.
Het NuGet-pakket genereren
Als u het NuGet-pakket wilt genereren, kunt u Visual Studio gebruiken (klik met de rechtermuisknop op het projectknooppunt in Solution Exploreren selecteer Pack). U kunt dit ook doen met behulp van de opdrachtregel. Navigeer naar de map waarin het taakprojectbestand AppSettingStronglyTyped.csproj
aanwezig is en voer de volgende opdracht uit:
// -o is to define the output; the following command chooses the current folder.
dotnet pack -o .
Gefeliciteerd! U hebt een NuGet-pakket gegenereerd met de naam \AppSettingStronglyTyped\AppSettingStronglyTyped\AppSettingStronglyTyped.1.0.0.nupkg.
Het pakket heeft een extensie .nupkg
en is een gecomprimeerd zip-bestand. U kunt het openen met een zip-tool. De bestanden .target
en .props
bevinden zich in de map build
. Het .dll
-bestand bevindt zich in de map lib\netstandard2.0\
. Het AppSettingStronglyTyped.nuspec
bestand bevindt zich op het hoofdniveau.
(Optioneel) Ondersteuning voor multitargeting
Overweeg zowel Full
(.NET Framework) als Core
(inclusief .NET 5 en hoger) MSBuild-distributies te ondersteunen ter ondersteuning van de breedst mogelijke gebruikersbasis.
Voor 'normale' .NET SDK-projecten betekent multitargeting het instellen van meerdere TargetFrameworks in uw projectbestand. Wanneer u dit doet, worden builds geactiveerd voor zowel TargetFrameworkMonikers, en de algehele resultaten kunnen worden verpakt in één artefact.
Dat is niet het volledige verhaal voor MSBuild. MSBuild heeft twee primaire verzendvoertuigen: Visual Studio en de .NET SDK. Dit zijn zeer verschillende runtime-omgevingen; een wordt uitgevoerd op de .NET Framework-runtime en andere wordt uitgevoerd op de CoreCLR. Dit betekent dat uw taaklogica verschillen kan hebben op basis van het msBuild-runtimetype dat momenteel wordt gebruikt, terwijl uw code kan zijn gericht op netstandard2.0. In de praktijk, omdat er zoveel nieuwe API's in .NET 5.0 en hoger zijn, is het zinvol om zowel de broncode van uw MSBuild-taken voor meerdere TargetFrameworkMonikers als de doellogica van uw MSBuild voor meerdere MSBuild-runtimetypen compatibel te maken.
Wijzigingen die vereist zijn voor multitarget
Meerdere TargetFrameworkMonikers (TFM) targeten:
Wijzig het projectbestand om de
net472
ennet6.0
TFM's te gebruiken (de laatste kan veranderen op basis van het SDK-niveau waarop u het doel wilt toepassen). Mogelijk wilt u zich richten opnetcoreapp3.1
totdat .NET Core 3.1 niet meer wordt ondersteund. Wanneer u dit doet, wordt de structuur van de pakketmap gewijzigd vantasks/
intasks/<TFM>/
.<TargetFrameworks>net472;net6.0</TargetFrameworks>
Werk uw
.targets
-bestanden bij om de juiste TFM te gebruiken voor het laden van taken. De vereiste TFM zal veranderen op basis van welke .NET TFM u hierboven hebt gekozen. Voor een project dat is gericht opnet472
ennet6.0
, zou u een eigenschap hebben zoals:
<AppSettingStronglyTyped_TFM Condition=" '$(MSBuildRuntimeType)' != 'Core' ">net472</AppSettingStronglyTyped_TFM>
<AppSettingStronglyTyped_TFM Condition=" '$(MSBuildRuntimeType)' == 'Core' ">net6.0</AppSettingStronglyTyped_TFM>
Deze code maakt gebruik van de eigenschap MSBuildRuntimeType
als proxy voor de actieve hostingomgeving. Zodra deze eigenschap is ingesteld, kunt u deze in de UsingTask
gebruiken om de juiste AssemblyFile
te laden:
<UsingTask
AssemblyFile="$(MSBuildThisFileDirectory)../tasks/$(AppSettingStronglyTyped_TFM)/AppSettingStronglyTyped.dll"
TaskName="AppSettingStrongTyped.AppSettingStronglyTyped" />
Volgende stappen
Veel taken omvatten het aanroepen van een uitvoerbaar bestand. In sommige scenario's kunt u de Exec-taak gebruiken, maar als de beperkingen van de Exec-taak een probleem zijn, kunt u ook een aangepaste taak maken. In de volgende zelfstudie worden beide opties beschreven met een realistischer scenario voor het genereren van code: het maken van een aangepaste taak voor het genereren van clientcode voor een REST API.
Of leer hoe u een aangepaste taak test.