Freigeben über


Erstellen einer MSBuild-Inlineaufgabe mit RoslynCodeTaskFactory

RoslynCodeTaskFactory verwendet die plattformübergreifenden Roslyn-Compiler, um Speicheraufgabenassemblys für die Verwendung als Inlineaufgaben zu generieren. RoslynCodeTaskFactory Aufgaben zielen auf .NET Standard ab und können auf .NET Framework- und .NET Core-Runtimes sowie auf anderen Plattformen wie Linux und macOS arbeiten.

Hinweis

Die RoslynCodeTaskFactory ist nur in MSBuild 15.8 und höher verfügbar. MSBuild-Versionen folgen Visual Studio-Versionen, sodass RoslynCodeTaskFactory in Visual Studio 2017, Version 15.8 und höher, verfügbar ist.

Die Struktur einer Inlineaufgabe mit RoslynCodeTaskFactory

RoslynCodeTaskFactory Inlineaufgaben werden mithilfe des UsingTask Elements deklariert. Die Inlineaufgabe und das UsingTask darin enthaltene Element sind in der Regel in einer .targets Datei enthalten und bei Bedarf in andere Projektdateien importiert. Hier ist eine einfache Inlineaufgabe. Beachten Sie, dass sie nichts tut.

<Project>
  <!-- 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 verfügt über drei Attribute, die die Aufgabe und die Inlineaufgabenfactory beschreiben, die sie kompiliert.

  • Das TaskName Attribut benennt die Aufgabe in diesem Fall DoNothing.

  • Das TaskFactory Attribut benennt die Klasse, die die Inlineaufgabenfactory implementiert.

  • Das AssemblyFile Attribut gibt die Position der Inlineaufgabenfactory an. Alternativ können Sie das AssemblyName Attribut verwenden, um den vollqualifizierten Namen der Inlineaufgaben-Factoryklasse anzugeben, die sich normalerweise im globalen Assemblycache (GAC) befindet.

Die verbleibenden Elemente des DoNothing Vorgangs sind leer und werden bereitgestellt, um die Reihenfolge und Struktur eines Inlinevorgangs zu veranschaulichen. Ein robusteres Beispiel wird weiter unten in diesem Artikel vorgestellt.

  • Das ParameterGroup-Element ist optional. Wenn angegeben, deklariert sie die Parameter für den Vorgang. 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 gibt Verweise auf die .NET-Assemblys an, die Sie in Ihrem Code verwenden. Dies entspricht dem Hinzufügen eines Verweises auf ein Projekt in Visual Studio. Das Include Attribut gibt den Pfad der Assembly an, auf die verwiesen wird.

  • Das Using Element listet die Namespaces auf, auf die Sie zugreifen möchten. Dieses Element ähnelt der using Direktive in Visual C#. Das Namespace Attribut gibt den einzuschließden Namespace an.

Reference und Using Elemente sind sprachunabhängig. Inlineaufgaben können in einer der unterstützten .NET CodeDom-Sprachen geschrieben werden, z. B. Visual Basic oder Visual C#.

Hinweis

Elemente, die Task vom Element enthalten sind, sind spezifisch für die Aufgabenfactory, in diesem Fall die Codeaufgabenfactory.

Codeelement

Das letzte untergeordnete Element, das innerhalb des Task Elements angezeigt werden soll, ist das Code Element. Das Code Element enthält oder sucht den Code, den Sie in einer Aufgabe kompilieren möchten. Was Sie in das Code Element einfügen, hängt davon ab, wie Sie die Aufgabe schreiben möchten.

Das Language Attribut gibt die Sprache an, in der Ihr Code geschrieben wird. Zulässige Werte sind cs für C# vb für Visual Basic.

Das Type Attribut gibt den Codetyp an, der Code im Element gefunden wird.

  • Wenn der Wert lautet TypeClass, enthält das Code Element Code für eine Klasse, die von der ITask Schnittstelle abgeleitet wird.

  • Wenn der Wert Type lautet Method, definiert der Code eine Außerkraftsetzung der Execute Methode der ITask Schnittstelle.

  • Wenn der Wert von Type ist Fragment, definiert der Code den Inhalt der Execute Methode, aber nicht die Signatur oder die return Anweisung.

