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 por onde esses arquivos são colocados.
Este artigo aborda personalizações aplicáveis aos seguintes cenários:
- Personalizar configurações de compilação para muitos projetos em uma solução
- Personalize as configurações de compilação para muitas soluções em um diretório de arquivos comum
- Personalizar configurações de compilação que podem ser diferentes para subpastas em uma estrutura complexa de pastas
- Substituir as configurações predefinidas, pastas de construção predefinidas e outros comportamentos definidos por um SDK, como
Microsoft.Net.Sdk
- Adicionar ou personalizar alvos de compilação 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 compilações 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 seu código-fonte.
Quando o MSBuild é executado, Microsoft.Common.props procura na estrutura de diretórios o ficheiro Directory.Build.props. Se encontrar um, ele importa o arquivo e lê as propriedades definidas nele. 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 que é 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.
Observação
Os sistemas de arquivos baseados em Linux diferenciam maiúsculas de minúsculas. Verifique se o invólucro do Directory.Build.props nome do arquivo corresponde exatamente ou ele não será detetado durante o processo de compilação.
Para obter mais informações, consulte este problema do GitHub.
Exemplo de Directory.Build.props
Por exemplo, aqui está um arquivo de 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 seu próprio nome de 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)
dá o nome de cada projeto. Como o arquivo Directory.Build.props é importado para cada projeto durante sua própria compilação, ele é avaliado com o valor certo para cada projeto individual na solução.
Limpe a solução para remover todos os arquivos de saída antigos.
msbuild /t:Clean SolutionName.sln
Crie um novo arquivo na raiz do repositório chamado Directory.Build.props.
Adicione o seguinte XML ao arquivo.
<Project> <PropertyGroup> <OutDir>C:\output\$(MSBuildProjectName)</OutDir> </PropertyGroup> </Project>
Observação
A propriedade
$(OutDir)
é um caminho absoluto para a saída, e a sua utilização ignora a criação de subpastas para a configuração, plataforma de destino ou ambiente de execução que normalmente são usadas em projetos .NET. Tente usar a propriedadeBaseOutputPath
em vez disso se quiser que as subpastas usuais sejam criadas em um caminho de saída personalizado.Execute MSBuild. As importações existentes do seu projeto de Microsoft.Common.props e Microsoft.Common.targets encontram o ficheiro Directory.Build.props e importam-no, e a nova pasta de saída é usada para todos os projetos dentro dessa pasta.
Âmbito da pesquisa
Ao procurar um arquivo Directory.Build.props, o MSBuild percorre a estrutura de diretórios subindo a partir do local do seu projeto $(MSBuildProjectFullPath)
, parando ao encontrar um arquivo Directory.Build.props. Por exemplo, se o seu $(MSBuildProjectFullPath)
fosse c:\users\username\code\test\case1 , o MSBuild começaria a pesquisar lá e, em seguida, pesquisaria a estrutura de diretórios para cima até localizar um arquivo Directory.Build.props, como na seguinte estrutura de diretórios.
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 antecipadamente em Microsoft.Common.props, e as propriedades definidas posteriormente não estão disponíveis para ele. Portanto, evite referir-se a propriedades que ainda não estão 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 de Microsoft.Common.targets após importar ficheiros .targets
de pacotes NuGet. Assim, ele pode substituir propriedades e alvos definidos na maior parte da lógica de construção, ou definir propriedades para todos os seus projetos, independentemente do que os projetos individuais definam.
Quando você precisar definir uma propriedade ou definir um destino para um projeto individual que substitua quaisquer 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 SDK style pelas importações equivalentes. Consulte Como usar SDKs de projeto MSBuild.
Observação
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. Quaisquer modificações não terão efeito até a próxima invocação de MSBuild.exe ou a próxima compilação do Visual Studio. Além disso, se o seu processo de compilação contiver muitas compilações de projeto (como no caso de múltiplos alvos ou de projetos dependentes), os arquivos importados, incluindo Directory.build.props, serão lidos quando a avaliação ocorrer para cada compilação individual do projeto.
Caso de uso: fusão multinível
Suponha que você tenha esta 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 desejável ter propriedades comuns para todos os projetos (1), propriedades comuns para projetos src (2-src)e propriedades comuns para testar projetos (2-teste).
Para fazer com que o MSBuild mescle corretamente os ficheiros "internos" (2-src e 2-test) com o ficheiro "externo" (1), deves ter em conta que, assim que o MSBuild encontra um ficheiro Directory.Build.props, ele para de fazer mais verificações. Para continuar a digitalização e mesclar no arquivo externo, coloque este 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, o MSBuild encontra o primeiro Directory.Build.props subindo na estrutura da solução, mescla-o com os padrões e deixa de procurar mais.
- Se você quiser que vários níveis sejam encontrados e mesclados, então
<Import...>
(mostrado anteriormente) o arquivo "externo" do arquivo "interno". - Se o arquivo "externo" em si também não importar algo acima dele, então o processo de varredura termina nesse ponto.
Ou mais simplesmente: 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 booleanas $(ImportDirectoryBuildProps)
e $(ImportDirectoryBuildTargets)
são definidas como true
por padrão, portanto, o MSBuild normalmente procura esses arquivos, mas você pode defini-los como 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 implicitamente importadas, e qualquer uma das suas próprias importações. Com essa saída, você pode ver onde sua propriedade precisa ser definida em relação a onde seu valor é usado.
Como exemplo, suponha que você tenha um projeto simples de Aplicativo de Console .NET Core ou .NET 5 ou posterior 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
, você descobrirá quando criar o projeto que a propriedade não entra em vigor. Se executar o MSBuild com a opção /pp
e pesquisar a saída para BaseIntermediateOutputPath
, poderá ver por quê. Neste 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 estiver indefinido, ele será definido como obj
.
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">obj\</BaseIntermediateOutputPath>
Portanto, essa colocação informa que, para definir esta propriedade, ela deve ser especificada em algum ponto anterior. Pouco antes deste código na saída pré-processada, você pode ver que Directory.Build.props
é importado, então você pode 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>
...