Compartir a través de


Ampliar el proceso de compilación de Visual Studio

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

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

  • Invalide las DependsOn propiedades definidas en los destinos comunes.

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

AfterTargets y BeforeTargets

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

En el ejemplo siguiente se muestra cómo usar el AfterTargets atributo para agregar un destino personalizado que haga algo con 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 mediante un CustomCleanBeforeTargets atributo y especificar que la operación de limpieza personalizada se ejecuta antes del CoreClean destino.

<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 diferentes a los destinos predefinidos (por ejemplo, el destino de compilación personalizado aquí es CustomAfterBuild, no AfterBuild), ya que la importación del SDK invalida esos destinos predefinidos, que 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 DependsOn propiedades (por ejemplo, BuildDependsOn), para especificar destinos que se deben ejecutar antes de un destino estándar.

Este método es preferible invalidar destinos predefinidos, que se describe en la sección siguiente. La invalidación de destinos predefinidos es un método anterior que todavía se admite, pero, dado que MSBuild evalúa la definición de destinos secuencialmente, no hay ninguna manera de evitar que otro proyecto que importe el proyecto invalide los destinos que ya ha invalidado. Por lo tanto, por ejemplo, el último AfterBuild destino definido en el archivo del proyecto, una vez importados todos los demás proyectos, será el que se usa durante la compilación.

Puede protegerse frente a invalidaciones no deseadas de destinos reemplazando las DependsOn propiedades que se usan en atributos a DependsOnTargets lo largo de los destinos comunes. Por ejemplo, el Build destino contiene un DependsOnTargets valor de atributo de "$(BuildDependsOn)". Tenga en cuenta lo siguiente:

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

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

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

Puede invalidar este valor de propiedad declarando 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 DependsOn propiedad después de la última importación. Al incluir la propiedad anterior BuildDependsOn en la nueva propiedad, puede agregar nuevos destinos al principio y al final de la lista de destino. 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 ampliar aún más estas propiedades sin sobrescribir las personalizaciones realizadas.

Para invalidar una propiedad DependsOn

  1. Identifique una propiedad predefinida DependsOn en los destinos comunes que desea invalidar. Consulte la tabla siguiente para obtener una lista de las propiedades invalidadas DependsOn habitualmente.

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

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

  4. Compile el archivo del proyecto.

Propiedades DependsOn invalidadas normalmente

Nombre de propiedad Los destinos agregados se ejecutan antes de este punto:
BuildDependsOn Punto de entrada de compilación principal. Invalide esta propiedad si desea 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 (Compile destino). Invalide esta propiedad si desea 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). Invalide esta propiedad si desea limpiar la salida 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). Consulte ResolveAssemblyReference.

Ejemplo: BuildDependsOn y CleanDependsOn

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

En este ejemplo, se trata de un proyecto de estilo SDK. Como se mencionó en la nota sobre los proyectos de estilo SDK anteriores en este artículo, debe usar el método de importación manual en lugar del Sdk atributo que Visual Studio usa cuando genera 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 BuildDependsOn elementos y CleanDependsOn deben aparecer después de importar el archivo de destinos del SDK estándar.

Invalidar 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 BeforeBuild destino antes del destino principal CoreBuild y al AfterBuild destino después del CoreBuild destino. De forma predeterminada, los destinos vacíos de los destinos comunes no hacen nada, pero puede invalidar su comportamiento predeterminado definiendo los destinos que desea 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 invalidar un destino predefinido

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

  2. Identifique un destino predefinido en los destinos comunes que desea invalidar. Consulte la tabla siguiente para obtener la lista completa de destinos que puede invalidar de forma segura.

  3. Defina el destino o los destinos al final del archivo del proyecto, inmediatamente antes de la </Project> etiqueta y después de la importación explícita del 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 Sdk atributo del elemento de nivel Project superior.

  4. Compile el archivo del proyecto.

Tabla de destinos predefinidos

En la tabla siguiente se muestran todos los destinos de los destinos comunes que puede invalidar.

Nombre de destino Descripción
BeforeCompile, AfterCompile Las tareas que se insertan en uno de estos destinos se ejecutan antes o después de que se realice la compilación principal. La mayoría de las personalizaciones se realizan en uno de estos dos destinos.
BeforeBuild, AfterBuild Las tareas que se insertan en uno de estos destinos se ejecutarán antes o después de todo lo demás en la compilación. Nota: Los BeforeBuild destinos y AfterBuild ya están definidos en los comentarios al final de la mayoría de los archivos de proyecto, lo que le permite agregar fácilmente eventos previos y posteriores a la compilación al archivo del proyecto.
BeforeRebuild, AfterRebuild Las tareas que se insertan en uno de estos destinos se ejecutan antes o después de invocar la funcionalidad de recompilación principal. El orden de ejecución de destino en Microsoft.Common.targets es: BeforeRebuild, Clean, Buildy, a continuación AfterRebuild, .
BeforeClean, AfterClean Las tareas que se insertan en uno de estos destinos se ejecutan antes o después de invocar la funcionalidad de limpieza básica.
BeforePublish, AfterPublish Las tareas que se insertan en uno de estos destinos se ejecutan antes o después de invocar la funcionalidad de publicación principal.
BeforeResolveReferences, AfterResolveReferences Las tareas que se insertan en uno de estos destinos se ejecutan antes o después de que se resuelvan las referencias de ensamblado.
BeforeResGen, AfterResGen Las tareas que se insertan en uno de estos destinos se ejecutan antes o después de generar 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 de 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 las restricciones de 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 DependsOnTargets atributo 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 debe 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 usarlos en combinación. Por ejemplo: DependsOnTargets="GenerateAssemblyInfo" BeforeTargets="BeforeCompile".

Importaciones explícitas e implícitas

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

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

Cuando el proyecto usa el Sdk atributo , 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 la primera línea del archivo de proyecto, después del Project elemento :

<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 Sdk atributo 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 del SDK .targets 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.