para ampliar el proceso de compilación de Visual Studio

El proceso de compilación de Visual Studio se define mediante una serie de archivos .targets de MSBuild que se importan en el archivo de proyecto. Estas importaciones son implícitas, si usa un SDK como hacen normalmente los proyectos de Visual Studio. Uno de estos archivos importados, Microsoft.Common.targets, se puede extender para que pueda ejecutar tareas personalizadas en varios puntos del proceso de compilación. En este artículo se explican tres métodos que puede usar para extender el proceso de compilación de Visual Studio:

  • Cree un destino personalizado y especifique cuándo debe ejecutarse mediante atributos BeforeTargets y AfterTargets.

  • Invalide las propiedades DependsOn definidas en los destinos comunes.

  • Reemplazar destinos predefinidos específicos definidos en los destinos comunes (Microsoft.Common.targets o los archivos que importa).

BeforeTargets y AfterTargets

Puede usar atributos AfterTargets y BeforeTargets en el destino personalizado para especificar cuándo se debe ejecutar.

En el ejemplo siguiente se muestra cómo usar el atributo AfterTargets para agregar un destino personalizado que realice una acción en los archivos de salida. En este caso, copia los archivos de salida en una nueva carpeta CustomOutput. En el ejemplo también se muestra cómo limpiar los archivos creados por la operación de compilación personalizada con un destino CustomClean mediante un atributo BeforeTargets y especificando que la operación de limpieza personalizada se ejecuta antes que el destino de 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>

Advertencia

Asegúrese de usar nombres distintos de los destinos predefinidos (por ejemplo, aquí el destino de la compilación personalizada es CustomAfterBuild, no AfterBuild), ya que la importación del SDK reemplaza los destinos predefinidos y también los define. Consulte la tabla al final de este artículo para obtener una lista de destinos predefinidos.

Extender las propiedades DependsOn

Otra manera de ampliar el proceso de compilación es usar las propiedades DependsOn (por ejemplo, BuildDependsOn), para especificar destinos que se deben ejecutar antes de un destino estándar.

Este método es preferible a invalidar destinos predefinidos, que se describe en la sección siguiente. Reemplazar destinos predefinidos es una manera antigua que se sigue admitiendo, pero, dado que MSBuild evalúa la definición de destinos secuencialmente, no hay manera de evitar que otro proyecto que importe su proyecto reemplace los destinos que ya ha reemplazado. Por lo tanto, por ejemplo, el último destino AfterBuild definido en el archivo del proyecto, después de que se hayan importado todos los demás proyectos, será el que se utiliza durante la compilación.

Para protegerse de reemplazos de destinos imprevistos, puede reemplazar las propiedades DependsOn que se usan en los atributos DependsOnTargets en los destinos comunes. Por ejemplo, el valor del atributo DependsOnTargets del destino Build es "$(BuildDependsOn)". Tenga en cuenta lo siguiente:

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

Este fragmento de XML indica que, antes de que el destino Build se pueda ejecutar, primero se deben ejecutar todos los destinos especificados en la propiedad BuildDependsOn. La propiedad BuildDependsOn se define como:

<PropertyGroup>
    <BuildDependsOn>
        $(BuildDependsOn);
        BeforeBuild;
        CoreBuild;
        AfterBuild
    </BuildDependsOn>
</PropertyGroup>

Para reemplazar este valor de propiedad, puede declarar otra propiedad denominada BuildDependsOn al final del archivo del proyecto. En un proyecto de estilo SDK, esto significa que tiene que usar importaciones explícitas. Consulte Importaciones implícitas y explícitas para que pueda colocar la propiedad DependsOn después de la última importación. Al incluir la propiedad BuildDependsOn anterior en la propiedad nueva, puede agregar nuevos destinos al principio y al final de la lista de destinos. Por ejemplo:

<PropertyGroup>
    <BuildDependsOn>
        MyCustomTarget1;
        $(BuildDependsOn);
        MyCustomTarget2
    </BuildDependsOn>
</PropertyGroup>

<Target Name="MyCustomTarget1">
    <Message Text="Running MyCustomTarget1..."/>
</Target>
<Target Name="MyCustomTarget2">
    <Message Text="Running MyCustomTarget2..."/>
</Target>

Los proyectos que importan el archivo del proyecto pueden extender aún más estas propiedades sin sobrescribir las personalizaciones que haya realizado.

