Compartir a través de


Cómo MSBuild construye proyectos

¿Cómo funciona MSBuild realmente? En este artículo, aprenderá cómo MSBuild procesa los archivos del proyecto, tanto si se invoca desde Visual Studio como desde una línea de comandos o un script. Saber cómo funciona MSBuild puede ayudarle a diagnosticar mejor los problemas y personalizar mejor el proceso de compilación. En este artículo se describe el proceso de compilación y se aplica en gran medida a todos los tipos de proyecto.

El proceso de compilación completo consta de las fases de inicio, evaluación y ejecución de los destinos y las tareas que compilan el proyecto. Además de estas entradas, las importaciones externas definen los detalles del proceso de compilación, incluidas las importaciones estándar , como Microsoft.Common.targets y importaciones configurables por el usuario en el nivel de solución o proyecto.

Inicio

MSBuild se puede invocar desde Visual Studio a través del modelo de objetos de MSBuild en Microsoft.Build.dll, o invocando el ejecutable (MSBuild.exe o dotnet build) directamente en la línea de comandos o en un script, como en sistemas de CI. En cualquier caso, las entradas que afectan al proceso de compilación incluyen el archivo de proyecto (o el objeto de proyecto interno en Visual Studio), posiblemente un archivo de solución, variables de entorno y modificadores de línea de comandos o sus equivalentes del modelo de objetos. Durante la fase de inicio, las opciones de línea de comandos o los equivalentes del modelo de objetos se usan para configurar las opciones de MSBuild, como la configuración de registradores. Las propiedades establecidas en la línea de comandos mediante el modificador -property o -p se establecen como propiedades globales, que invalidan los valores que se establecerían en los archivos del proyecto, aunque los archivos de proyecto se lean más adelante.

Las secciones siguientes son sobre los archivos de entrada, como archivos de solución o archivos de proyecto.

Soluciones y proyectos

Las instancias de MSBuild pueden constar de un proyecto o de muchos proyectos como parte de una solución. El archivo de solución no es un archivo XML de MSBuild, pero MSBuild lo interpreta para conocer todos los proyectos necesarios para compilarse para la configuración y la plataforma especificadas. Cuando MSBuild procesa esta entrada XML, se conoce como compilación de la solución. Tiene algunos puntos extensibles que permiten ejecutar algo en cada compilación de solución, pero dado que esta compilación es una ejecución independiente de las compilaciones de proyecto individuales, no hay ninguna configuración de propiedades o definiciones de destino de la compilación de la solución que sean relevantes para cada compilación de proyecto.

Puede averiguar cómo ampliar la compilación de la solución en Personalizar la compilación de la solución.

Compilaciones de Visual Studio frente a compilaciones de MSBuild.exe

Existen algunas diferencias significativas entre cuando los proyectos se compilan en Visual Studio frente a cuando se invoca MSBuild directamente, ya sea a través del ejecutable de MSBuild o cuando se usa el modelo de objetos de MSBuild para iniciar una compilación. Visual Studio administra el orden de compilación del proyecto para las compilaciones de Visual Studio; solo llama a MSBuild en el nivel de proyecto individual y, cuando lo hace, se establecen un par de propiedades booleanas (BuildingInsideVisualStudio, BuildProjectReferences) que afectan significativamente a lo que hace MSBuild. Dentro de cada proyecto, la ejecución se produce igual que cuando se invoca a través de MSBuild, pero la diferencia surge con los proyectos a los que se hace referencia. En MSBuild, cuando se requiere un proyecto al que se hace referencia, se produce realmente una compilación; es decir, ejecuta tareas y herramientas y genera la salida. Cuando una compilación de Visual Studio encuentra un proyecto al que se hace referencia, MSBuild solo devuelve las salidas esperadas del proyecto al que se hace referencia; permite a Visual Studio controlar la compilación de esos otros proyectos. Visual Studio determina el orden de compilación y llama a MSBuild por separado (según sea necesario), todo ello completamente bajo el control de Visual Studio.

