Partilhar 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. Não é necessário 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 no arquivo de projeto MSBuild ou arquivo importado, normalmente um .targets arquivo.

Você cria uma tarefa embutida usando uma fábrica de tarefas de código. Para o desenvolvimento atual, certifique-se de usar RoslynCodeTaskFactory, não CodeTaskFactory. CodeTaskFactory suporta apenas versões C# até 4.0.

As tarefas em linha destinam-se a ser uma conveniência para pequenas tarefas que não exigem dependências complicadas. O suporte de depuração para tarefas em linha é limitado. É recomendável criar uma tarefa compilada em vez de uma tarefa embutida quando você deseja escrever 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 em linha são compiladas sempre que o build é criado, podendo haver um impacto notável no desempenho do build.

A estrutura de uma tarefa embutida

Uma tarefa embutida é contida por um elemento UsingTask . A tarefa embutida e o elemento UsingTask que a contém são normalmente incluídos num ficheiro .targets e importados para outros ficheiros de projeto, conforme necessário. Aqui está uma tarefa básica embutida 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 embutida que a compila.

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

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

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

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

  • O ParameterGroup elemento é 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 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 algumas assemblies que são referenciadas transitivamente como dependências, estão disponíveis sem um Reference.

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

Reference e Using são elementos agnósticos em relação à linguagem. As tarefas embutidas podem ser escritas em Visual Basic ou C#.

Observação

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

Elemento de código

O último elemento filho a aparecer 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 de Type é Class, então o Code elemento contém código para uma classe que deriva da ITask interface.

  • Se o valor de Type é Method, então o código define uma substituição do Execute método da ITask interface.

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

O código em si 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, pode usar o atributo Source do elemento Code para especificar a localização de um arquivo que contém o código da 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 padrão de Type é 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 inline simples. A tarefa HelloWorld exibe "Olá, mundo!" no dispositivo de log de erros padrão, que normalmente é o console do sistema ou a janela de 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, em seguida, invocá-la a partir de um projeto da seguinte maneira.

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

Parâmetros de entrada e saída

Os parâmetros de tarefa embutidos são elementos filho de um ParameterGroup elemento. Cada parâmetro leva 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 destes atributos:

  • Required é um atributo opcional que é false por padrão. Se true, então 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 para e de 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, então o parâmetro deve 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 obrigatório.

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

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

Depurar uma tarefa embutida

MSBuild gera um arquivo de origem da tarefa embutida e grava a saída num ficheiro de texto com um nome de ficheiro GUID na pasta de ficheiros 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 todas as ocorrências 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 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>