Compartilhar via


Tarefas embutidas do MSBuild

As tarefas do MSBuild normalmente são criadas compilando uma classe que implementa a ITask interface. Para obter mais informações, consulte Tarefas.

Quando quiser evitar a sobrecarga de criar uma tarefa compilada, você pode criar uma tarefa embutida no arquivo de projeto ou em um arquivo importado. Você não precisa criar um assembly separado para hospedar a tarefa. O uso de uma tarefa embutida facilita o controle do código-fonte e a implantação da tarefa. O código-fonte é integrado ao arquivo de projeto do MSBuild ou ao arquivo importado, normalmente um .targets arquivo.

Você cria uma tarefa embutida usando uma fábrica de tarefas de código. Para o desenvolvimento atual, use RoslynCodeTaskFactory, não CodeTaskFactory. CodeTaskFactory só dá suporte a versões do C# até 4.0.

Tarefas embutidas são destinadas como uma conveniência para tarefas pequenas que não exigem dependências complicadas. O suporte de depuração para tarefas embutidas é limitado. É recomendável criar uma tarefa compilada em vez de tarefa embutida quando você deseja escrever um código mais complexo, fazer referência a um pacote NuGet, executar ferramentas externas ou executar operações que possam produzir condições de erro. Além disso, as tarefas embutidas são compiladas sempre que você cria, para que possa haver um impacto perceptível no desempenho da compilação.

A estrutura de uma tarefa embutida

Uma tarefa embutida é contida por um elemento UsingTask . A tarefa embutida e o elemento que a UsingTask contém normalmente são incluídos em um .targets arquivo e importados para outros arquivos de projeto, conforme necessário. Aqui está uma tarefa embutida básica que não faz nada, mas ilustra a sintaxe:

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