Otra diferencia surge cuando MSBuild se invoca con un archivo de solución, MSBuild analiza el archivo de solución, crea un archivo de entrada XML estándar, lo evalúa y lo ejecuta como un proyecto. La compilación de la solución se ejecuta antes de cualquier proyecto. Al compilar desde Visual Studio, no ocurre nada de esto; MSBuild nunca ve el archivo de solución. Como consecuencia, la personalización de la compilación de soluciones (con antes. SolutionName.sln.targets y después. SolutionName.sln.targets) solo se aplica a las compilaciones controladas por MSBuild.exe, dotnet buildo controladas por modelos de objetos, no a compilaciones de Visual Studio.

SDK de proyecto

La característica del SDK para los archivos de proyecto de MSBuild es relativamente nueva. Antes de este cambio, los archivos de proyecto importaron explícitamente los archivos .targets y .props que definían el proceso de compilación para un tipo de proyecto específico.

Los proyectos de .NET Core importan la versión del SDK de .NET adecuada para ellos. Vea el artículo de información general, SDK de proyectos de .NET Core, y la referencia a las propiedades.

Fase de evaluación

En esta sección se describe cómo se procesan y analizan estos archivos de entrada para generar objetos en memoria que determinan lo que se va a compilar.

El propósito de la fase de evaluación es crear las estructuras de objeto en memoria en función de los archivos XML de entrada y el entorno local. La fase de evaluación consta de seis pases que procesan los archivos de entrada, como los archivos XML del proyecto o, y los archivos XML importados, denominados generalmente como .props o .targets archivos, dependiendo de si establecen propiedades o definen destinos de compilación. Cada paso compila una parte de los objetos en memoria que se usan más adelante en la fase de ejecución para compilar los proyectos, pero no se producen acciones de compilación reales durante la fase de evaluación. Dentro de cada paso, los elementos se procesan en el orden en que aparecen.

Los pasos de la fase de evaluación son los siguientes:

  • Evaluación de variables de entorno
  • Evaluación de importaciones y propiedades
  • Evaluación de definiciones de elementos
  • Evaluar elementos
  • Evaluar elementos UsingTask
  • Evaluar objetivos

Las importaciones y las propiedades se evalúan en la misma secuencia de aparición, como si las importaciones se expandieran en su lugar. Por lo tanto, la configuración de propiedades de los archivos importados anteriormente está disponible en archivos importados posteriores.

El orden de estos pases tiene implicaciones significativas y es importante saberlo al personalizar el archivo del proyecto. Consulte la sección Orden de evaluación de propiedades y elementos.

Evaluación de variables de entorno

En esta fase, las variables de entorno se usan para establecer propiedades equivalentes. Por ejemplo, la variable de entorno PATH está disponible como una propiedad $(PATH). Cuando se ejecuta desde la línea de comandos o un script, el entorno de comandos se usa de manera normal y, cuando se ejecuta desde Visual Studio, se utiliza el entorno activo al iniciar Visual Studio.

Evaluación de importaciones y propiedades

En esta fase, se lee todo el XML de entrada, incluidos los archivos del proyecto y toda la cadena de importaciones. MSBuild crea una estructura XML en memoria que representa el XML del proyecto y todos los archivos importados. En este momento, las propiedades que no están incluidas en los objetivos se evalúan y configuran.

Como consecuencia de que MSBuild lee todos los archivos de entrada XML al principio de su proceso, los cambios en esas entradas durante el proceso de compilación no afectan a la compilación actual.

Las propiedades fuera de cualquier destino se controlan de forma diferente a las propiedades dentro de los destinos. En esta fase, solo se evalúan las propiedades definidas fuera de cualquier destino.

Dado que las propiedades se procesan en orden en el paso de propiedades, una propiedad en cualquier punto de la entrada puede tener acceso a los valores de propiedad que aparecen anteriormente en la entrada, pero no a las propiedades que aparecen más adelante.

Dado que las propiedades se procesan antes de evaluar los elementos, no se puede tener acceso al valor de ningún elemento durante cualquier parte del proceso de propiedades.

Evaluación de definiciones de elementos

En esta fase, se interpretan las definiciones de elementos y se crea una representación en memoria de esas definiciones.

Evaluar elementos