Para reemplazar una propiedad DependsOn

  1. Identifique una propiedad DependsOn predefinida en los destinos comunes que quiera reemplazar. Vea la tabla siguiente para obtener una lista de las propiedades DependsOn que normalmente se reemplazan.

  2. Defina otra instancia de la propiedad o de las propiedades al final del archivo del proyecto. Incluya la propiedad original, por ejemplo $(BuildDependsOn), en la propiedad nueva.

  3. Defina sus destinos personalizados antes o después de la definición de la propiedad.

  4. Compile el archivo del proyecto.

Propiedades DependsOn que normalmente se reemplazan

Nombre de propiedad Los destinos agregados se ejecutan antes de este punto:
BuildDependsOn Punto de entrada de compilación principal Reemplace esta propiedad si quiere insertar destinos personalizados antes o después del proceso de compilación completo.
RebuildDependsOn El Rebuild
RunDependsOn Ejecución de la salida de compilación final (si es un .EXE)
CompileDependsOn Compilación (destino Compile). Reemplace esta propiedad si quiere insertar procesos personalizados antes o después del paso de compilación.
CreateSatelliteAssembliesDependsOn Creación de los ensamblados satélite
CleanDependsOn Destino Clean (eliminación de todas las salidas de compilación intermedias y finales). Reemplace esta propiedad si quiere limpiar el resultado del proceso de compilación personalizado.
PostBuildEventDependsOn Destino PostBuildEvent
PublishBuildDependsOn Publicación de compilación
ResolveAssemblyReferencesDependsOn Destino ResolveAssemblyReferences (búsqueda del cierre transitivo de dependencias para una dependencia determinada). Vea ResolveAssemblyReference.

Ejemplo: BuildDependsOn y CleanDependsOn

El ejemplo siguiente es parecido al ejemplo de BeforeTargets y AfterTargets, y muestra cómo lograr una funcionalidad similar. Amplía la compilación mediante BuildDependsOn para que pueda agregar su propia tarea CustomAfterBuild, la cual copia los archivos de salida después de la compilación y agrega la tarea CustomClean correspondiente usando CleanDependsOn.

En este ejemplo, se trata de un proyecto tipo SDK. Como se ha mencionado anteriormente en este artículo en la nota sobre los proyectos tipo SDK, debe usar el método de importación manual en lugar del atributo Sdk que usa Visual Studio al generar archivos de proyecto.

<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>

El orden de los elementos es importante. Los elementos BuildDependsOn y CleanDependsOn deben aparecer después de importar el archivo de destinos SDK estándar.

Reemplazar destinos predefinidos

Los archivos comunes .targets contienen un conjunto de destinos vacíos predefinidos a los que se llama antes y después de algunos de los destinos principales del proceso de compilación. Por ejemplo, MSBuild llama al destino BeforeBuild antes del destino CoreBuild principal y al destino AfterBuild después del destino CoreBuild. De forma predeterminada, los destinos vacíos de los destinos comunes no hacen nada, pero puede reemplazar su comportamiento predeterminado si define los destinos que quiere en un archivo de proyecto. Se prefieren los métodos descritos anteriormente en este artículo, pero es posible que encuentre código anterior que use este método.

Si el proyecto usa un SDK (por ejemplo Microsoft.Net.Sdk), debe realizar un cambio de importaciones implícitas a explícitas, como se describe en Importaciones explícitas e implícitas.

Para reemplazar un destino predefinido

  1. Si el proyecto usa el atributo Sdk, cámbielo a la sintaxis de importación explícita. Consulte Importaciones explícitas e implícitas.

  2. Identifique un destino predefinido de los destinos comunes que quiera reemplazar. Consulte la tabla siguiente para ver la lista completa de destinos que puede reemplazar de forma segura.

  3. Defina el destino o los destinos al final del archivo del proyecto, inmediatamente antes de la etiqueta </Project> y después de la importación explícita de SDK. Por ejemplo:

    <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>
    

    Tenga en cuenta que se ha quitado el atributo Sdk del elemento de nivel superior Project.

  4. Compile el archivo del proyecto.

Tabla de destinos predefinidos

En la tabla siguiente se muestran todos los destinos incluidos en los destinos comunes que puede reemplazar.