Der Code selbst wird in der Regel zwischen einer <![CDATA[ Markierung und einer ]]> Markierung angezeigt. Da sich der Code in einem CDATA-Abschnitt befindet, müssen Sie sich keine Gedanken darüber machen, reservierte Zeichen zu entfernen, z. B. "<" oder ">".

Alternativ können Sie das Source Attribut des Code Elements verwenden, um den Speicherort einer Datei anzugeben, die den Code für ihre Aufgabe enthält. Der Code in der Quelldatei muss vom Typ sein, der durch das Type Attribut angegeben wird. Wenn das Source Attribut vorhanden ist, lautet Classder Standardwert von Type . Wenn Source nicht vorhanden, lautet Fragmentder Standardwert .

Hinweis

Wenn Sie die Aufgabenklasse in einer Quelldatei definieren, muss der Klassenname dem TaskName Attribut des entsprechenden UsingTask-Elements zustimmen.

Hallo Welt

Hier ist eine robustere Inlineaufgabe mit RoslynCodeTaskFactory. Die HelloWorld-Aufgabe zeigt "Hello, world!" auf dem Standardmäßigen Fehlerprotokollierungsgerät an, das in der Regel die Systemkonsole oder das Visual Studio-Ausgabefenster ist. Das Reference Element im Beispiel ist nur zur Veranschaulichung enthalten.

<Project>
  <!-- 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 den HelloWorld Vorgang in einer Datei mit dem Namen "HelloWorld.targets" speichern und dann wie folgt aus einem Projekt aufrufen.

<Project>
  <Import Project="HelloWorld.targets" />
  <Target Name="Hello">
    <HelloWorld />
  </Target>
</Project>

Eingabe- und Ausgabeparameter

Inlineaufgabenparameter sind untergeordnete Elemente eines ParameterGroup Elements. Jeder Parameter verwendet den Namen des Elements, das es definiert. Der folgende Code definiert den Parameter Text.

<ParameterGroup>
    <Text />
</ParameterGroup>

Parameter können mindestens eins dieser Attribute aufweisen:

  • Required ist ein optionales Attribut, das standardmäßig ist false . Wenn true, dann ist der Parameter erforderlich und muss vor dem Aufrufen der Aufgabe einen Wert erhalten.

  • ParameterType ist ein optionales Attribut, das standardmäßig ist System.String . Er kann auf einen vollqualifizierten Typ festgelegt werden, der entweder ein Element oder ein Wert ist, der mithilfe von System.Convert.ChangeType in eine und aus einer Zeichenfolge konvertiert werden kann. (Mit anderen Worten, jeder Typ, der an und von einem externen Vorgang übergeben werden kann.)

  • Output ist ein optionales Attribut, das standardmäßig ist false . If true, then the parameter must be given a value before returning from the Execute method.

Beispiel:

<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 über das Type Attribut oder Methoddas Attribut verfügtFragment, werden eigenschaften automatisch für jeden Parameter erstellt. Wenn das Element das CodeType Attribut hat, müssen Sie in RoslynCodeTaskFactory das Attribut nicht Classangeben ParameterGroup, da es aus dem Quellcode abgeleitet wird (dies ist ein Unterschied von CodeTaskFactory). Andernfalls müssen Eigenschaften explizit im Aufgabenquellcode deklariert werden und müssen exakt mit ihren Parameterdefinitionen übereinstimmen.

Beispiel

Die folgende Inlineaufgabe protokolliert einige Nachrichten und gibt eine Zeichenfolge zurück.

<Project>

    <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 Inlineaufgaben können Pfade kombinieren und den Dateinamen abrufen.

<Project>

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

Bereitstellen der Abwärtskompatibilität

RoslynCodeTaskFactory wurde zuerst in MSBuild, Version 15.8, verfügbar. Angenommen, Sie möchten frühere Versionen von Visual Studio und MSBuild unterstützen, wenn RoslynCodeTaskFactory sie nicht verfügbar war, aber CodeTaskFactory sie möchten dasselbe Buildskript verwenden. Sie können ein Choose Konstrukt verwenden, das die $(MSBuildVersion) Eigenschaft verwendet, um zur Erstellungszeit zu entscheiden, ob sie die RoslynCodeTaskFactory oder den Fallback verwenden CodeTaskFactorysoll, wie im folgenden Beispiel gezeigt:

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

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</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>