Udostępnij za pośrednictwem


Tworzenie zadania wbudowanego programu MSBuild za pomocą polecenia RoslynCodeTaskFactory

Podobnie jak w przypadku codeTaskFactory, roslynCodeTaskFactory używa kompilatorów roslyn międzyplatformowych do generowania zestawów zadań w pamięci do użycia jako zadań wbudowanych. Zadania RoslynCodeTaskFactory są przeznaczone dla platformy .NET Standard i mogą działać w środowiskach uruchomieniowych .NET Framework i .NET Core, a także na innych platformach, takich jak Linux i macOS.

Uwaga

Element RoslynCodeTaskFactory jest dostępny tylko w programie MSBuild 15.8 lub nowszym. Wersje programu MSBuild są zgodne z wersjami programu Visual Studio, więc program RoslynCodeTaskFactory jest dostępny w programie Visual Studio 2017 w wersji 15.8 lub nowszej.

Struktura zadania wbudowanego z elementem RoslynCodeTaskFactory

Zadania wbudowane RoslynCodeTaskFactory są deklarowane w taki sam sposób jak CodeTaskFactory, a jedyną różnicą jest to, że są one przeznaczone dla platformy .NET Standard. Wbudowane zadanie i UsingTask element, który go zawiera, są zwykle uwzględniane w pliku .targets i importowane do innych plików projektu zgodnie z potrzebami. Oto podstawowe zadanie wbudowane. Zwróć uwagę, że nic nie robi.

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

Element UsingTask w przykładzie ma trzy atrybuty opisujące zadanie i wbudowaną fabrykę zadań, która ją kompiluje.

  • Atrybut TaskName nazywa zadanie , w tym przypadku DoNothing.

  • Atrybuty TaskFactory nazwiją klasę, która implementuje fabrykę zadań wbudowanych.

  • Atrybut AssemblyFile udostępnia lokalizację wbudowanej fabryki zadań. Alternatywnie można użyć atrybutu AssemblyName , aby określić w pełni kwalifikowaną nazwę wbudowanej klasy fabryki zadań, która jest zwykle zlokalizowana w globalnej pamięci podręcznej zestawów (GAC).

Pozostałe elementy DoNothing zadania są puste i są udostępniane w celu zilustrowania kolejności i struktury zadania wbudowanego. Bardziej niezawodny przykład zostanie przedstawiony w dalszej części tego tematu.

  • Element ParameterGroup jest opcjonalny. Gdy zostanie określony, deklaruje parametry zadania. Aby uzyskać więcej informacji na temat parametrów wejściowych i wyjściowych, zobacz Parametry wejściowe i wyjściowe w dalszej części tego tematu.

  • Element Task opisuje i zawiera kod źródłowy zadania.

  • Element Reference określa odwołania do zestawów .NET używanych w kodzie. Jest to odpowiednik dodawania odwołania do projektu w programie Visual Studio. Atrybut Include określa ścieżkę przywołytowanego zestawu.

  • Element Using zawiera listę przestrzeni nazw, do których chcesz uzyskać dostęp. Przypomina to instrukcję Using w języku Visual C#. Atrybut Namespace określa przestrzeń nazw do uwzględnienia.

Reference i Using elementy są niezależne od języka. Zadania wbudowane można napisać w jednym z obsługiwanych języków .NET CodeDom, na przykład Visual Basic lub Visual C#.

Uwaga

Elementy zawarte przez Task element są specyficzne dla fabryki zadań, w tym przypadku fabryki zadań kodu.

Element kodu

Ostatnim elementem podrzędnym Task , który ma pojawić się w elemecie , jest Code element . Element Code zawiera lub lokalizuje kod, który chcesz skompilować w zadaniu. To, co umieścisz w elemenie Code , zależy od tego, jak chcesz napisać zadanie.

Atrybut Language określa język, w którym jest napisany kod. Dopuszczalne wartości to cs C#, vb dla języka Visual Basic.

Atrybut Type określa typ kodu, który znajduje się w elemecie Code .

  • Jeśli wartość Type to Class, Code element zawiera kod klasy pochodzącej z interfejsu ITask .

  • Jeśli wartość Type to Method, kod definiuje przesłonięć Execute metodę interfejsu ITask .

  • Jeśli wartość Type to Fragment, kod definiuje zawartość Execute metody, ale nie podpis lub instrukcję return .

