Personalización de la compilación por carpeta
Puede agregar determinados archivos que MSBuild va a importar para invalidar la configuración de propiedades predeterminada y agregar destinos personalizados. El ámbito de estas personalizaciones se puede controlar en el nivel de carpeta en el que se colocan estos archivos.
En este artículo se tratan las personalizaciones aplicables a los escenarios siguientes:
- Personalización de la configuración de compilación para muchos proyectos en una solución
- Personalización de la configuración de compilación para muchas soluciones en un directorio de archivos común
- Personalizar la configuración de compilación que puede ser diferente para las subcarpetas de una estructura compleja de carpetas
- Invalide la configuración predeterminada, las carpetas de compilación predeterminadas y otros comportamientos establecidos por un SDK, como
Microsoft.Net.Sdk
- Adición o personalización de destinos de compilación aplicables a un número cualquiera de proyectos o soluciones
Si trabaja con proyectos de C++, también puede usar los métodos descritos en Personalizar compilaciones de C++.
Directory.Build.props y Directory.Build.targets
Puede agregar una nueva propiedad a cada proyecto definiendola en un único archivo denominado Directory.Build.props en la carpeta raíz que contiene el origen.
Cuando se ejecuta MSBuild, Microsoft.Common.props busca en tu estructura de directorios el archivo Directory.Build.props. Si encuentra uno, importa el archivo y lee las propiedades definidas en él. Directory.Build.props es un archivo definido por el usuario que proporciona personalizaciones a los proyectos de un directorio.
Del mismo modo, Microsoft.Common.targets busca Directory.Build.targets.
Directory.Build.props se importa al principio en la secuencia de archivos importados, lo que puede ser importante si necesita establecer una propiedad que se usa en las importaciones, especialmente aquellas que se importan implícitamente mediante el atributo Sdk
, como cuando se usa el SDK de .NET en la mayoría de los archivos de proyecto de .NET.
Nota
Los sistemas de archivos basados en Linux distinguen mayúsculas de minúsculas. Asegúrese de que las mayúsculas y minúsculas del nombre del archivo Directory.Build.props coincidan de manera exacta o no se detectará durante el proceso de compilación.
Para más información, consulte este problema de GitHub.
Ejemplo de Directory.Build.props
Por ejemplo, este es un archivo Directory.Build.props que establece el directorio de salida para todos los proyectos de una solución de Visual Studio. La salida de cada proyecto se coloca bajo su propio nombre de proyecto. En este ejemplo, el archivo Directory.Build.props está en una carpeta de solución, con muchos proyectos en subcarpetas en ella. La propiedad $(MSBuildProjectName)
da el nombre de cada proyecto. Dado que el archivo Directory.Build.props se importa en cada proyecto durante su propia construcción, se evalúa como el valor adecuado para cada proyecto individual de la solución.
Limpie la solución para quitar los archivos de salida antiguos.
msbuild /t:Clean SolutionName.sln
Cree un nuevo archivo en la raíz del repositorio denominado Directory.Build.props.
Agregue el siguiente CÓDIGO XML al archivo.
<Project> <PropertyGroup> <OutDir>C:\output\$(MSBuildProjectName)</OutDir> </PropertyGroup> </Project>
Nota
La propiedad
$(OutDir)
es una ruta de acceso absoluta a la salida y su uso omite la creación de subcarpetas para la configuración, la plataforma de destino o el tiempo de ejecución que se usan normalmente en proyectos de .NET. Intente usar la propiedadBaseOutputPath
en su lugar si desea que se creen las subcarpetas habituales en una ruta de salida personalizada.Ejecute MSBuild. Las importaciones existentes del proyecto Microsoft.Common.props y Microsoft.Common.targets encuentran el archivo Directory.Build.props y lo importan, y se utiliza la nueva carpeta de salida para todos los proyectos de esa carpeta.
Ámbito de búsqueda
Al buscar un archivo de Directory.Build.props, MSBuild recorre la estructura de directorios hacia arriba desde la ubicación del proyecto $(MSBuildProjectFullPath)
, deteniéndose después de localizar un archivo Directory.Build.props. Por ejemplo, si el $(MSBuildProjectFullPath)
era c:\users\username\code\test\case1, MSBuild empezaría a buscar allí y, a continuación, buscaría la estructura del directorio hacia arriba hasta que se encuentra un archivo Directory.Build.props, como en la siguiente estructura de directorios.
c:\users\username\code\test\case1
c:\users\username\code\test
c:\users\username\code
c:\users\username
c:\users
c:\
La ubicación del archivo de solución es irrelevante para Directory.Build.props.
Pedido de importación
Directory.Build.props se importa de forma temprana en Microsoft.Common.props y las propiedades definidas posteriormente no están disponibles en el. Por lo tanto, evite hacer referencia a las propiedades que aún no están definidas (y se evaluarán como vacías).
Las propiedades que se establecen en Directory.Build.props se pueden invalidar en otro lugar del archivo del proyecto o en archivos importados, por lo que debe pensar en la configuración de Directory.Build.props como especificar los valores predeterminados para los proyectos.
Directory.Build.targets se importa desde Microsoft.Common.targets después de importar archivos .targets
de paquetes NuGet. Por lo tanto, puede sobrescribir las propiedades y los objetivos definidos en la mayoría de la lógica de compilación o establecer propiedades para todos los proyectos, independientemente de lo que establezcan los proyectos individuales.
Cuando necesite establecer una propiedad o definir un destino para un proyecto individual que invalide cualquier configuración anterior, coloque esa lógica en el archivo del proyecto después de la importación final. Para hacerlo en un proyecto de estilo SDK, primero debe reemplazar el atributo de estilo SDK por las importaciones equivalentes. Consulte Cómo usar SDKs de proyectos de MSBuild.
Nota
El motor de MSBuild lee todos los archivos importados durante la evaluación, antes de iniciar la ejecución de la compilación para un proyecto (incluido cualquier PreBuildEvent
), por lo que no se espera que los PreBuildEvent
o cualquier otra parte del proceso de compilación modifiquen estos archivos. Las modificaciones no surten efecto hasta la siguiente invocación de MSBuild.exe o la siguiente compilación de Visual Studio. Además, si el proceso de compilación contuviera muchas compilaciones de proyecto (como con proyectos dependientes de compatibilidad con múltiples versiones (multi-targeting) o de compilación), los archivos importados, incluyendo Directory.build.props, se leerán cuando se produzca la evaluación para cada compilación de proyecto individual.
Caso de uso: combinación de varios niveles
Supongamos que tiene esta estructura de solución estándar:
\
MySolution.sln
Directory.Build.props (1)
\src
Directory.Build.props (2-src)
\Project1
\Project2
\test
Directory.Build.props (2-test)
\Project1Tests
\Project2Tests
Puede que convenga tener propiedades comunes para todos los proyectos (1), propiedades comunes para los proyectos src(2-src) y propiedades comunes para los proyectos test(2-test).
Para que MSBuild combine correctamente los archivos "internos" (2-src y 2-test) con el archivo "externo" (1), debe tener en cuenta que una vez que MSBuild encuentra un archivo Directory.Build.props, detiene el examen adicional. Para continuar escaneando y combinar en el archivo externo, coloque este código en ambos archivos internos.
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
Un resumen del enfoque general de MSBuild es el siguiente:
- Para cualquier proyecto determinado, MSBuild busca el primer Directory.Build.props en la parte superior de la estructura de la solución, lo combina con valores predeterminados y deja de buscar más.
- Si desea que se busquen y combinen varios niveles,
<Import...>
(que se muestra anteriormente) el archivo "externo" desde el archivo "interno". - Si el archivo "externo" no importase también algo por encima de el, el análisis se detendrá en ese punto.
O más sencillo: el primer Directory.Build.props que no importa nada es donde se detiene MSBuild.
Para controlar el proceso de importación de forma más explícita, use las propiedades $(DirectoryBuildPropsPath)
, $(ImportDirectoryBuildProps)
, $(DirectoryBuildTargetsPath)
y $(ImportDirectoryBuildTargets)
. La propiedad $(DirectoryBuildPropsPath)
especifica la ruta de acceso al archivo Directory.Build.props
que se va a usar; de forma similar, $(DirectoryBuildTargetsPath)
especifica la ruta de acceso al archivo Directory.Build.targets
.
Las propiedades booleanas $(ImportDirectoryBuildProps)
y $(ImportDirectoryBuildTargets)
se establecen en true
de forma predeterminada, por lo que MSBuild normalmente busca estos archivos, pero puede establecerlos en false
para evitar que MSBuild los importe.
Ejemplo
En este ejemplo se muestra el uso de la salida preprocesada para determinar dónde establecer una propiedad.
Para ayudarle a analizar el uso de una propiedad determinada que desea establecer, puede ejecutar MSBuild con el argumento /preprocess
o /pp
. El texto de salida es el resultado de todas las importaciones, incluidas las importaciones del sistema como Microsoft.Common.props que se importan implícitamente y cualquiera de sus propias importaciones. Con esta salida, puede ver dónde debe establecerse la propiedad respecto al lugar en el que se usa su valor.
Por ejemplo, supongamos que tiene un proyecto simple de aplicación de consola de .NET Core o .NET 5 o posterior y desea personalizar la carpeta de salida intermedia, normalmente obj
. La propiedad que especifica esta ruta de acceso es BaseIntermediateOutput
. Si intenta colocar esto en un elemento de PropertyGroup
en el archivo de proyecto junto con las otras propiedades que ya están establecidas allí, como TargetFramework
, detectaría al compilar el proyecto que la propiedad no surte efecto. Si ejecutas MSBuild con la opción /pp
y buscas la salida de BaseIntermediateOutputPath
, puedes ver por qué. En este caso, BaseIntermediateOutput
se lee y se usa en Microsoft.Common.props
.
Hay un comentario en microsoft.Common.props que dice que la propiedad BaseIntermediateOutput
debe establecerse aquí, antes de que otra propiedad la use, MSBuildProjectExtensionsPath
. También puede ver que, cuando se establece inicialmente BaseIntermediateOutputPath
, hay una comprobación de un valor preexistente y, si no está definido, se establece en obj
.
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">obj\</BaseIntermediateOutputPath>
Por lo tanto, esta ubicación indica que para establecer esta propiedad se debe especificar en algún lugar antes de esto. Justo antes de este código en la salida preprocesada, se puede ver que Directory.Build.props
se importa, por lo que se puede establecer BaseIntermediateOutputPath
allí y se establecerá lo suficientemente pronto como para tener el efecto deseado.
La siguiente salida abreviada preprocesada muestra el resultado de colocar la configuración de BaseIntermediateOutput
en Directory.Build.props
. Los comentarios de la parte superior de las importaciones estándar incluyen el nombre de archivo y normalmente alguna información útil sobre por qué se importa ese archivo.
<?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>
...