O UsingTask elemento no exemplo tem três atributos que descrevem a tarefa e a fábrica de tarefas embutidas que a compila.

  • O TaskName atributo nomeia a tarefa, nesse caso, DoNothing.

  • O TaskFactory atributo nomeia a classe que implementa a fábrica de tarefas embutidas.

  • O AssemblyFile atributo fornece o local da fábrica de tarefas embutidas. Como alternativa, você pode usar o AssemblyName atributo para especificar o nome totalmente qualificado da classe de fábrica de tarefas embutida, que normalmente está localizada em $(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll.

Os elementos restantes da DoNothing tarefa estão vazios e são fornecidos para ilustrar a ordem e a estrutura de uma tarefa embutida. Um exemplo completo é apresentado posteriormente neste artigo.

  • O elemento ParameterGroup é opcional. Quando especificado, ele declara os parâmetros para a tarefa. Para obter mais informações sobre parâmetros de entrada e saída, consulte os parâmetros de entrada e saída mais adiante neste artigo.

  • O Task elemento descreve e contém o código-fonte da tarefa.

  • O Reference elemento especifica referências aos assemblies .NET que você está usando em seu código. Usar esse elemento é equivalente a adicionar uma referência a um projeto no Visual Studio. O Include atributo especifica o caminho do assembly referenciado. Assemblies em mscorlib, .NET Standard, Microsoft.Build.Framework e Microsoft.Build.Utilities.Core, bem como alguns assemblies que são referenciados transitivamente como dependências, estão disponíveis sem um Reference.

  • O Using elemento lista os namespaces que você deseja acessar. Esse elemento é equivalente à using diretiva em C#. O Namespace atributo especifica o namespace a ser incluído. Não funciona para colocar uma using diretiva no código embutido, porque esse código é colocado em um corpo do método, onde using as diretivas não são permitidas.

Reference e Using os elementos são independentes de linguagem. Tarefas embutidas podem ser escritas no Visual Basic ou em C#.

Observação

Os elementos contidos pelo Task elemento são específicos para a fábrica de tarefas, nesse caso, a fábrica de tarefas de código.

Elemento code

O último elemento filho a ser exibido dentro do Task elemento é o Code elemento. O Code elemento contém ou localiza o código que você deseja que seja compilado em uma tarefa. O que você coloca no Code elemento depende de como você deseja escrever a tarefa.

O Language atributo especifica o idioma no qual o código é escrito. Os valores aceitáveis são cs para C#, vb para Visual Basic.

O Type atributo especifica o tipo de código encontrado no Code elemento.

  • Se o valor Type for Class, o elemento conterá código Code para uma classe derivada da ITask interface.

  • Se o valor Type for Method, o código definirá uma substituição do Execute método da ITask interface.

  • Se o valor Type for Fragment, o código definirá o conteúdo do Execute método, mas não a assinatura ou a instrução return .

O próprio código normalmente aparece entre um <![CDATA[ marcador e um ]]> marcador. Como o código está em uma seção CDATA, você não precisa se preocupar em escapar de caracteres reservados, por exemplo, "<" ou ">".

Como alternativa, você pode usar o Source atributo do Code elemento para especificar o local de um arquivo que contém o código para sua tarefa. O código no arquivo de origem deve ser do tipo especificado pelo Type atributo. Se o Source atributo estiver presente, o valor Type padrão será Class. Se Source não estiver presente, o valor padrão será Fragment.

Observação

Ao definir a classe de tarefa no arquivo de origem, o nome da classe deve concordar com o TaskName atributo do elemento UsingTask correspondente.

HelloWorld

Aqui está um exemplo de uma tarefa embutida simples. A tarefa HelloWorld exibe "Olá, mundo!" no dispositivo de log de erros padrão, que normalmente é o console do sistema ou a janela Saída do 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>

Você pode salvar a tarefa HelloWorld em um arquivo chamado HelloWorld.targets e invocá-la de um projeto da seguinte maneira.

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

Parâmetros de entrada e saída

Parâmetros de tarefa embutidos são elementos filho de um ParameterGroup elemento. Cada parâmetro usa o nome do elemento que o define. O código a seguir define o parâmetro Text.

<ParameterGroup>
  <Text />
</ParameterGroup>

Os parâmetros podem ter um ou mais desses atributos:

  • Required é um atributo opcional que é false por padrão. Se true, o parâmetro é necessário e deve receber um valor antes de chamar a tarefa.
  • ParameterType é um atributo opcional que é System.String por padrão. Ele pode ser definido como qualquer tipo totalmente qualificado que seja um item ou um valor que possa ser convertido de e para uma cadeia de caracteres usando ChangeType. (Em outras palavras, qualquer tipo que possa ser passado de e para uma tarefa externa.)
  • Output é um atributo opcional que é false por padrão. Se true, o parâmetro deverá receber um valor antes de retornar do método Execute.

Por exemplo

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

define estes três parâmetros:

  • Expression é um parâmetro de entrada necessário do tipo System.String.

  • Files é um parâmetro de entrada de lista de itens necessário.

  • Tally é um parâmetro de saída do tipo System.Int32.

Se o Code elemento tiver o Type atributo de Fragment ou Method, em seguida, as propriedades serão criadas automaticamente para cada parâmetro. Caso contrário, as propriedades devem ser declaradas explicitamente no código-fonte da tarefa e devem corresponder exatamente às definições de parâmetro.

Depurar uma tarefa embutida

O MSBuild gera um arquivo de origem na tarefa embutida e grava a saída no arquivo de texto com um nome de arquivo GUID na pasta de arquivos temporários , AppData\Local\Temp\MSBuildTemp. A saída normalmente é excluída, mas para preservar esse arquivo de saída, você pode definir a variável MSBUILDLOGCODETASKFACTORYOUTPUT de ambiente como 1.

Exemplo 1

A tarefa embutida a seguir substitui cada ocorrência de um token no arquivo fornecido pelo valor fornecido.

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

Exemplo 2

A tarefa embutida a seguir gera uma saída serializada. Este exemplo mostra o uso de um parâmetro de saída e uma referência.

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