Sam kod zwykle pojawia się między znacznikiem <![CDATA[ a znacznikiem ]]> . Ponieważ kod znajduje się w sekcji CDATA, nie musisz martwić się o ucieczkę zastrzeżonych znaków, na przykład "<" lub ">".

Alternatywnie możesz użyć Source atrybutu Code elementu, aby określić lokalizację pliku zawierającego kod zadania. Kod w pliku źródłowym musi być typu określony przez Type atrybut . Source Jeśli atrybut jest obecny, wartość domyślna to Type Class. Jeśli Source nie ma wartości, wartość domyślna to Fragment.

Uwaga

Podczas definiowania klasy zadań w pliku źródłowym nazwa klasy musi być zgodna z atrybutem TaskName odpowiedniego elementu UsingTask .

Witaj, świecie

Oto bardziej niezawodne zadanie wbudowane w rozwiązaniu RoslynCodeTaskFactory. Zadanie HelloWorld wyświetla komunikat "Hello, world!" na domyślnym urządzeniu rejestrowania błędów, które jest zazwyczaj konsolą systemową lub oknem danych wyjściowych programu Visual Studio. Element Reference w przykładzie jest dołączany tylko do ilustracji.

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

Zadanie HelloWorld można zapisać w pliku o nazwie HelloWorld.targets, a następnie wywołać je z projektu w następujący sposób.

<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="HelloWorld.targets" />
  <Target Name="Hello">
    <HelloWorld />
  </Target>
</Project>

Parametry wejściowe i wyjściowe

Wbudowane parametry zadania to elementy podrzędne ParameterGroup elementu. Każdy parametr przyjmuje nazwę elementu, który go definiuje. Poniższy kod definiuje parametr Text.

<ParameterGroup>
    <Text />
</ParameterGroup>

Parametry mogą mieć co najmniej jeden z następujących atrybutów:

  • Required jest opcjonalnym atrybutem, który jest false domyślnie. Jeśli trueparametr jest wymagany i musi mieć wartość przed wywołaniem zadania.

  • ParameterType jest opcjonalnym atrybutem, który jest System.String domyślnie. Można go ustawić na dowolny w pełni kwalifikowany typ, który jest elementem lub wartością, którą można przekonwertować na i z ciągu przy użyciu elementu System.Convert.ChangeType. (Innymi słowy, dowolny typ, który można przekazać do i z zadania zewnętrznego).

  • Output jest opcjonalnym atrybutem, który jest false domyślnie. Jeśli trueparametr , należy podać wartość przed zwróceniem z metody Execute.

Przykład:

<ParameterGroup>
    <Expression Required="true" />
    <Files ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
    <Tally ParameterType="System.Int32" Output="true" />
</ParameterGroup>

definiuje następujące trzy parametry:

  • Expression jest wymaganym parametrem wejściowym typu System.String.

  • Files jest wymaganym parametrem wejściowym listy elementów.

  • Tally jest parametrem wyjściowym typu System.Int32.

Code Jeśli element ma Type atrybut Fragment lub Method, właściwości są tworzone automatycznie dla każdego parametru. W pliku RoslynCodeTaskFactory, jeśli Code element ma Type atrybut Class, nie trzeba określać ParameterGroupelementu , ponieważ jest on wnioskowany z kodu źródłowego (jest to różnica od CodeTaskFactory). W przeciwnym razie właściwości muszą być jawnie zadeklarowane w kodzie źródłowym zadania i muszą dokładnie odpowiadać ich definicjom parametrów.

Przykład

Następujące wbudowane zadanie rejestruje niektóre komunikaty i zwraca ciąg.

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

Te wbudowane zadania mogą łączyć ścieżki i pobierać nazwę pliku.

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

Zapewnianie zgodności z poprzednimi wersjami

RoslynCodeTaskFactory po raz pierwszy stał się dostępny w programie MSBuild w wersji 15.8. Załóżmy, że masz sytuację, w której chcesz obsługiwać poprzednie wersje programów Visual Studio i MSBuild, gdy RoslynCodeTaskFactory były niedostępne, ale CodeTaskFactory były, ale chcesz użyć tego samego skryptu kompilacji. Możesz użyć konstrukcji korzystającej Choose $(MSBuildVersion) z właściwości , aby zdecydować w czasie kompilacji, czy użyć RoslynCodeTaskFactory obiektu lub powrócić do CodeTaskFactoryelementu , jak w poniższym przykładzie:

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