Los elementos definidos dentro de un destino se controlan de forma diferente a los elementos fuera de cualquier destino. En esta fase, se procesan los elementos fuera de cualquier destino y sus metadatos asociados. Los metadatos establecidos por definiciones de elementos se reemplazan por los metadatos establecidos en los elementos. Dado que los elementos se procesan en el orden en que aparecen, puede hacer referencia a elementos que se han definido anteriormente, pero no a los que aparecen más adelante. Y como el paso de elementos va después del paso de propiedades, los elementos pueden acceder a cualquier propiedad si se definen fuera de los destinos, independientemente de si la definición de propiedad aparece más adelante.

Evaluar elementos UsingTask

En esta fase, se leen los elementos UsingTask y se declaran las tareas para su uso posterior durante la fase de ejecución.

Evaluar destinos

En esta fase, todas las estructuras de objetos de destino se crean en memoria, en preparación para su ejecución. No se realiza ninguna ejecución real.

Fase de ejecución

En la fase de ejecución, los destinos se ordenan y ejecutan, y todas las tareas se ejecutan. Pero en primer lugar, las propiedades y los elementos definidos dentro de los destinos se evalúan conjuntamente en una sola fase en el orden en que aparecen. El orden de procesamiento es especialmente diferente de cómo se procesan las propiedades y los elementos que no están en un destino: todas las propiedades primero y, a continuación, todos los elementos, en pasos independientes. Los cambios en las propiedades y los elementos dentro de un destino se pueden observar después del destino en el que se cambiaron.

Orden de compilación de destinos

En un solo proyecto, los objetivos se ejecutan de forma secuencial. El problema central es cómo determinar en qué orden construir todo para que las dependencias se usen para construir los objetivos en el orden correcto.

El orden de compilación de los destinos viene determinado por el uso de los atributos BeforeTargets, DependsOnTargets y AfterTargets en cada destino. El orden de los destinos posteriores se puede influir durante la ejecución de un destino anterior si el destino anterior modifica una propiedad a la que se hace referencia en estos atributos.

Las reglas para determinar el orden de compilación objetivo se describen en Determine the target build order. El proceso viene determinado por una estructura de pila que contiene los destinos que se van a compilar. El destino de la parte superior de esta tarea inicia la ejecución y, si depende de cualquier otro, los destinos se insertan en la parte superior de la pila y se inicia la ejecución. Cuando hay un destino sin dependencias, se ejecuta hasta su finalización y se reanuda su destino primario.

Referencias de proyecto

Hay dos rutas de acceso de código que MSBuild puede tomar, la normal, que se describe aquí y la opción de grafo que se describe en la sección siguiente.

Los proyectos individuales especifican su dependencia de otros proyectos a través de elementos ProjectReference. Cuando se empieza a compilar un proyecto en la parte superior de la pila, alcanza el punto en el que se ejecuta el destino ResolveProjectReferences, un destino estándar definido en los archivos de destino comunes.

ResolveProjectReferences invoca la tarea MSBuild utilizando como entradas los elementos de ProjectReference para obtener los resultados. Los elementos de ProjectReference se transforman en elementos locales, como Reference. La fase de ejecución de MSBuild para el proyecto actual se detiene mientras la fase de ejecución comienza a procesar el proyecto al que se hace referencia (la fase de evaluación se realiza primero según sea necesario). El proyecto al que se hace referencia solo se compila después de empezar a compilar el proyecto dependiente, por lo que se crea un árbol de proyectos.

Visual Studio permite crear dependencias de proyecto en archivos de solución (.sln). Las dependencias se especifican en el archivo de solución y solo se respetan al compilar una solución o al compilar dentro de Visual Studio. Si compila un solo proyecto, se omite este tipo de dependencia. MsBuild transforma las referencias de solución en elementos ProjectReference y, a partir de entonces, se tratan de la misma manera.

Opción de gráfica

Si especifica el modificador de compilación del grafo (-graphBuild o -graph), ProjectReference se convierte en un concepto de primera clase que usa MSBuild. MSBuild analizará todos los proyectos y construirá el gráfico de orden de compilación, un gráfico de dependencias real de proyectos, que luego se recorre para determinar el orden de compilación. Al igual que con los destinos de proyectos individuales, MSBuild se asegura de que los proyectos a los que se hace referencia se compilen después de los proyectos de los que dependen.

