Compartilhar via


Personalizar a compilação por pasta

Você pode adicionar determinados arquivos a serem importados pelo MSBuild para substituir as configurações de propriedade padrão e adicionar destinos personalizados. O escopo dessas personalizações pode ser controlado no nível da pasta pelo local em que esses arquivos são colocados.

Este artigo aborda as personalizações aplicáveis aos seguintes cenários:

  • Personalizar configurações de build para muitos projetos em uma solução
  • Personalizar as configurações de build para muitas soluções em um diretório de arquivos comum
  • Personalizar configurações de build que podem ser diferentes para subpastas em uma estrutura complexa de pastas
  • Substituir configurações padrão, pastas de build padrão e outros comportamentos definidos por um SDK, como Microsoft.Net.Sdk
  • Adicionar ou personalizar destinos de build que se aplicam a qualquer número de projetos ou soluções

Se você estiver trabalhando com projetos C++, também poderá usar os métodos descritos em Personalizar builds do C++.

Directory.Build.props e Directory.Build.targets

Você pode adicionar uma nova propriedade a cada projeto definindo-a em um único arquivo chamado Directory.Build.props na pasta raiz que contém sua origem.

Quando o MSBuild é executado, Microsoft.Common.props pesquisa sua estrutura de diretório para o arquivo Directory.Build.props. Se encontrar um, ele importará o arquivo e lerá as propriedades definidas dentro dele. Directory.Build.props é um arquivo definido pelo usuário que fornece personalizações para projetos em um diretório.

Da mesma forma, Microsoft.Common.targets procura Directory.Build.targets.

Directory.Build.props é importado no início da sequência de arquivos importados, o que pode ser importante se você precisar definir uma propriedade usada por importações, especialmente aquelas que são importadas implicitamente usando o atributo Sdk, como ao usar o SDK do .NET na maioria dos arquivos de projeto do .NET.

Nota

Sistemas de arquivos baseados em Linux diferenciam maiúsculas de minúsculas. As maiúsculas e minúsculas do nome do arquivo Directory.Build.props devem manter a exata correspondência ou ele não será detectado durante o processo de build.

Para obter mais informações, confira este problema do GitHub.

Exemplo de Directory.Build.props

Por exemplo, aqui está um arquivo Directory.Build.props que define o diretório de saída para todos os projetos em uma solução do Visual Studio. A saída de cada projeto é colocada sob o respectivo nome do projeto. Neste exemplo, o arquivo Directory.Build.props está em uma pasta de solução, com muitos projetos em subpastas sob ele. A propriedade $(MSBuildProjectName) fornece o nome de cada projeto. Como o arquivo Directory.Build.props é importado para cada projeto durante seu próprio build, ele é avaliado com o valor certo para cada projeto individual na solução.

  1. Limpe a solução para remover todos os arquivos de saída antigos.

    msbuild /t:Clean SolutionName.sln

  2. Crie um novo arquivo na raiz do repositório chamado Directory.Build.props.

  3. Adicione o XML a seguir ao arquivo.

    <Project>
       <PropertyGroup>
          <OutDir>C:\output\$(MSBuildProjectName)</OutDir>
       </PropertyGroup>
    </Project>
    

    Nota

    A propriedade $(OutDir) é um caminho absoluto para a saída, e usá-la ignora a criação de subpastas para a configuração, a estrutura de destino ou o runtime que normalmente são usados em projetos .NET. Tente usar a propriedade BaseOutputPath se quiser que as subpastas usuais sejam criadas em um caminho de saída personalizado.

  4. Execute MSBuild. As importações existentes do projeto de Microsoft.Common.props e Microsoft.Common.targets localizam o arquivo Directory.Build.props e o importam, e a nova pasta de saída é usada para todos os projetos nessa nova pasta.

Escopo da pesquisa

Ao procurar um arquivo Directory.Build.props, o MSBuild percorre a estrutura do diretório para cima a partir do local do seu projeto $(MSBuildProjectFullPath), parando depois de localizar um arquivo Directory.Build.props. Por exemplo, se seu $(MSBuildProjectFullPath) for c:\usuários\nomedeusuário\código\teste\caso1, o MSBuild deverá iniciar a pesquisa aí e depois pesquisar a estrutura do diretório para cima até localizar um arquivo Directory.Build.props, como na estrutura de diretório a seguir.

c:\users\username\code\test\case1
c:\users\username\code\test
c:\users\username\code
c:\users\username
c:\users
c:\

O local do arquivo de solução é irrelevante para Directory.Build.props.

Ordem de importação

Directory.Build.props é importado no início de Microsoft.Common.props e as propriedades definidas posteriormente não estão disponíveis para ele. Portanto, evite fazer referência a propriedades que ainda não foram definidas (e serão avaliadas como vazias).

