Erstellen einer MSBuild-Inlineaufgabe mit RoslynCodeTaskFactory
Ähnlich wie bei CodeTaskFactory verwendet RoslynCodeTaskFactory die plattformübergeifenden Roslyn-Compiler, um In-Memory-Taskassemblys für die Verwendung als Inlinetasks zu generieren. RoslynCodeTaskFactory-Tasks sind für .NET Standard vorgesehen und funktionieren ebenfalls mit .NET Framework- und .NET Core-Runtimes sowie auf anderen Plattformen wie Linux und macOS.
Hinweis
Der RoslynCodeTaskFactory-Task ist nur in MSBuild 15.8 und höher verfügbar. MSBuild-Versionen folgen den Versionen von Visual Studio. Daher ist RoslynCodeTaskFactory in Visual Studio 2017 ab Version 15.8 verfügbar.
Die Struktur einer Inlineaufgabe mit RoslynCodeTaskFactory
RoslynCodeTaskFactory-Inlineaufgaben werden genau wie bei CodeTaskFactory deklariert. Der einzige Unterschied liegt darin, dass Sie .NET Standard als Ziel verwenden. Die Inlineaufgabe und das UsingTask
-Element, in dem sie enthalten ist, befinden sich in der Regel in einer TARGETS-Datei und werden bei Bedarf in andere Projektdateien importiert. Im Folgenden finden Sie eine einfache Inlineaufgabe. Beachten Sie, dass mit dieser Aufgabe keine Aktionen ausgeführt werden.
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This simple inline task does nothing. -->
<UsingTask
TaskName="DoNothing"
TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll" >
<ParameterGroup />
<Task>
<Reference Include="" />
<Using Namespace="" />
<Code Type="Fragment" Language="cs">
</Code>
</Task>
</UsingTask>
</Project>
Das UsingTask
-Element im Beispiel besitzt drei Attribute, die die Aufgabe und die Inlineaufgabenfactory beschreiben, die diese kompiliert.
Die Aufgabe wird nach dem
TaskName
-Attribut benannt, in diesem FallDoNothing
.Nach dem
TaskFactory
-Attribut wird die Klasse benannt, die die Inlineaufgabenfactory implementiert.Über das
AssemblyFile
-Attribut wird der Speicherort der Inlineaufgabenfactory angegeben. Sie können auch mit demAssemblyName
-Attribut den vollqualifizierten Namen der Inlineaufgabenfactory-Klasse angeben, die sich in der Regel im globalen Assemblycache (Global Assembly Cache, GAC) befindet.
Die verbleibenden Elemente der DoNothing
-Aufgabe sind leer und werden bereitgestellt, um die Reihenfolge und Struktur einer Inlineaufgabe zu veranschaulichen. Ein robusteres Beispiel wird weiter unten in diesem Thema präsentiert.
Das
ParameterGroup
-Element ist optional. Bei Angabe werden die Parameter für die Aufgabe deklariert. Weitere Informationen zu Eingabe- und Ausgabeparametern finden Sie weiter unten in diesem Artikel unter Eingabe- und Ausgabeparameter.Das
Task
-Element beschreibt und enthält den Quellcode der Aufgabe.Das
Reference
-Element stellt Verweise auf die im Code verwendeten .NET-Assemblys bereit. Dies entspricht dem Hinzufügen eines Verweises auf ein Projekt in Visual Studio. DasInclude
-Attribut gibt den Pfad der Assembly an, auf die verwiesen wird.Das
Using
-Element führt die Namespaces auf, auf die Sie zugreifen möchten. Dies ähnelt derUsing
-Anweisung in Visual C#. DasNamespace
-Attribut gibt den zu einschließenden Namespace an.
Das Reference
-Element und das Using
-Element sind sprachunabhängig. Inlineaufgaben können jeder unterstützten .NET CodeDom-Sprache geschrieben werden, z. B. Visual Basic oder Visual C#.
Hinweis
Elemente im Task
-Element sind für die Aufgabenfactory spezifisch, in diesem Fall die Codeaufgabenfactory.
Codeelement
Als letztes untergeordnetes Element wird im Task
-Element das Code
-Element angegeben. Das Code
-Element enthält oder sucht den Code, den Sie zu einer Aufgabe kompilieren möchten. Welche Elemente Sie im Code
-Element einfügen, ist davon abhängig, wie Sie die Aufgabe erstellen möchten.
Das Language
-Attribut gibt die Sprache an, in die der Code geschrieben ist. Zulässige Werte sind cs
für C# und vb
für Visual Basic.
Das Type
-Attribut gibt den Typ von Code im Code
-Element an.
Wenn der Wert von
Type
aufClass
festgelegt ist, enthält dasCode
-Element Code für eine Klasse, die von der ITask-Schnittstelle abgeleitet wird.Wenn der Wert von
Type
aufMethod
festgelegt ist, wird im Code dieExecute
-Methode der ITask-Schnittstelle überschrieben.Wenn der Wert von
Type
aufFragment
festgelegt ist, wird im Code der Inhalt derExecute
-Methode, nicht jedoch die Signatur oder diereturn
-Anweisung definiert.
Der Code selbst befindet sich in der Regel zwischen einem <![CDATA[
-Marker und einem ]]>
-Marker. Da sich der Code in einem CDATA-Abschnitt befindet, müssen Sie sich keine Sorgen über mit Escape-Zeichen versehene Zeichen mache, so wie „<“ oder „>“.
Sie können den Speicherort einer Datei mit dem Code für die Aufgabe auch über das Source
-Attribut des Code
-Elements angeben. Der Code in der Quelldatei muss den vom Type
-Attribut angegebenen Typ aufweisen. Bei vorhandenem Source
-Attribut ist der Standardwert von Type
Class
. Wenn Source
nicht vorhanden ist, lautet der Standardwert Fragment
.
Hinweis
Bei der Definition der Aufgabenklasse in der Quelldatei muss der Klassenname mit dem TaskName
-Attribut des entsprechenden UsingTask-Elements übereinstimmen.
Hello World
Im Folgenden sehen Sie einen robusteren Inlinetask mit RoslynCodeTaskFactory: Mit der HelloWorld-Aufgabe wird „Hello, world!“ auf dem Standardgerät für die Fehlerprotokollierung angezeigt, in der Regel im Visual Studio-Ausgabe-Fenster. Das Reference
-Element im Beispiel wurde nur zur Veranschaulichung eingefügt.
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This simple inline task displays "Hello, world!" -->
<UsingTask
TaskName="HelloWorld"
TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll" >
<ParameterGroup />
<Task>
<Reference Include="System.Xml"/>
<Using Namespace="System"/>
<Using Namespace="System.IO"/>
<Code Type="Fragment" Language="cs">
<![CDATA[
// Display "Hello, world!"
Log.LogError("Hello, world!");
]]>
</Code>
</Task>
</UsingTask>
</Project>
Sie können die HelloWorld-Aufgabe in der Datei HelloWorld.targets speichern und anschließend wie folgt in einem Projekt aufrufen.
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="HelloWorld.targets" />
<Target Name="Hello">
<HelloWorld />
</Target>
</Project>
Eingabe- und Ausgabeparameter
Inlineaufgabenparameter bilden untergeordnete Elemente eines ParameterGroup
-Elements. Jeder Parameter akzeptiert den Namen des Elements, das von ihm definiert wird. Im folgenden Code wird der Parameter Text
definiert.
<ParameterGroup>
<Text />
</ParameterGroup>
Parameter können ein oder mehrere Attribute besitzen:
Required
ist ein optionales Attribut, das standardmäßig den Wertfalse
besitzt. Beitrue
ist der Parameter erforderlich und muss vor dem Aufrufen der Aufgabe einen Wert erhalten.ParameterType
ist ein optionales Attribut, das standardmäßig den WertSystem.String
besitzt. Es kann auf jeden vollqualifizierten Typ festgelegt werden, bei dem es sich um ein Element oder einen Wert handelt und mit System.Convert.ChangeType in und aus einer Zeichenfolge konvertiert werden kann. (Anders gesagt kann jeder Typ an eine und von einer externen Aufgabe übergeben werden.)Output
ist ein optionales Attribut, das standardmäßig den Wertfalse
besitzt. Beitrue
muss dem Parameter ein Wert zugewiesen werden, bevor die Execute-Methode die Rückgabe ausführt.
Ein auf ein Objekt angewendeter
<ParameterGroup>
<Expression Required="true" />
<Files ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
<Tally ParameterType="System.Int32" Output="true" />
</ParameterGroup>
definiert die folgenden drei Parameter:
Expression
ist ein erforderlicher Eingabeparameter vom Typ System.String.Files
ist ein erforderlicher Eingabeparameter für Elementlisten.Tally
ist ein Ausgabeparameter vom Typ System.Int32.
Wenn das Code
-Element das Type
-Attribut Fragment
oder Method
aufweist, werden für jeden Parameter automatisch Eigenschaften erstellt. Wenn in RoslynCodeTaskFactory das Code
-Element über das Type
-Attribut von Class
verfügt, müssen Sie ParameterGroup
nicht angeben, da es aus dem Quellcode abgeleitet wird (darin besteht ein Unterschied zu CodeTaskFactory
). Andernfalls müssen Eigenschaften explizit im Aufgabenquellcode deklariert werden und exakt mit den zugehörigen Parameterdefinitionen übereinstimmen.
Beispiel
Der folgende Inlinetask protokolliert einige Meldungen und gibt eine Zeichenfolge zurück.
<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' ToolsVersion="15.0">
<UsingTask TaskName="MySample"
TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildBinPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Parameter1 ParameterType="System.String" Required="true" />
<Parameter2 ParameterType="System.String" />
<Parameter3 ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Code Type="Fragment" Language="cs">
<![CDATA[
Log.LogMessage(MessageImportance.High, "Hello from an inline task created by Roslyn!");
Log.LogMessageFromText($"Parameter1: '{Parameter1}'", MessageImportance.High);
Log.LogMessageFromText($"Parameter2: '{Parameter2}'", MessageImportance.High);
Parameter3 = "A value from the Roslyn CodeTaskFactory";
]]>
</Code>
</Task>
</UsingTask>
<Target Name="Demo">
<MySample Parameter1="A value for parameter 1" Parameter2="A value for parameter 2">
<Output TaskParameter="Parameter3" PropertyName="NewProperty" />
</MySample>
<Message Text="NewProperty: '$(NewProperty)'" />
</Target>
</Project>
Diese Inlinetasks können Pfade kombinieren und den Dateinamen abrufen.
<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003' ToolsVersion="15.0">
<UsingTask TaskName="PathCombine"
TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildBinPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Paths ParameterType="System.String[]" Required="true" />
<Combined ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Code Type="Fragment" Language="cs">
<![CDATA[
Combined = Path.Combine(Paths);
]]>
</Code>
</Task>
</UsingTask>
<UsingTask TaskName="PathGetFileName"
TaskFactory="RoslynCodeTaskFactory"
AssemblyFile="$(MSBuildBinPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<Path ParameterType="System.String" Required="true" />
<FileName ParameterType="System.String" Output="true" />
</ParameterGroup>
<Task>
<Using Namespace="System" />
<Code Type="Fragment" Language="cs">
<![CDATA[
FileName = System.IO.Path.GetFileName(Path);
]]>
</Code>
</Task>
</UsingTask>
<Target Name="Demo">
<PathCombine Paths="$(Temp);MyFolder;$([System.Guid]::NewGuid()).txt">
<Output TaskParameter="Combined" PropertyName="MyCombinedPaths" />
</PathCombine>
<Message Text="Combined Paths: '$(MyCombinedPaths)'" />
<PathGetFileName Path="$(MyCombinedPaths)">
<Output TaskParameter="FileName" PropertyName="MyFileName" />
</PathGetFileName>
<Message Text="File name: '$(MyFileName)'" />
</Target>
</Project>
Gewährleisten von Abwärtskompatibilität
RoslynCodeTaskFactory
steht seit der MSBuild-Version 15.8 zur Verfügung. Angenommen, Sie haben eine Situation, in der Sie frühere Versionen von Visual Studio und MSBuild unterstützen möchten, bei der RoslynCodeTaskFactory
im Gegensatz zu CodeTaskFactory
nicht verfügbar war, Sie aber dasselbe Buildskript einsetzen möchten. Sie können ein Choose
-Konstrukt verwenden, das die $(MSBuildVersion)
-Eigenschaft verwendet, um zur Buildzeit zu entscheiden, ob die RoslynCodeTaskFactory
-Eigenschaft verwendet oder auf CodeTaskFactory
zurückgegriffen werden soll, wie im folgenden Beispiel:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<Choose>
<When Condition=" '$(MSBuildVersion.Substring(0,2))' >= 16 Or
('$(MSBuildVersion.Substring(0,2))' == 15 And '$(MSBuildVersion.Substring(3,1))' >= 8)">
<PropertyGroup>
<TaskFactory>RoslynCodeTaskFactory</TaskFactory>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<TaskFactory>CodeTaskFactory</TaskFactory>
</PropertyGroup>
</Otherwise>
</Choose>
<UsingTask
TaskName="HelloWorld"
TaskFactory="$(TaskFactory)"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup />
<Task>
<Using Namespace="System"/>
<Using Namespace="System.IO"/>
<Code Type="Fragment" Language="cs">
<![CDATA[
Log.LogError("Using RoslynCodeTaskFactory");
]]>
</Code>
</Task>
</UsingTask>
<Target Name="RunTask" AfterTargets="Build">
<Message Text="MSBuildVersion: $(MSBuildVersion)"/>
<Message Text="TaskFactory: $(TaskFactory)"/>
<HelloWorld />
</Target>
</Project>