Ejecución en paralelo

Si usa compatibilidad con varios procesadores (-maxCpuCount o conmutador de -m), MSBuild crea nodos, que son procesos de MSBuild que usan los núcleos de CPU disponibles. Cada proyecto se envía a un nodo disponible. Dentro de un nodo, las compilaciones de proyecto individuales se ejecutan en serie.

Las tareas se pueden habilitar para la ejecución en paralelo estableciendo una variable booleana BuildInParallel, que se establece según el valor de la propiedad $(BuildInParallel) en MSBuild. En el caso de las tareas habilitadas para la ejecución en paralelo, un programador de trabajo administra los nodos y asigna trabajo a los nodos.

Consulte Creación de varios proyectos en paralelo con MSBuild

Importaciones estándar

microsoft.Common.props y microsoft.Common.targets se importan mediante archivos de proyecto de .NET (explícita o implícitamente en proyectos de estilo SDK) y se encuentran en la carpeta MSBuild\Current\bin en una instalación de Visual Studio. Los proyectos de C++ tienen su propia jerarquía de importaciones. Consulte Parámetros internos de MSBuild para proyectos de C++.

El archivo Microsoft.Common.props establece valores predeterminados que pueden invalidarse. Se importa (explícita o implícitamente) al principio de un archivo de proyecto. Así, la configuración del proyecto aparece después de los valores predeterminados, de modo que se invalidan.

El archivo Microsoft.Common.targets y los archivos de destino que importa definen el proceso de compilación estándar para los proyectos de .NET. También proporciona puntos de extensión que puede usar para personalizar la compilación.

En la implementación, Microsoft.Common.targets es un contenedor fino que importa Microsoft.Common.CurrentVersion.targets. Este archivo contiene la configuración de las propiedades estándar y define los destinos reales que definen el proceso de compilación. El destino Build se define aquí, pero en realidad está vacío. Sin embargo, el destino de Build contiene el atributo DependsOnTargets que especifica los destinos individuales que componen los pasos de compilación reales, que son BeforeBuild, CoreBuildy AfterBuild. El destino Build se define de la manera siguiente:

  <PropertyGroup>
    <BuildDependsOn>
      BeforeBuild;
      CoreBuild;
      AfterBuild
    </BuildDependsOn>
  </PropertyGroup>

  <Target
      Name="Build"
      Condition=" '$(_InvalidConfigurationWarning)' != 'true' "
      DependsOnTargets="$(BuildDependsOn)"
      Returns="@(TargetPathWithTargetPlatformMoniker)" />

BeforeBuild y AfterBuild son puntos de extensión. Están vacíos en el archivo Microsoft.Common.CurrentVersion.targets, pero los proyectos pueden proporcionar sus propios destinos BeforeBuild y AfterBuild con tareas que deben realizarse antes o después del proceso de compilación principal. AfterBuild se ejecuta antes del destino de no-op, Build, porque AfterBuild aparece en el atributo DependsOnTargets en el destino de Build, pero se produce después de CoreBuild.

El destino CoreBuild contiene las llamadas a las herramientas de compilación, como se indica a continuación:

  <PropertyGroup>
    <CoreBuildDependsOn>
      BuildOnlySettings;
      PrepareForBuild;
      PreBuildEvent;
      ResolveReferences;
      PrepareResources;
      ResolveKeySource;
      Compile;
      ExportWindowsMDFile;
      UnmanagedUnregistration;
      GenerateSerializationAssemblies;
      CreateSatelliteAssemblies;
      GenerateManifests;
      GetTargetPath;
      PrepareForRun;
      UnmanagedRegistration;
      IncrementalClean;
      PostBuildEvent
    </CoreBuildDependsOn>
  </PropertyGroup>
  <Target
      Name="CoreBuild"
      DependsOnTargets="$(CoreBuildDependsOn)">

    <OnError ExecuteTargets="_TimeStampAfterCompile;PostBuildEvent" Condition="'$(RunPostBuildEvent)'=='Always' or '$(RunPostBuildEvent)'=='OnOutputUpdated'"/>
    <OnError ExecuteTargets="_CleanRecordFileWrites"/>

  </Target>