As propriedades definidas em Directory.Build.props podem ser substituídas em outro lugar no arquivo de projeto ou em arquivos importados, portanto, você deve pensar nas configurações em Directory.Build.props como especificando os padrões para seus projetos.

Directory.Build.targets é importado do Microsoft.Common.targets depois de importar os arquivos .targets dos pacotes do NuGet. Portanto, ele pode sobrescrever propriedades e alvos definidos na maior parte da lógica de build ou definir propriedades para todos os seus projetos, independentemente das definições dos projetos individuais.

Quando precisar definir uma propriedade ou definir um destino para um projeto individual que substitua as configurações anteriores, coloque essa lógica no arquivo de projeto após a importação final. Para fazer isso em um projeto no estilo SDK, primeiro você precisa substituir o atributo no estilo SDK pelas importações equivalentes. Consulte Como usar os SDKs de projeto do MSBuild.

Nota

O mecanismo MSBuild lê todos os arquivos importados durante a avaliação, antes de iniciar a execução da compilação para um projeto (incluindo qualquer PreBuildEvent), portanto, não se espera que esses arquivos sejam modificados pelo PreBuildEvent ou qualquer outra parte do processo de compilação. As modificações não entrarão em vigor até a próxima invocação de MSBuild.exe ou a próxima compilação do Visual Studio. Além disso, se o processo de build contiver muitos builds de projeto (como quando há projetos multiplataforma ou que dependem do build), os arquivos importados, incluindo Directory.build.props, serão lidos quando ocorrer uma avaliação para cada build de projeto individual.

Caso de uso: mesclagem de vários níveis

Suponha que você tenha essa estrutura de solução padrão:

\
  MySolution.sln
  Directory.Build.props     (1)
  \src
    Directory.Build.props   (2-src)
    \Project1
    \Project2
  \test
    Directory.Build.props   (2-test)
    \Project1Tests
    \Project2Tests

Pode ser interessante ter propriedades comuns para todos os projetos (1), propriedades comuns para projetos src(2-src) e propriedades comuns para projetos test(2-test).

Para fazer o MSBuild mesclar corretamente os arquivos "internos" (2-src e 2-test) com o arquivo "externo" (1), você deve levar em conta que, depois que o MSBuild encontrar um arquivo Directory.Build.props, ele parará de escanear. Para continuar a verificação e mesclagem no arquivo externo, coloque esse código em ambos os arquivos internos:

<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />

Um resumo da abordagem geral do MSBuild é o seguinte:

  • Para qualquer projeto específico, o MSBuild localiza o primeiro Directory.Build.props na estrutura acima da solução, mescla-o com os padrões e para de procurar por mais.
  • Se você quiser que vários níveis sejam encontrados e mesclados, então <Import...> (mostrado anteriormente) no arquivo "externo" do arquivo "interno".
  • Se o arquivo "externo" não importar algo acima dele, a verificação será interrompida.

Ou mais simples: o primeiro Directory.Build.props que não importa nada é onde o MSBuild para.

Para controlar o processo de importação de forma mais explícita, use as propriedades $(DirectoryBuildPropsPath), $(ImportDirectoryBuildProps), $(DirectoryBuildTargetsPath)e $(ImportDirectoryBuildTargets). A propriedade $(DirectoryBuildPropsPath) especifica o caminho para o arquivo Directory.Build.props a ser usado; da mesma forma, $(DirectoryBuildTargetsPath) especifica o caminho para o arquivo Directory.Build.targets.

As propriedades boolianas $(ImportDirectoryBuildProps) e $(ImportDirectoryBuildTargets) são definidas como true por padrão, portanto, o MSBuild normalmente pesquisa esses arquivos, mas você pode defini-los para false para impedir que o MSBuild os importe.

Exemplo

Este exemplo mostra o uso da saída pré-processada para determinar onde definir uma propriedade.

Para ajudá-lo a analisar o uso de uma propriedade específica que você deseja definir, você pode executar o MSBuild com o argumento /preprocess ou /pp. O texto de saída é o resultado de todas as importações, incluindo as importações do sistema, como Microsoft.Common.props que são importadas implicitamente e qualquer uma de suas próprias importações. Com essa saída, você pode ver o local em que sua propriedade precisa ser definida em relação ao local em que o valor dela é usado.

Por exemplo, suponha que você tenha um projeto simples do .NET Core ou do .NET 5 ou posterior do Console App e queira personalizar a pasta de saída intermediária, normalmente obj. A propriedade que especifica esse caminho é BaseIntermediateOutput. Se você tentar colocar isso em um elemento PropertyGroup em seu arquivo de projeto junto com as várias outras propriedades que já estão definidas lá, como TargetFramework, descobrirá ao compilar o projeto que a propriedade não entra em vigor. Se você executar o MSBuild com a opção /pp e pesquisar BaseIntermediateOutputPath na saída, poderá ver o motivo. Nesse caso, BaseIntermediateOutput é lido e usado em Microsoft.Common.props.