Nombre de destino Descripción
BeforeCompile, AfterCompile Las tareas insertadas en uno de estos destinos se ejecutan antes o después de que se realice la compilación básica. La mayoría de las personalizaciones se realiza en uno de estos dos destinos.
BeforeBuild, AfterBuild Las tareas insertadas en uno de estos destinos se ejecutan antes o después de todo lo demás de la compilación. Nota: Los destinos BeforeBuild y AfterBuild ya están definidos en los comentarios al final de la mayoría de los archivos de proyecto, lo que permite agregar con facilidad eventos anteriores y posteriores a la compilación al archivo de proyecto.
BeforeRebuild, AfterRebuild Las tareas insertadas en uno de estos destinos se ejecutan antes o después de la invocación a la funcionalidad de recompilación básica. El orden de ejecución de destino en Microsoft.Common.targets es: BeforeRebuild, Clean, Build y luego AfterRebuild.
BeforeClean, AfterClean Las tareas insertadas en uno de estos destinos se ejecutan antes o después de la invocación a la funcionalidad de limpieza básica.
BeforePublish, AfterPublish Las tareas insertadas en uno de estos destinos se ejecutan antes o después de la invocación a la funcionalidad de publicación de limpieza básica.
BeforeResolveReferences, AfterResolveReferences Las tareas insertadas en uno de estos destinos se ejecutan antes o después de la resolución de las referencias de ensamblados.
BeforeResGen, AfterResGen Las tareas insertadas en uno de estos destinos se ejecutan antes o después de generar los recursos.

Hay muchos más destinos en el sistema de compilación y el SDK de .NET, consulte Destinos de MSBuild: SDK y destinos de compilación predeterminados.

Procedimientos recomendados para destinos personalizados

Las propiedades DependsOnTargets y BeforeTargets pueden especificar que un destino debe ejecutarse antes que otro destino, pero ambos son necesarios en escenarios diferentes. Difieren en qué destino se especifica el requisito de dependencia. Solo tiene control sobre sus propios destinos y no puede modificar de forma segura los destinos del sistema u otro destino importado, de modo que restringe su elección de métodos.

Al crear un destino personalizado, siga estas instrucciones generales para asegurarse de que el destino se ejecuta en el orden previsto.

  1. Use el atributo DependsOnTargets para especificar destinos que necesita que se realicen antes de que se ejecute el destino. Para una cadena de destinos que controle, cada destino puede especificar el miembro anterior de la cadena en DependsOnTargets.

  2. Use BeforeTargets para cualquier destino que no controle que deba ejecutar antes (por ejemplo BeforeTargets="PrepareForBuild" para un destino que necesite ejecutarse al principio de la compilación).

  3. Use AfterTargets para cualquier destino que no controle que garantice que las salidas que necesita estén disponibles. Por ejemplo, especifique AfterTargets="ResolveReferences" para algo que modificará una lista de referencias.

  4. Puede usar cualquier combinación. Por ejemplo, DependsOnTargets="GenerateAssemblyInfo" BeforeTargets="BeforeCompile".

Importaciones explícitas e implícitas

Los proyectos generados por Visual Studio suelen usar el atributo Sdk en el elemento del proyecto. Estos tipos de proyectos se denominan proyectos de estilo SDK. Consulte Usar SDK de proyecto de MSBuild. Este es un ejemplo:

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

Cuando el proyecto usa el atributo Sdk, se agregan implícitamente dos importaciones, una al principio del archivo del proyecto y otra al final.

Las importaciones implícitas son equivalentes a tener una instrucción import como esta como la primera línea del archivo de proyecto, después del elemento Project:

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

y la siguiente instrucción import como la última línea del archivo del proyecto:

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

Esta sintaxis se conoce como importaciones explícitas del SDK. Al usar esta sintaxis explícita, debe omitir el atributo Sdk en el elemento del proyecto.

La importación implícita del SDK es equivalente a importar los archivos "comunes" .props o .targets específicos que son una construcción típica en archivos de proyecto más antiguos, como:

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

y

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

Todas estas referencias antiguas deben reemplazarse por la sintaxis explícita del SDK mostrada anteriormente en esta sección.

El uso de la sintaxis explícita del SDK significa que puede agregar su propio código antes de la primera importación o después de la importación final del SDK. Esto significa que puede cambiar el comportamiento estableciendo propiedades antes de la primera importación que surtirá efecto en el archivo importado .props y puede invalidar un destino definido en uno de los archivos .targets del SDK después de la importación final. Con este método, puede invalidar BeforeBuild o AfterBuild como se describe a continuación.

Pasos siguientes

Hay mucho más que puede hacer con MSBuild para personalizar la compilación. Consulte Personalizar una compilación.