En la tabla siguiente se describen estos destinos; algunos destinos solo son aplicables a determinados tipos de proyecto.

Destino Descripción
BuildOnlySettings Configuraciones solo para compilaciones reales, no para cuando MSBuild es invocado por Visual Studio al cargar el proyecto.
PrepareForBuild Prepara los requisitos previos para la compilación
PreBuildEvent Punto de extensión para que los proyectos definan tareas que se ejecutarán antes de la construcción
ResolveProjectReferences Análisis de dependencias del proyecto y compilación de proyectos a los que se hace referencia
ResolveAssemblyReferences Busque ensamblados a los que se hace referencia.
ResolveReferences Consta de ResolveProjectReferences y ResolveAssemblyReferences para buscar todas las dependencias.
PrepareResources Procesar archivos de recursos
ResolveKeySource Resuelve la clave de nombre seguro utilizada para firmar el ensamblado y el certificado usado para firmar los manifiestos ClickOnce.
Compile Invoca al compilador
ExportWindowsMDFile Genere un archivo WinMD a partir de los archivos WinMDModule generados por el compilador.
UnmanagedUnregistration Quita o limpia las entradas del registro de interoperabilidad COM de una compilación anterior.
GenerateSerializationAssemblies Genere un ensamblado de serialización XML mediante sgen.exe.
CreateSatelliteAssemblies Crea un ensamblado satélite para cada referencia cultural única en los recursos.
Generar manifiestos Genera manifiestos de aplicación y de implementación de ClickOnce o un manifiesto nativo.
GetTargetPath Devuelve un elemento que contiene el producto de compilación (ejecutable o ensamblado) para este proyecto, con metadatos.
PrepareForRun Copie las salidas de compilación en el directorio final si han cambiado.
Registro no gestionado Establece entradas del registro para la Interoperabilidad COM.
IncrementalClean Quite los archivos que se generaron en una compilación anterior, pero que no se generaron en la compilación actual. Esto es necesario para que Clean funcionen en compilaciones incrementales.
PostBuildEvent Punto de extensión para que los proyectos definan tareas que se van a ejecutar después de la compilación

Muchos de los objetivos de la tabla anterior se encuentran en importaciones específicas del idioma, como Microsoft.CSharp.targets. Este archivo define los pasos del proceso de compilación estándar específico para los proyectos de .NET de C#. Por ejemplo, contiene el destino Compile que llama realmente al compilador de C#.

Importaciones configurables por el usuario

Además de las importaciones estándar, hay varias importaciones que puede agregar para personalizar el proceso de compilación.

  • Directory.Build.props
  • Directory.Build.targets

Las importaciones estándar leen estos archivos para cualquier proyecto que se encuentre un nivel por debajo. Esto suele estar en el nivel de solución para que la configuración controle todos los proyectos de la solución, pero también podría estar más arriba en el sistema de archivos, hasta la raíz de la unidad.

El archivo Directory.Build.props se importa mediante Microsoft.Common.props, por lo que las propiedades definidas en el archivo del proyecto están disponibles. Se pueden redefinir en el archivo de proyecto para personalizar los valores por proyecto. El archivo Directory.Build.targets se lee después del archivo del proyecto. Normalmente contiene destinos, pero aquí también puede definir propiedades que no quiere que los proyectos individuales vuelvan a definir.

Personalizaciones en un archivo de proyecto

Visual Studio actualiza los archivos de proyecto a medida que realiza cambios en Explorador de soluciones, la ventana propiedades de o en Propiedades del proyecto, pero también puede realizar sus propios cambios editando directamente el archivo del proyecto.

Muchos comportamientos de compilación se pueden configurar estableciendo propiedades de MSBuild, ya sea en el archivo de proyecto para la configuración local en un proyecto, o como se mencionó en la sección anterior, creando un archivo Directory.Build.props para establecer propiedades globalmente para carpetas completas de proyectos y soluciones. Para compilaciones ad hoc en la línea de comandos o scripts, también puede usar la opción /p en la línea de comandos para establecer propiedades para una invocación determinada de MSBuild. Vea Propiedades comunes de proyectos de MSBuild para obtener información sobre las propiedades que se pueden establecer.