Поделиться через


Создание встроенной задачи MSBuild с помощью RoslynCodeTaskFactory

RoslynCodeTaskFactory использует кроссплатформенные компиляторы Roslyn для создания сборок задач в памяти для использования в качестве встроенных задач. RoslynCodeTaskFactory задачи предназначены для .NET Standard и могут работать в средах выполнения .NET Framework и .NET Core, а также на других платформах, таких как Linux и macOS.

Замечание

RoslynCodeTaskFactory доступен только в MSBuild 15.8 и выше. Версии MSBuild следуют версиям Visual Studio, поэтому RoslynCodeTaskFactory доступна в Visual Studio 2017 версии 15.8 и выше.

Структура встроенной задачи с Помощью RoslynCodeTaskFactory

RoslynCodeTaskFactory Встроенные задачи объявляются с помощью UsingTask элемента. Встроенная задача и UsingTask элемент, содержащий его, обычно включаются в файл и импортируются в .targets другие файлы проекта по мере необходимости. Ниже приведена базовая встроенная задача. Обратите внимание, что это ничего не делает.

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

Элемент UsingTask в примере содержит три атрибута, описывающие задачу и встроенную фабрику задач, которая компилирует ее.

  • Атрибут TaskName называет задачу в данном случае DoNothing.

  • Атрибут TaskFactory именует класс, реализующий встроенную фабрику задач.

  • Атрибут AssemblyFile предоставляет расположение встроенной фабрики задач. Кроме того, атрибут можно использовать AssemblyName для указания полного имени класса встроенной фабрики задач, который обычно находится в глобальном кэше сборок (GAC).

Остальные элементы DoNothing задачи пусты и предоставляются для иллюстрации порядка и структуры встроенной задачи. Более надежный пример представлен далее в этой статье.

  • Элемент ParameterGroup необязательный. При указании он объявляет параметры для задачи. Дополнительные сведения о входных и выходных параметрах см. далее в этой статье.

  • Элемент Task описывает и содержит исходный код задачи.

  • Элемент Reference указывает ссылки на сборки .NET, которые вы используете в коде. Это эквивалентно добавлению ссылки на проект в Visual Studio. Атрибут Include задает путь к указанной сборке.

  • Элемент Using перечисляет пространства имен, к которым требуется получить доступ. Этот элемент напоминает директиву using в Visual C#. Атрибут Namespace указывает пространство имен для включения.

Reference и Using элементы являются языковыми, не зависящими от языка. Встроенные задачи можно писать на любом из поддерживаемых языков .NET CodeDom, например Visual Basic или Visual C#.

Замечание

Элементы, содержащиеся Task элементом, относятся к фабрике задач, в данном случае фабрике задач кода.

Элемент кода

Последний дочерний элемент, который будет отображаться в элементе Task , является элементом Code . Элемент Code содержит или находит код, который требуется скомпилировать в задачу. То, что вы помещаете Code в элемент, зависит от способа записи задачи.

Атрибут Language задает язык, на котором написан код. Допустимые значения предназначены cs для C#, vb для Visual Basic.

Атрибут Type указывает тип кода, найденного в элементе Code .

  • Если значение Type равно Class, Code элемент содержит код для класса, наследуемого ITask от интерфейса.

  • Если значение Type равно Method, код определяет переопределение Execute метода ITask интерфейса.

  • Если значение Type равно Fragment, код определяет содержимое Execute метода, но не подпись или return оператор.

Сам код обычно отображается между маркером <![CDATA[ и маркером ]]> . Так как код находится в разделе CDATA, вам не нужно беспокоиться о выходе зарезервированных символов, например "<" или ">".

Кроме того, атрибут элемента можно использовать SourceCode для указания расположения файла, содержащего код для задачи. Код в исходном файле должен иметь тип, указанный атрибутом Type . Source Если атрибут присутствует, значение Type по умолчанию равноClass. Если Source он отсутствует, значение по умолчанию равно Fragment.

Замечание

Если вы определяете класс задач в исходном файле, имя класса должно согласиться с TaskName атрибутом соответствующего элемента UsingTask .

Всем привет

Ниже приведена более надежная встроенная задача.RoslynCodeTaskFactory Задача HelloWorld отображает "Hello, world!" на устройстве ведения журнала ошибок по умолчанию, которое обычно является системной консолью или окном вывода Visual Studio. Элемент Reference в примере включается только для иллюстрации.

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

Можно сохранить HelloWorld задачу в файле с именем HelloWorld.targets, а затем вызвать его из проекта следующим образом.

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

Входные и выходные параметры

Встроенные параметры задачи — это дочерние элементы ParameterGroup элемента. Каждый параметр принимает имя элемента, определяющего его. Следующий код определяет параметр Text.

<ParameterGroup>
    <Text />
</ParameterGroup>

Параметры могут иметь один или несколько следующих атрибутов:

  • Required— это необязательный атрибут, который по умолчанию.false Если trueпараметр является обязательным и должен быть задано значение перед вызовом задачи.

  • ParameterType— это необязательный атрибут, который по умолчанию.System.String Для него может быть задан любой полный тип, который является элементом или значением, которое можно преобразовать в строку и из нее с помощью System.Convert.ChangeType. (Другими словами, любой тип, который можно передать во внешнюю задачу и из нее.)

  • Output— это необязательный атрибут, который по умолчанию.false Если trueпараметр должен быть задан перед возвратом из метода Execute.

Например

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

определяет эти три параметра:

  • Expression — обязательный входной параметр типа System.String.

  • Files — обязательный входной параметр списка элементов.

  • Tally — выходной параметр типа System.Int32.

Code Если элемент имеет Type атрибут Fragment или Method, то свойства автоматически создаются для каждого параметра. В RoslynCodeTaskFactory, если Code элемент имеет Type атрибут Class, то не нужно указывать ParameterGroupзначение , так как он выводится из исходного кода (это разница от CodeTaskFactory). В противном случае свойства должны быть явно объявлены в исходном коде задачи и должны точно соответствовать их определениям параметров.

Пример

Следующие встроенные журналы задач записывают некоторые сообщения и возвращают строку.

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

Эти встроенные задачи могут объединять пути и получать имя файла.

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

Обеспечение обратной совместимости

RoslynCodeTaskFactory впервые стало доступно в MSBuild версии 15.8. Предположим, вы хотите поддерживать предыдущие версии Visual Studio и MSBuild, если RoslynCodeTaskFactory она недоступна, но CodeTaskFactory вы хотите использовать тот же скрипт сборки. Можно использовать Choose конструкцию, которая использует $(MSBuildVersion) свойство для выбора во время сборки, следует ли использовать RoslynCodeTaskFactory или вернуться CodeTaskFactoryобратно, как показано в следующем примере:

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