Compartilhar via


Estender o processo de build do Visual Studio

O processo de build do Visual Studio é definido por uma série de arquivos MSBuild .targets que são importados para o arquivo de projeto. Essas importações serão implícitas, se você usar um SDK como os projetos do Visual Studio normalmente usam. Um desses arquivos importados, Microsoft.Common.targets, pode ser estendido para permitir que você execute tarefas personalizadas em vários pontos do processo de build. Este artigo explica três métodos que você pode usar para estender o processo de build do Visual Studio:

  • Crie um destino personalizado e especifique quando ele deve ser executado usando BeforeTargets e AfterTargets atributos.

  • Substitua as DependsOn propriedades definidas nos destinos comuns.

  • Substitua destinos predefinidos específicos definidos nos destinos comuns (Microsoft.Common.targets ou nos arquivos importados).

AfterTargets e BeforeTargets

Você pode usar AfterTargets e BeforeTargets atributos em seu destino personalizado para especificar quando ele deve ser executado.

O exemplo a seguir mostra como usar o AfterTargets atributo para adicionar um destino personalizado que faz algo com os arquivos de saída. Nesse caso, ele copia os arquivos de saída para uma nova pasta CustomOutput. O exemplo também mostra como limpar os arquivos criados pela operação de build personalizada com um CustomClean destino usando um BeforeTargets atributo e especificando que a operação de limpeza personalizada é executada antes do CoreClean destino.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
     <TargetFramework>netcoreapp3.1</TargetFramework>
     <_OutputCopyLocation>$(OutputPath)..\..\CustomOutput\</_OutputCopyLocation>
  </PropertyGroup>

  <Target Name="CustomAfterBuild" AfterTargets="Build">
    <ItemGroup>
      <_FilesToCopy Include="$(OutputPath)**\*"/>
    </ItemGroup>
    <Message Text="_FilesToCopy: @(_FilesToCopy)" Importance="high"/>

    <Message Text="DestFiles:
        @(_FilesToCopy->'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>

    <Copy SourceFiles="@(_FilesToCopy)"
          DestinationFiles=
          "@(_FilesToCopy->'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>
  </Target>

  <Target Name="CustomClean" BeforeTargets="CoreClean">
    <Message Text="Inside Custom Clean" Importance="high"/>
    <ItemGroup>
      <_CustomFilesToDelete Include="$(_OutputCopyLocation)**\*"/>
    </ItemGroup>
    <Delete Files='@(_CustomFilesToDelete)'/>
  </Target>
</Project>

Aviso

Certifique-se de usar nomes diferentes dos destinos predefinidos (por exemplo, o destino de build personalizado aqui é CustomAfterBuild, não AfterBuild), uma vez que esses destinos predefinidos são substituídos pela importação do SDK que também os define. Consulte a tabela no final deste artigo para obter uma lista de destinos predefinidos.

Estender as propriedades DependsOn

Outra maneira de estender o processo de build é usar as DependsOn propriedades (por exemplo, BuildDependsOn), para especificar destinos que devem ser executados antes de um destino padrão.

Esse método é preferível à substituição de destinos predefinidos, o que é discutido na próxima seção. Substituir destinos predefinidos é um método mais antigo que ainda tem suporte, mas, como o MSBuild avalia a definição de destinos sequencialmente, não há como impedir que outro projeto que importe seu projeto substitua os destinos que você já substituiu. Portanto, por exemplo, o último AfterBuild destino definido no arquivo de projeto, depois que todos os outros projetos tiverem sido importados, será o que será usado durante o build.

Você pode se proteger contra substituições não intencionais de destinos alterando as propriedades DependsOn que são usadas nos atributos DependsOnTargets ao longo dos destinos comuns. Por exemplo, o Build destino contém um atributo DependsOnTargets com o valor de "$(BuildDependsOn)". Considere:

<Target Name="Build" DependsOnTargets="$(BuildDependsOn)"/>

Este pedaço de XML indica que, antes que o Build destino possa ser executado, todos os destinos especificados na BuildDependsOn propriedade devem ser executados primeiro. A BuildDependsOn propriedade é definida como:

<PropertyGroup>
    <BuildDependsOn>
        $(BuildDependsOn);
        BeforeBuild;
        CoreBuild;
        AfterBuild
    </BuildDependsOn>
</PropertyGroup>

Você pode substituir esse valor de propriedade declarando outra propriedade nomeada BuildDependsOn no final do arquivo de projeto. Em um projeto no estilo SDK, isso significa que você precisa usar importações explícitas. Consulte importações implícitas e explícitas, para que você possa colocar a DependsOn propriedade após a última importação. Ao incluir a propriedade anterior BuildDependsOn na nova propriedade, você pode adicionar novos destinos ao início e ao final da lista de destino. Por exemplo:

<PropertyGroup>
    <BuildDependsOn>
        MyCustomTarget1;
        $(BuildDependsOn);
        MyCustomTarget2
    </BuildDependsOn>
</PropertyGroup>

<Target Name="MyCustomTarget1">
    <Message Text="Running MyCustomTarget1..."/>
</Target>
<Target Name="MyCustomTarget2">
    <Message Text="Running MyCustomTarget2..."/>
</Target>

Projetos que importam seu arquivo de projeto podem estender ainda mais essas propriedades sem substituir as personalizações que você fez.

Para substituir uma propriedade DependsOn

  1. Identifique uma propriedade predefinida DependsOn nos destinos comuns que você deseja substituir. Consulte a tabela a seguir para uma lista das propriedades normalmente substituídas DependsOn.

  2. Defina outra instância da propriedade ou das propriedades no final do arquivo de projeto. Inclua a propriedade original, por exemplo $(BuildDependsOn), na nova propriedade.

  3. Defina seus alvos personalizados antes ou depois da definição da propriedade.

  4. Compile o arquivo de projeto.

Propriedades DependsOn geralmente substituídas

Nome da propriedade Os destinos adicionados são executados antes deste ponto:
BuildDependsOn O ponto de entrada de build principal. Substitua essa propriedade se você quiser inserir destinos personalizados antes ou depois de todo o processo de compilação.
RebuildDependsOn O Rebuild
RunDependsOn A execução da saída de build final (se for um .EXE)
CompileDependsOn A compilação (Compile destino). Sobrescreva esta propriedade se você quiser inserir processos personalizados antes ou depois da etapa de compilação.
CreateSatelliteAssembliesDependsOn A criação dos conjuntos satelitais
CleanDependsOn O Clean alvo (exclusão de todas as saídas de compilação intermediárias e finais). Substitua essa propriedade caso deseje limpar a saída do processo de compilação personalizado.
PostBuildEventDependsOn O PostBuildEvent alvo
PublishBuildDependsOn Compilar publicação
ResolveAssemblyReferencesDependsOn O ResolveAssemblyReferences alvo (localizando o fechamento transitivo de dependências para uma determinada dependência). Consulte ResolveAssemblyReference.

Exemplo: BuildDependsOn e CleanDependsOn

O exemplo a seguir é semelhante ao exemplo BeforeTargets e AfterTargets, mas mostra como obter funcionalidade semelhante. Ele estende o build usando BuildDependsOn para adicionar sua própria tarefa CustomAfterBuild que copia os arquivos de saída após o build e também adiciona a tarefa correspondente CustomClean usando CleanDependsOn.

Neste exemplo, este é um projeto no estilo SDK. Conforme mencionado na nota sobre projetos no estilo SDK anteriormente neste artigo, você deve usar o método de importação manual em vez do atributo que o Sdk Visual Studio usa ao gerar arquivos de projeto.

<Project>
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk"/>

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk"/>

  <PropertyGroup>
    <BuildDependsOn>
      $(BuildDependsOn);CustomAfterBuild
    </BuildDependsOn>

    <CleanDependsOn>
      $(CleanDependsOn);CustomClean
    </CleanDependsOn>

    <_OutputCopyLocation>$(OutputPath)..\..\CustomOutput\</_OutputCopyLocation>
  </PropertyGroup>

  <Target Name="CustomAfterBuild">
    <ItemGroup>
      <_FilesToCopy Include="$(OutputPath)**\*"/>
    </ItemGroup>
    <Message Importance="high" Text="_FilesToCopy: @(_FilesToCopy)"/>

    <Message Text="DestFiles:
      @(_FilesToCopy-&gt;'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>

    <Copy SourceFiles="@(_FilesToCopy)"
          DestinationFiles="@(_FilesToCopy-&gt;'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>
  </Target>

  <Target Name="CustomClean">
    <Message Importance="high" Text="Inside Custom Clean"/>
    <ItemGroup>
      <_CustomFilesToDelete Include="$(_OutputCopyLocation)**\*"/>
    </ItemGroup>
    <Delete Files="@(_CustomFilesToDelete)"/>
  </Target>
</Project>

A ordem dos elementos é importante. Os elementos BuildDependsOn e CleanDependsOn devem aparecer depois de importar o arquivo de destino do SDK padrão.

Sobrescrever metas predefinidas

Os arquivos comuns .targets contêm um conjunto de destinos vazios predefinidos que são chamados antes e depois de alguns dos principais destinos no processo de build. Por exemplo, o MSBuild chama o BeforeBuild destino antes do destino principal CoreBuild e do AfterBuild destino após o CoreBuild destino. Por padrão, os destinos vazios nos destinos comuns não fazem nada, mas você pode substituir o comportamento padrão definindo os destinos desejados em um arquivo de projeto. Os métodos descritos anteriormente neste artigo são preferenciais, mas você pode encontrar código mais antigo que usa esse método.

Se o projeto usar um SDK (por exemplo Microsoft.Net.Sdk), você precisará fazer uma alteração de importações implícitas para explícitas, conforme discutido em importações explícitas e implícitas.

Para substituir um destino predefinido

  1. Se o projeto usar o Sdk atributo, altere-o para a sintaxe de importação explícita. Consulte importações explícitas e implícitas.

  2. Identifique um destino predefinido nos destinos comuns que você deseja substituir. Consulte a tabela a seguir para obter a lista completa de destinos que você pode substituir com segurança.

  3. Defina o destino ou os destinos no final do arquivo de projeto, imediatamente antes da </Project> marca e após a importação explícita do SDK. Por exemplo:

    <Project>
        <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
        ...
        <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
        <Target Name="BeforeBuild">
            <!-- Insert tasks to run before build here -->
        </Target>
        <Target Name="AfterBuild">
            <!-- Insert tasks to run after build here -->
        </Target>
    </Project>
    

    Observe que o Sdk atributo no elemento de nível Project superior foi removido.

  4. Compile o arquivo de projeto.

Tabela de objetivos predefinidos

A tabela a seguir mostra todos os objetivos dentro dos alvos comuns que você pode substituir.

Nome do destino Descrição
BeforeCompile, AfterCompile As tarefas inseridas em um desses destinos são executadas antes ou depois que a compilação principal é feita. A maioria das personalizações é feita em um desses dois destinos.
BeforeBuild, AfterBuild As tarefas inseridas em um desses alvos serão executadas antes ou depois de todo o restante no build. Nota: Os BeforeBuild e AfterBuild alvos já estão definidos em comentários no final da maioria dos arquivos de projeto, permitindo que você adicione facilmente eventos antes e depois da compilação ao arquivo de projeto.
BeforeRebuild, AfterRebuild As tarefas inseridas em um desses destinos são executadas antes ou depois que a funcionalidade de recompilação principal é invocada. A ordem de execução de destino em Microsoft.Common.targets é: BeforeRebuild, , Cleane Builddepois AfterRebuild.
BeforeClean, AfterClean As tarefas inseridas em um desses destinos são executadas antes ou depois de a funcionalidade principal de limpeza ser invocada.
BeforePublish, AfterPublish As tarefas inseridas em um desses destinos são executadas antes ou depois que a funcionalidade de publicação principal é invocada.
BeforeResolveReferences, AfterResolveReferences As tarefas inseridas em um desses destinos são executadas antes ou depois que as referências de montagem são resolvidas.
BeforeResGen, AfterResGen As tarefas inseridas em um desses destinos são executadas antes ou depois que os recursos são gerados.

Há muitos outros destinos no sistema de build e no SDK do .NET, consulte os destinos do MSBuild – SDK e destinos de build padrão.

Práticas recomendadas para destinos personalizados

As propriedades DependsOnTargets e BeforeTargets ambas podem especificar que um destino deve ser executado antes de outro destino, mas ambas são necessárias em cenários diferentes. Elas diferem em relação a onde o requisito de dependência do destino é especificado. Você só tem controle sobre seus próprios destinos e não pode modificar com segurança os destinos do sistema ou outro destino importado, de modo que restricione sua escolha de métodos.

Ao criar um destino personalizado, siga estas diretrizes gerais para garantir que seu destino seja executado na ordem pretendida.

  1. Use o DependsOnTargets atributo para especificar destinos que você precisa fazer antes que seu destino seja executado. Para uma cadeia de destinos que você controla, cada destino pode especificar o membro anterior da cadeia em DependsOnTargets.

  2. Use BeforeTargets para qualquer destino que você não controle que você deve executar antes (como BeforeTargets="PrepareForBuild" para um destino que precisa ser executado no início do build).

  3. Use AfterTargets para qualquer destino que você não controle que garanta que as saídas necessárias estejam disponíveis. Por exemplo, especifique AfterTargets="ResolveReferences" algo que modificará uma lista de referências.

  4. Você pode usá-los em combinação. Por exemplo, DependsOnTargets="GenerateAssemblyInfo" BeforeTargets="BeforeCompile".

Importações explícitas e implícitas

Os projetos gerados pelo Visual Studio geralmente usam o Sdk atributo no elemento do projeto. Esses tipos de projetos são chamados de projetos no estilo SDK. Consulte Usar SDKs de projeto do MSBuild. Veja um exemplo:

<Project Sdk="Microsoft.Net.Sdk">

Quando o projeto usa o Sdk atributo, duas importações são adicionadas implicitamente, uma no início do arquivo de projeto e outra no final.

As importações implícitas são equivalentes a ter uma instrução de importação como esta como a primeira linha no arquivo de projeto, após o Project elemento:

<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />

e insira a seguinte instrução de importação como a última linha no arquivo do projeto:

<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />

Essa sintaxe é conhecida como importações explícitas do SDK. Ao usar essa sintaxe explícita, você deve omitir o Sdk atributo no elemento do projeto.

A importação implícita do SDK é equivalente à importação de arquivos "comuns" .props ou .targets específicos que é um constructo típico em arquivos de projeto mais antigos, como:

<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />

e

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

Essas referências antigas devem ser substituídas pela sintaxe explícita do SDK mostrada anteriormente nesta seção.

Usar a sintaxe explícita do SDK significa que você pode adicionar seu próprio código antes da primeira importação ou após a importação final do SDK. Isso significa que você pode alterar o comportamento definindo as propriedades antes da primeira importação que entrará em vigor no arquivo importado .props e pode substituir um destino definido em um dos arquivos do SDK .targets após a importação final. Usando esse método, você pode substituir BeforeBuild ou AfterBuild conforme discutido a seguir.

Próximas etapas

Há muito mais que você pode fazer com o MSBuild para personalizar o build. Confira Personalizar seu build.