Estender o processo de build do Visual Studio
O processo de build do Visual Studio é definido por uma série de arquivos .targets
do MSBuild que são importados para o arquivo de projeto. Essas importações são implícitas ao usar um SDK, como é comum nos projetos do Visual Studio. Um desses arquivos importados, Microsoft.Common.targets, pode ser estendido para permitir a execução de tarefas personalizadas em vários pontos no processo de build. Este artigo explica os três métodos que você pode usar para estender o processo de build do Visual Studio:
Criar um destino personalizado e especifique quando ele deve ser executado usando atributos
BeforeTargets
eAfterTargets
.Substituir as propriedades
DependsOn
definidas nos destinos comuns.Substituir destinos predefinidos específicos definidos nos destinos comuns (Microsoft.Common.targets ou nos arquivos importados por ele).
AfterTargets e BeforeTargets
Você pode usar atributos AfterTargets
e BeforeTargets
no destino personalizado para especificar quando ele deve ser executado.
O exemplo a seguir mostra como usar o atributo AfterTargets
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 destino CustomClean
usando um atributo BeforeTargets
e especificando que a operação de limpeza personalizada é executada antes do destino CoreClean
.
<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
Use nomes diferentes dos destinos predefinidos (por exemplo, o destino de build 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. Confira 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 propriedades DependsOn
(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 discutida na próxima seção. A substituição de destinos predefinidos é um método antigo que ainda tem suporte. No entanto, como o MSBuild avalia a definição de destinos sequencialmente, não há nenhuma maneira de impedir que outro projeto que importa seu projeto substitua os destinos já substituídos por você. Dessa forma, por exemplo, o último destino AfterBuild
no arquivo de projeto, depois que todos os outros projetos foram importados, será aquele usado durante o build.
Você pode se proteger contra substituições indesejadas de destinos substituindo as propriedades DependsOn
usadas em atributos DependsOnTargets
em todo o arquivo de destinos comuns. Por exemplo, o destino Build
contém um valor de atributo DependsOnTargets
de "$(BuildDependsOn)"
. Considere:
<Target Name="Build" DependsOnTargets="$(BuildDependsOn)"/>
Este trecho de XML indica que, antes de poder executar o destino Build
, todos os destinos especificados na propriedade BuildDependsOn
devem ser executados primeiro. A propriedade BuildDependsOn
está definida como:
<PropertyGroup>
<BuildDependsOn>
$(BuildDependsOn);
BeforeBuild;
CoreBuild;
AfterBuild
</BuildDependsOn>
</PropertyGroup>
Você pode substituir esse valor da propriedade declarando outra propriedade denominada BuildDependsOn
no final do seu arquivo de projeto. Em um projeto no estilo SDK, isso significa que você precisa usar importações explícitas. Confira Importações implícitas e explícitas, para colocar a propriedade DependsOn
após a última importação. Incluindo a propriedade BuildDependsOn
anterior na nova propriedade, você pode adicionar novos destinos no início e 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 essas propriedades sem substituir as personalizações que você fez.
Para substituir uma propriedade DependsOn
Identifique uma propriedade
DependsOn
predefinida no arquivo de destinos comuns que você deseja substituir. Confira a tabela a seguir para obter uma lista das propriedadesDependsOn
geralmente substituídas.Defina outra instância da propriedade ou propriedades no final do seu arquivo de projeto. Inclua a propriedade original, por exemplo
$(BuildDependsOn)
, na nova propriedade.Defina seus destinos personalizados antes ou após a definição da propriedade.
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 esta propriedade se você quiser inserir destinos personalizados antes ou após o processo inteiro de build. |
RebuildDependsOn |
O Rebuild |
RunDependsOn |
A execução da saída de build final (se for um .EXE) |
CompileDependsOn |
A compilação (destino Compile ). Substitua esta propriedade se você quiser inserir processos personalizados antes ou após a etapa de compilação. |
CreateSatelliteAssembliesDependsOn |
A criação das assemblies satélites |
CleanDependsOn |
O destino Clean (Exclusão de todas as saídas de compilação intermediárias e finais). Substitua esta propriedade se você quiser limpar a saída do processo de build personalizado. |
PostBuildEventDependsOn |
O destino PostBuildEvent |
PublishBuildDependsOn |
Publicação de build |
ResolveAssemblyReferencesDependsOn |
O destino ResolveAssemblyReferences (encontrar o fechamento transitivo de dependências para uma dependência). Consulte ResolveAssemblyReference . |
Exemplo: BuildDependsOn e CleanDependsOn
O exemplo a seguir é semelhante ao exemplo BeforeTargets
e AfterTargets
, mas mostra como obter uma funcionalidade semelhante. Ele estende o build usando BuildDependsOn
para adicionar sua tarefa CustomAfterBuild
, que copia os arquivos de saída após o build, e também adiciona a tarefa CustomClean
correspondente usando CleanDependsOn
.
Neste exemplo, este é um projeto no estilo SDK. Conforme mencionado na nota sobre projetos no estilo SDK anteriormente neste artigo, você precisa usar o método de importação manual em vez do atributo Sdk
que o 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->'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>
<Copy SourceFiles="@(_FilesToCopy)"
DestinationFiles="@(_FilesToCopy->'$(_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
precisam aparecer depois de importar o arquivo de destinos padrão do SDK.
Substituir destinos predefinidos
Os arquivos de .targets
comuns 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 destino BeforeBuild
antes do destino principal CoreBuild
e o destino AfterBuild
após o destino CoreBuild
. Por padrão, os destinos vazios no arquivo de destinos comuns não têm nenhum efeito, mas você pode substituir o comportamento padrão deles definindo os destinos desejados em um arquivo de projeto. Os métodos descritos anteriormente neste artigo são preferenciais, mas códigos mais antigos podem usar esse método.
Quando o projeto usa um SDK (por exemplo Microsoft.Net.Sdk
), é necessário alterar as importações implícitas para explícitas, conforme discutido em Importações implícitas e explícitas.
Para substituir um destino predefinido
Quando o projeto usa o atributo
Sdk
, altere-o para a sintaxe de importação explícita. Confira Importações implícitas e explícitas.Identifique um destino predefinido no arquivo de destinos comuns que você deseja substituir. Confira a tabela abaixo para obter uma lista completa de destinos que podem ser substituídos com segurança.
Defina o destino ou destinos no final do arquivo de projeto, imediatamente antes da marca
</Project>
e após a importação de SDK explicita. 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 atributo
Sdk
no elemento de nível superiorProject
foi removido.Compile o arquivo de projeto.
Tabela de destinos predefinidos
A tabela a seguir mostra todos os destinos no arquivo de destinos comuns que você pode substituir.
Nome de destino | Descrição |
---|---|
BeforeCompile , AfterCompile |
As tarefas inseridas em um desses destinos são executadas antes ou após a conclusão da compilação principal. A maioria das personalizações é realizada em um desses dois destinos. |
BeforeBuild , AfterBuild |
As tarefas inseridas em um desses destinos serão executadas antes ou depois de todo o resto no build. Observação: os destinos BeforeBuild e AfterBuild já estão definidos nos comentários ao final da maioria dos arquivos de projeto, permitindo que você adicione com facilidade eventos de pré e pós-build ao arquivo de projeto. |
BeforeRebuild , AfterRebuild |
As tarefas inseridas em um desses destinos são executadas antes ou após a invocação da funcionalidade de recompilação principal. A ordem de execução de destino em Microsoft.Common.targets é: BeforeRebuild , Clean , Build e, em seguida, AfterRebuild . |
BeforeClean , AfterClean |
As tarefas inseridas em um desses destinos são executadas antes ou após a invocação da funcionalidade de limpeza principal. |
BeforePublish , AfterPublish |
As tarefas inseridas em um desses destinos são executadas antes ou após a invocação da funcionalidade de publicação principal. |
BeforeResolveReferences , AfterResolveReferences |
As tarefas inseridas em um desses destinos são executadas antes ou após a resolução das referências de assembly. |
BeforeResGen , AfterResGen |
As tarefas inseridas em um desses destinos são executadas antes ou após a geração de recursos. |
Há muito mais destinos no sistema de build e no SDK do .NET, confira Destinos do MSBuild - SDK e destinos de build padrão.
As melhores práticas para destinos personalizados
As propriedades DependsOnTargets
e BeforeTargets
podem especificar se um destino deve ser executado antes de outro destino, mas ambas são necessárias em cenários diferentes. Elas diferem em relação ao destino em que o requisito de dependência é especificado. Você só tem controle sobre os próprios destinos e não pode modificar com segurança os destinos do sistema ou outros destinos importados, de modo que isso restringe a escolha dos métodos.
Ao criar um destino personalizado, siga estas diretrizes gerais para garantir que o destino seja executado na ordem desejada.
Use o atributo
DependsOnTargets
para especificar os destinos que devem ser concluídos antes da execução do destino. Para cadeias de destinos que você controla, cada destino pode especificar o membro anterior da cadeia emDependsOnTargets
.Use
BeforeTargets
para qualquer destino que você não controla e deve executar antes (comoBeforeTargets="PrepareForBuild"
para um destino que precisa ser executado no início do processo de build).Use
AfterTargets
para qualquer destino que você não controla e que garante que as saídas que você precisa estão disponíveis. Por exemplo, especifiqueAfterTargets="ResolveReferences"
para algo que modificará uma lista de referências.Você pode usar qualquer combinação. Por exemplo,
DependsOnTargets="GenerateAssemblyInfo" BeforeTargets="BeforeCompile"
.
Importações implícitas e explícitas
Os projetos gerados pelo Visual Studio costumam usar o atributo Sdk
no elemento do projeto. Esses tipos de projetos são chamados de projetos no estilo SDK. Confira Como usar SDKs de projeto do MSBuild. Veja um exemplo:
<Project Sdk="Microsoft.Net.Sdk">
Quando o projeto usa o atributo Sdk
, duas importações são adicionadas implicitamente, uma no início do arquivo de projeto e outra no final.
As importações implícitas equivalem a ter uma instrução de importação como esta como a primeira linha no arquivo de projeto, após o elemento Project
:
<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 é chamada de importações de SDK explícitas. Ao usar essa sintaxe explícita, você deve omitir o atributo Sdk
no elemento do projeto.
A importação de SDK implícita é equivalente à importação de arquivos “comuns” .props
ou .targets
específicos, uma construção típica em arquivos de projeto mais antigos, como por exemplo:
<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 de SDK explícita mostrada anteriormente nesta seção.
O uso da sintaxe de SDK explícita significa que você pode adicionar seu próprio código antes da primeira importação ou após a importação de SDK final. Isso significa que é possível alterar o comportamento ao definir as propriedades antes da primeira importação que entrará em vigor no arquivo importado .props
e que você pode substituir um destino definido em um dos arquivos do SDK .targets
após a importação final. Você pode substituir BeforeBuild
ou AfterBuild
usando esse método, conforme discutido a seguir.
Próximas etapas
Você pode fazer muito mais com o MSBuild para personalizar o build. Confira Personalizar seu build.