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


Встроенные задачи MSBuild

Задачи MSBuild обычно создаются путем компиляции класса, реализующего ITask интерфейс. Дополнительные сведения см. в разделе Задачи.

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

Вы создаете встроенную задачу с помощью фабрики задач в коде. Для текущей разработки не забудьте использовать RoslynCodeTaskFactory, а не CodeTaskFactory. CodeTaskFactory поддерживает только версии C# до 4.0.

Встроенные задачи предназначены для небольших задач, которые не требуют сложных зависимостей. Поддержка отладки встроенных задач ограничена. Рекомендуется создать скомпилированную задачу вместо встроенной задачи, если требуется написать более сложный код, ссылаться на пакет NuGet, запускать внешние инструменты или выполнять операции, которые могут привести к ошибкам. Кроме того, встроенные задачи компилируются каждый раз при сборке, поэтому может быть заметное влияние на производительность сборки.

Структура встроенной задачи

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

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

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

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

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

  • Атрибут AssemblyFile предоставляет расположение встроенной фабрики задач. Кроме того, атрибут можно использовать AssemblyName для указания полного имени класса встроенной фабрики задач, который обычно находится в $(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll.

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

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

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

  • Элемент Reference указывает ссылки на сборки .NET, которые вы используете в коде. Использование этого элемента эквивалентно добавлению ссылки на проект в Visual Studio. Атрибут Include задает путь к указанной сборке. Сборки в mscorlib, .NET Standard, Microsoft.Build.Framework и Microsoft.Build.Utilities.Core, а также некоторые сборки, на которые транзитивно ссылаются как зависимости, доступны без Reference.

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

Reference и Using элементы являются независимыми от языка. Встроенные задачи можно записывать в Visual Basic или 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, вам не нужно беспокоиться о выходе зарезервированных символов, например "" или "<>".

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

Замечание

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

HelloWorld

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

<Project>
  <!-- This simple inline task displays "Hello, world!" -->
  <UsingTask
    TaskName="HelloWorld"
    TaskFactory="RoslynCodeTaskFactory"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll" >
    <ParameterGroup />
    <Task>
      <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 Его можно задать для любого полностью квалифицированного типа, который является элементом или значением, которое можно преобразовать в строку и обратно с помощью 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, то свойства автоматически создаются для каждого параметра. В противном случае свойства должны быть явно объявлены в исходном коде задачи и должны точно соответствовать их определениям параметров.

Отладка встроенной задачи

MSBuild создает исходный файл встроенной задачи и записывает выходные данные в текстовый файл с именем GUID в папке временных файлов AppData\Local\Temp\MSBuildTemp. Выходные данные обычно удаляются, но для сохранения этого выходного файла можно задать для переменной MSBUILDLOGCODETASKFACTORYOUTPUT среды значение 1.

Пример 1

Следующая встроенная задача заменяет каждое вхождение маркера в заданном файле заданным значением.

<Project>

  <UsingTask TaskName="TokenReplace" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
    <ParameterGroup>
      <Path ParameterType="System.String" Required="true" />
      <Token ParameterType="System.String" Required="true" />
      <Replacement ParameterType="System.String" Required="true" />
    </ParameterGroup>
    <Task>
      <Code Type="Fragment" Language="cs"><![CDATA[
string content = File.ReadAllText(Path);
content = content.Replace(Token, Replacement);
File.WriteAllText(Path, content);

]]></Code>
    </Task>
  </UsingTask>

  <Target Name='Demo' >
    <TokenReplace Path="Target.config" Token="$MyToken$" Replacement="MyValue"/>
  </Target>
</Project>

Пример 2

Следующая встроенная задача создает сериализованные выходные данные. В этом примере показано использование выходного параметра и ссылки.

<Project>
  <PropertyGroup>
    <RoslynCodeTaskFactoryAssembly Condition="$(RoslynCodeTaskFactoryAssembly) == ''">$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll</RoslynCodeTaskFactoryAssembly>
  </PropertyGroup>

    <UsingTask 
    TaskName="MyInlineTask" 
    TaskFactory="RoslynCodeTaskFactory" 
    AssemblyFile="$(RoslynCodeTaskFactoryAssembly)">
    <ParameterGroup>
      <Input ParameterType="System.String" Required="true" />
      <Output ParameterType="System.String" Output="true" />
    </ParameterGroup>
    <Task>
      <Reference Include="System.Text.Json" /> <!-- Reference an assembly -->
      <Using Namespace="System.Text.Json" />   <!-- Use a namespace -->
      <Code Type="Fragment" Language="cs">
        <![CDATA[
          Output = JsonSerializer.Serialize(new { Message = Input });
        ]]>
      </Code>
    </Task>
  </UsingTask>

  <Target Name="RunInlineTask">
    <MyInlineTask Input="Hello, Roslyn!" >
      <Output TaskParameter="Output" PropertyName="SerializedOutput" />
    </MyInlineTask>
    <Message Text="Serialized Output: $(SerializedOutput)" />
  </Target>
</Project>