Há um comentário em Microsoft.Common.props que diz que a propriedade BaseIntermediateOutput deve ser definida aqui, antes de ser usada por outra propriedade, MSBuildProjectExtensionsPath. Você também pode ver que, quando BaseIntermediateOutputPath é definido inicialmente, há uma verificação de um valor pré-existente e, se ele for indefinido, ele será definido como obj.

<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">obj\</BaseIntermediateOutputPath>

Portanto, esse posicionamento informa que, para definir essa propriedade, ela deve ser especificada para algum momento anterior ao presente. Logo antes desse código na saída pré-processada, você pode ver que Directory.Build.props é importado, para que você possa definir BaseIntermediateOutputPath lá e ele será definido cedo o suficiente para ter o efeito desejado.

A saída pré-processada abreviada a seguir mostra o resultado de colocar a configuração BaseIntermediateOutput em Directory.Build.props. Os comentários na parte superior das importações padrão incluem o nome do arquivo e, geralmente, algumas informações úteis sobre por que esse arquivo é importado.

<?xml version="1.0" encoding="IBM437"?>
<!--
============================================================================================================================================
c:\source\repos\ConsoleApp9\ConsoleApp9\ConsoleApp9.csproj
============================================================================================================================================
-->
<Project DefaultTargets="Build">
  <!--
============================================================================================================================================
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk">
  This import was added implicitly because the Project element's Sdk attribute specified "Microsoft.NET.Sdk".

C:\Program Files\dotnet\sdk\7.0.200-preview.22628.1\Sdks\Microsoft.NET.Sdk\Sdk\Sdk.props
============================================================================================================================================
-->
  <!--
***********************************************************************************************
Sdk.props

WARNING:  DO NOT MODIFY this file unless you are knowledgeable about MSBuild and have
          created a backup copy.  Incorrect changes to this file will make it
          impossible to load or build your projects from the command-line or the IDE.

Copyright (c) .NET Foundation. All rights reserved.
***********************************************************************************************
-->
  <PropertyGroup xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <!--
      Indicate to other targets that Microsoft.NET.Sdk is being used.

      This must be set here (as early as possible, before Microsoft.Common.props)
      so that everything that follows can depend on it.

      In particular, Directory.Build.props and nuget package props need to be able
      to use this flag and they are imported by Microsoft.Common.props.
    -->
    <UsingMicrosoftNETSdk>true</UsingMicrosoftNETSdk>
    <!--
      Indicate whether the set of SDK defaults that makes SDK style project concise are being used.
      For example: globbing, importing msbuild common targets.

      Similar to the property above, it must be set here.
    -->
    <UsingNETSdkDefaults>true</UsingNETSdkDefaults>
  </PropertyGroup>
  <PropertyGroup Condition="'$(MSBuildProjectFullPath)' == '$(ProjectToOverrideProjectExtensionsPath)'" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <MSBuildProjectExtensionsPath>$(ProjectExtensionsPathForSpecifiedProject)</MSBuildProjectExtensionsPath>
  </PropertyGroup>
  <!--<Import Project="$(AlternateCommonProps)" Condition="'$(AlternateCommonProps)' != ''" />-->
  <!--
============================================================================================================================================
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="'$(AlternateCommonProps)' == ''">

C:\Program Files\Microsoft Visual Studio\2022\Preview\MSBuild\Current\Microsoft.Common.props
============================================================================================================================================
-->
  <!--
***********************************************************************************************
Microsoft.Common.props

WARNING:  DO NOT MODIFY this file unless you are knowledgeable about MSBuild and have
          created a backup copy.  Incorrect changes to this file will make it
          impossible to load or build your projects from the command-line or the IDE.

