Partilhar via


Estender o processo de compilação do Visual Studio

O processo de compilação do Visual Studio é definido por uma série de arquivos MSBuild .targets que são importados para o arquivo de projeto. Essas importações estão implícitas, se você usar um SDK como os projetos do Visual Studio geralmente fazem. Um desses arquivos importados, Microsoft.Common.targets, pode ser estendido para permitir que você execute tarefas personalizadas em vários pontos do processo de compilação. Este artigo explica três métodos que você pode usar para estender o processo de compilação 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 alvos comuns.

  • Substitua destinos predefinidos específicos definidos nos destinos comuns (Microsoft.Common.targets ou os 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 compilação 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>

Advertência

Certifique-se de usar nomes diferentes dos destinos predefinidos (por exemplo, o destino de compilação personalizado aqui é CustomAfterBuild, não AfterBuild), já 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.

Expandir as propriedades DependsOn

Outra maneira de estender o processo de compilação é 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 à sobreposição de objetivos predefinidos, que são discutidos na próxima seção. Substituir destinos predefinidos é um método mais antigo que ainda é suportado, mas, como o MSBuild avalia a definição de destinos sequencialmente, não há como impedir que outro projeto que importa seu projeto substitua os destinos que você já substituiu. Assim, por exemplo, o último AfterBuild destino definido no arquivo de projeto, depois que todos os outros projetos tiverem sido importados, será aquele que for usado durante a compilação.

Pode proteger-se de substituições não intencionais de destinos ao substituir as propriedades DependsOn utilizadas nos atributos DependsOnTargets em todos os destinos comuns. Por exemplo, o Build destino contém um atributo DependsOnTargets com valor de "$(BuildDependsOn)". Considere:

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

Essa parte do 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 fim da lista de destinos. 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 obter uma lista das propriedades comumente substituídas DependsOn .

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

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

  4. Crie o arquivo de projeto.

Propriedades DependsOn comumente substituídas

Nome da propriedade As metas adicionadas são concluídas antes deste ponto.
BuildDependsOn O principal ponto de entrada da construção. Substitua essa propriedade se desejar inserir destinos personalizados antes ou depois de todo o processo de compilação.
RebuildDependsOn O Rebuild
RunDependsOn A execução da saída final da compilação (se for um .EXE)
CompileDependsOn A compilação (Compile alvo). Substitua essa propriedade se desejar inserir processos personalizados antes ou depois da etapa de compilação.
CreateSatelliteAssembliesDependsOn A criação dos conjuntos satélites
CleanDependsOn O Clean destino (Exclusão de todas as saídas de compilação intermediárias e finais). Substitua essa propriedade se quiser limpar a saída do seu processo de compilação personalizado.
PostBuildEventDependsOn O PostBuildEvent alvo
PublishBuildDependsOn Criar publicação
ResolveAssemblyReferencesDependsOn O ResolveAssemblyReferences alvo (determinar o fecho transitivo das dependências para uma dependência específica). Consulte ResolveAssemblyReference.

Exemplo: BuildDependsOn e CleanDependsOn

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

Neste exemplo, este é um projeto no estilo SDK. Conforme mencionado na observação 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 quando gera 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 após a importação do ficheiro de alvos padrão do SDK.

Substituir alvos predefinidos

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 compilação. Por exemplo, o MSBuild chama o BeforeBuild destino antes do destino principal CoreBuild e o AfterBuild destino depois do CoreBuild destino. Por padrão, os destinos vazios nos destinos comuns não fazem nada, mas você pode substituir seu comportamento padrão definindo os destinos desejados em um arquivo de projeto. Os métodos descritos anteriormente neste artigo são preferidos, mas você pode encontrar código mais antigo que usa esse método.

Se o seu projeto usa um SDK (por exemplo Microsoft.Net.Sdk), você precisa 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 alvos que poderá substituir com segurança.

  3. Defina o destino ou destinos no final do arquivo de projeto, imediatamente antes da </Project> tag 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. Crie o arquivo de projeto.

Tabela de metas predefinidas

A tabela seguinte apresenta todos os alvos nos objetivos comuns que pode substituir.

Nome do destino Descrição
BeforeCompile, AfterCompile As tarefas inseridas em um desses destinos são executadas antes ou depois da compilação principal ser concluída. A maioria das personalizações é feita em um desses dois destinos.
BeforeBuild, AfterBuild As tarefas inseridas em um destes alvos serão executadas antes ou depois de tudo o restante na compilação. Nota: Os BeforeBuild e AfterBuild alvos já estão definidos nos comentários ao final da maioria dos ficheiros de projeto, permitindo adicionar facilmente eventos de pré e pós-compilação ao seu ficheiro de projeto.
BeforeRebuild, AfterRebuild As tarefas inseridas em um desses destinos são executadas antes ou depois que a funcionalidade de reconstrução principal é invocada. A ordem de execução de destino em Microsoft.Common.targets é: BeforeRebuild, Clean, Builde, em seguida AfterRebuild, .
BeforeClean, AfterClean As tarefas inseridas em um desses destinos são executadas antes ou depois que a funcionalidade de limpeza principal é 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 assembly 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á muito mais destinos no sistema de compilação e no SDK do .NET, consulte Destinos MSBuild - SDK e destinos de compilação padrão.

Práticas recomendadas para destinos personalizados

As propriedades DependsOnTargets e BeforeTargets podem especificar que um destino deve ser executado antes de outro destino, mas ambos são necessários em cenários diferentes. Eles diferem na especificação do destino do requisito de dependência. Você só tem controle sobre seus próprios objetivos e não pode modificar com segurança os objetivos do sistema ou outros objetivos importados, o que limita a 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 atributo DependsOnTargets para especificar destinos que devem ser feitos antes que o 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 controla e que você deve executar antes (como BeforeTargets="PrepareForBuild" para um destino que precisa ser executado no início da compilação).

  3. Use AfterTargets para qualquer destino que você não controla 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. Pode utilizá-los em combinação. Por exemplo, DependsOnTargets="GenerateAssemblyInfo" BeforeTargets="BeforeCompile".

Importações explícitas e implícitas

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

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

Quando seu 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 import como esta como a primeira linha no arquivo de projeto, após o Project elemento :

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

e a seguinte instrução de importação como a última linha no arquivo de 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 a importar os arquivos "comuns" .props ou .targets específicos, que são uma construção típica em arquivos de projeto mais antigos, como:

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

e ainda

<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 propriedades antes da primeira importação que entrará em vigor no arquivo importado .props e pode substituir um destino definido em um dos arquivos SDK .targets após a importação final. Usando esse método, você pode substituir BeforeBuild ou AfterBuild conforme discutido a seguir.

Próximos passos

Há muito mais que você pode fazer com o MSBuild para personalizar a compilação. Consulte Personalizar sua compilação.