Copyright (C) Microsoft Corporation. All rights reserved.
***********************************************************************************************
-->
  <PropertyGroup>
    <ImportByWildcardBeforeMicrosoftCommonProps Condition="'$(ImportByWildcardBeforeMicrosoftCommonProps)' == ''">true</ImportByWildcardBeforeMicrosoftCommonProps>
    <ImportByWildcardAfterMicrosoftCommonProps Condition="'$(ImportByWildcardAfterMicrosoftCommonProps)' == ''">true</ImportByWildcardAfterMicrosoftCommonProps>
    <ImportUserLocationsByWildcardBeforeMicrosoftCommonProps Condition="'$(ImportUserLocationsByWildcardBeforeMicrosoftCommonProps)' == ''">true</ImportUserLocationsByWildcardBeforeMicrosoftCommonProps>
    <ImportUserLocationsByWildcardAfterMicrosoftCommonProps Condition="'$(ImportUserLocationsByWildcardAfterMicrosoftCommonProps)' == ''">true</ImportUserLocationsByWildcardAfterMicrosoftCommonProps>
    <ImportDirectoryBuildProps Condition="'$(ImportDirectoryBuildProps)' == ''">true</ImportDirectoryBuildProps>
  </PropertyGroup>
  <!--
      Determine the path to the directory build props file if the user did not disable $(ImportDirectoryBuildProps) and
      they did not already specify an absolute path to use via $(DirectoryBuildPropsPath)
  -->
  <PropertyGroup Condition="'$(ImportDirectoryBuildProps)' == 'true' and '$(DirectoryBuildPropsPath)' == ''">
    <_DirectoryBuildPropsFile Condition="'$(_DirectoryBuildPropsFile)' == ''">Directory.Build.props</_DirectoryBuildPropsFile>
    <_DirectoryBuildPropsBasePath Condition="'$(_DirectoryBuildPropsBasePath)' == ''">$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), '$(_DirectoryBuildPropsFile)'))</_DirectoryBuildPropsBasePath>
    <DirectoryBuildPropsPath Condition="'$(_DirectoryBuildPropsBasePath)' != '' and '$(_DirectoryBuildPropsFile)' != ''">$([System.IO.Path]::Combine('$(_DirectoryBuildPropsBasePath)', '$(_DirectoryBuildPropsFile)'))</DirectoryBuildPropsPath>
  </PropertyGroup>
  <!--
============================================================================================================================================
  <Import Project="$(DirectoryBuildPropsPath)" Condition="'$(ImportDirectoryBuildProps)' == 'true' and exists('$(DirectoryBuildPropsPath)')">

c:\source\repos\ConsoleApp9\Directory.Build.props
============================================================================================================================================
-->
  <!-- Directory.build.props
-->
  <PropertyGroup>
    <BaseIntermediateOutputPath>myBaseIntermediateOutputPath</BaseIntermediateOutputPath>
  </PropertyGroup>
  <!--
============================================================================================================================================
  </Import>

C:\Program Files\Microsoft Visual Studio\2022\Preview\MSBuild\Current\Microsoft.Common.props
============================================================================================================================================
-->
  <!--
      Prepare to import project extensions which usually come from packages.  Package management systems will create a file at:
        $(MSBuildProjectExtensionsPath)\$(MSBuildProjectFile).<SomethingUnique>.props

      Each package management system should use a unique moniker to avoid collisions.  It is a wild-card import so the package
      management system can write out multiple files but the order of the import is alphabetic because MSBuild sorts the list.
  -->
  <PropertyGroup>
    <!--
        The declaration of $(BaseIntermediateOutputPath) had to be moved up from Microsoft.Common.CurrentVersion.targets
        in order for the $(MSBuildProjectExtensionsPath) to use it as a default.
    -->
    <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">obj\</BaseIntermediateOutputPath>
    <BaseIntermediateOutputPath Condition="!HasTrailingSlash('$(BaseIntermediateOutputPath)')">$(BaseIntermediateOutputPath)\</BaseIntermediateOutputPath>
    <_InitialBaseIntermediateOutputPath>$(BaseIntermediateOutputPath)</_InitialBaseIntermediateOutputPath>
    <MSBuildProjectExtensionsPath Condition="'$(MSBuildProjectExtensionsPath)' == '' ">$(BaseIntermediateOutputPath)</MSBuildProjectExtensionsPath>
    <!--
        Import paths that are relative default to be relative to the importing file.  However, since MSBuildExtensionsPath
        defaults to BaseIntermediateOutputPath we expect it to be relative to the project directory.  So if the path is relative
        it needs to be made absolute based on the project directory.
    -->
    <MSBuildProjectExtensionsPath Condition="'$([System.IO.Path]::IsPathRooted($(MSBuildProjectExtensionsPath)))' == 'false'">$([System.IO.Path]::Combine('$(MSBuildProjectDirectory)', '$(MSBuildProjectExtensionsPath)'))</MSBuildProjectExtensionsPath>
    <MSBuildProjectExtensionsPath Condition="!HasTrailingSlash('$(MSBuildProjectExtensionsPath)')">$(MSBuildProjectExtensionsPath)\</MSBuildProjectExtensionsPath>
    <ImportProjectExtensionProps Condition="'$(ImportProjectExtensionProps)' == ''">true</ImportProjectExtensionProps>
    <_InitialMSBuildProjectExtensionsPath Condition=" '$(ImportProjectExtensionProps)' == 'true' ">$(MSBuildProjectExtensionsPath)</_InitialMSBuildProjectExtensionsPath>
  </PropertyGroup>
  ...