Partilhar via


Como o MSBuild cria projetos

MSBuild é o mecanismo de compilação da Microsoft que é usado para criar a maioria dos projetos do Visual Studio. O MSBuild invoca compiladores e outras ferramentas para criar seu código, mas também inclui opções flexíveis de configuração e personalização e infraestrutura para criar não apenas binários compilados, mas também uma ampla gama de outros artefatos de saída. O MSBuild é muito configurável e personalizável, mas para tirar o máximo proveito dessa personalização, é importante entender como o MSBuild funciona. Neste artigo, você aprenderá como o MSBuild processa seus arquivos de projeto, sejam invocados do Visual Studio ou de uma linha de comando ou script. Saber como o MSBuild funciona pode ajudá-lo a diagnosticar melhor problemas e personalizar melhor seu processo de compilação. Este artigo descreve o processo de compilação e é amplamente aplicável a todos os tipos de projeto.

O processo de compilação completo consiste em:

  • inicialização inicial - o processamento de opções de linha de comando.
  • avaliação - a interpretação e processamento do texto do arquivo de projeto MSBuild.
  • Execução - executa as metas e tarefas que constroem o projeto.

Além dos arquivos de origem e outros artefatos de entrada, as importações externas definem os detalhes do processo de compilação, incluindo importações padrão , como Microsoft.Common.targets , e importações configuráveis pelo usuário no nível da solução ou do projeto.

Startup

MSBuild pode ser invocado do Visual Studio através do modelo de objeto MSBuild em Microsoft.Build.dll, ou invocando o executável (MSBuild.exe ou dotnet build) diretamente na linha de comando, ou em um script, como em sistemas CI. Em ambos os casos, as entradas que afetam o processo de compilação incluem o arquivo de projeto (ou objeto de projeto interno ao Visual Studio), possivelmente um arquivo de solução, variáveis de ambiente e opções de linha de comando ou seus equivalentes de modelo de objeto. Durante a fase de inicialização, as opções de linha de comando ou equivalentes de modelo de objeto são usadas para definir as configurações do MSBuild, como configurar loggers. As propriedades definidas na linha de comando usando a opção ou -p são definidas como propriedades globais, que substituem quaisquer valores que seriam definidos nos arquivos de projeto, mesmo que os -property arquivos de projeto sejam lidos posteriormente.

As próximas seções são sobre os arquivos de entrada, como arquivos de solução ou arquivos de projeto.

Soluções e projetos

As instâncias do MSBuild podem consistir em um projeto ou vários projetos como parte de uma solução. Arquivos de .slnx solução no formato ou no .sln formato são suportados (no MSBuild 17.12 e posterior). O arquivo de solução (.sln) não é um arquivo XML do MSBuild, mas o MSBuild o interpreta para conhecer todos os projetos que precisam ser criados para a configuração e as configurações da plataforma dadas. Quando o MSBuild processa essa entrada, ela é chamada de compilação da solução. Ele tem alguns pontos extensíveis que permitem que você execute algo em cada compilação de solução, mas como essa compilação é uma execução separada das compilações de projeto individuais, nenhuma configuração de propriedades ou definições de destino da compilação de solução são relevantes para cada compilação de projeto.

Você pode descobrir como estender a compilação da solução em Personalizar a compilação da solução.

Compilações do Visual Studio versus compilações MSBuild.exe

Há algumas diferenças significativas entre quando projetos compilados no Visual Studio vs. quando você invoca MSBuild diretamente, seja através do executável MSBuild, ou quando você usa o modelo de objeto MSBuild para iniciar uma compilação. O Visual Studio gerencia a ordem de compilação do projeto para compilações do Visual Studio; ele só chama MSBuild no nível de projeto individual e, quando isso acontece, algumas propriedades booleanas (BuildingInsideVisualStudio, BuildProjectReferences) são definidas que afetam significativamente o que o MSBuild faz. Dentro de cada projeto, a execução ocorre da mesma forma que quando invocada através do MSBuild, mas a diferença surge com projetos referenciados. No MSBuild, quando projetos referenciados são necessários, uma compilação realmente ocorre; ou seja, ele executa tarefas e ferramentas e gera a saída. Quando uma compilação do Visual Studio encontra um projeto referenciado, o MSBuild retorna apenas as saídas esperadas do projeto referenciado; ele permite que o Visual Studio controle a criação desses outros projetos. O Visual Studio determina a ordem de compilação e chama o MSBuild separadamente (conforme necessário), tudo completamente sob o controle do Visual Studio.

Outra diferença surge quando o MSBuild é invocado com um arquivo de solução, o MSBuild analisa o arquivo de solução, cria um arquivo de entrada XML padrão, avalia-o e o executa como um projeto. A compilação da solução é executada antes de qualquer projeto. Ao criar a partir do Visual Studio, nada disso acontece; MSBuild nunca vê o arquivo de solução. Como consequência, a personalização da compilação da solução (usando antes. SolutionName.sln.alvos e depois. SolutionName.sln.targets) só se aplica a compilações MSBuild.exe, dotnet buildou orientadas a modelos de objeto, não a compilações do Visual Studio.

SDKs de projeto

O recurso SDK para arquivos de projeto MSBuild é relativamente novo. Antes dessa alteração, os arquivos de projeto importavam explicitamente os arquivos .targets e .props que definiam o processo de compilação para um tipo de projeto específico.

Os projetos do .NET Core importam a versão do SDK do .NET apropriada para eles. Consulte a visão geral, SDKs de projeto .NET Core e a referência às propriedades.

Fase de avaliação

Esta seção discute como esses arquivos de entrada são processados e analisados para produzir objetos na memória que determinam o que será criado.

O objetivo da fase de avaliação é criar as estruturas de objetos na memória com base nos arquivos XML de entrada e no ambiente local. A fase de avaliação consiste em seis etapas que processam os arquivos de entrada, como os arquivos XML do projeto ou, e os arquivos XML importados, geralmente nomeados como arquivos .props ou .targets , dependendo se eles definem principalmente propriedades ou destinos de compilação. Cada passo cria uma parte dos objetos na memória que são usados posteriormente na fase de execução para construir os projetos, mas nenhuma ação de compilação real ocorre durante a fase de avaliação. Dentro de cada passagem, os elementos são processados na ordem em que aparecem.

Os resultados na fase de avaliação são os seguintes:

  • Avaliar variáveis de ambiente
  • Avaliar importações e propriedades
  • Avaliar definições de itens
  • Avaliar itens
  • Avaliar elementos UsingTask
  • Avaliar metas

Importações e propriedades são avaliadas na mesma passagem em sequência de aparência, como se as importações fossem expandidas no lugar. Assim, as configurações de propriedade em arquivos importados anteriormente estão disponíveis em arquivos importados posteriormente.

A ordem desses passes tem implicações significativas e é importante saber ao personalizar o arquivo de projeto. Consulte Ordem de avaliação de propriedades e itens.

Avaliar variáveis de ambiente

Nesta fase, as variáveis de ambiente são usadas para definir propriedades equivalentes. Por exemplo, a variável de ambiente PATH é disponibilizada como uma propriedade $(PATH). Quando executado a partir da linha de comando ou de um script, o ambiente de comando é usado normalmente e, quando executado a partir do Visual Studio, o ambiente em vigor quando o Visual Studio é iniciado é usado.

Avaliar importações e propriedades

Nesta fase, todo o XML de entrada é lido, incluindo os arquivos de projeto e toda a cadeia de importações. MSBuild cria uma estrutura XML na memória que representa o XML do projeto e todos os arquivos importados. Neste momento, as propriedades que não estão nas metas são avaliadas e definidas.

Como consequência da leitura do MSBuild de todos os arquivos de entrada XML no início de seu processo, quaisquer alterações nessas entradas durante o processo de compilação não afetam a compilação atual.

As propriedades fora de qualquer destino são tratadas de forma diferente das propriedades dentro dos destinos. Nesta fase, apenas as propriedades definidas fora de qualquer destino são avaliadas.

Como as propriedades são processadas em ordem na passagem de propriedades, uma propriedade em qualquer ponto da entrada pode acessar valores de propriedade que aparecem anteriormente na entrada, mas não propriedades que aparecem posteriormente.

Como as propriedades são processadas antes que os itens sejam avaliados, você não pode acessar o valor de nenhum item durante qualquer parte da passagem de propriedades.

Avaliar definições de itens

Nesta fase, as definições de item são interpretadas e uma representação na memória dessas definições é criada.

Avaliar itens

Os itens definidos dentro de um destino são tratados de forma diferente dos itens fora de qualquer destino. Nesta fase, os itens fora de qualquer destino e seus metadados associados são processados. O conjunto de metadados por definições de item é substituído pelo conjunto de metadados nos itens. Como os itens são processados na ordem em que aparecem, você pode fazer referência a itens que foram definidos anteriormente, mas não aos que aparecem posteriormente. Como a passagem de itens é depois que as propriedades passam, os itens podem acessar qualquer propriedade se definida fora de quaisquer destinos, independentemente de a definição de propriedade aparecer mais tarde.

Avaliar UsingTask elementos

Nesta fase, os elementos UsingTask são lidos e as tarefas são declaradas para uso posterior durante a fase de execução.

Avaliar metas

Nesta fase, todas as estruturas de objeto de destino são criadas na memória, em preparação para a execução. Não há execução efetiva.

Fase de execução

Na fase de execução, os alvos são ordenados e executados, e todas as tarefas são executadas. Mas, primeiro, propriedades e itens que são definidos dentro de metas são avaliados juntos em uma única fase na ordem em que aparecem. A ordem de processamento é notavelmente diferente de como as propriedades e os itens que não estão em um destino são processados: todas as propriedades primeiro e, em seguida, todos os itens, em etapas separadas. As alterações nas propriedades e itens dentro de um destino podem ser observadas após o destino onde foram alterados.

Ordem de compilação de destino

Em um único projeto, os alvos são executados em série. A questão central é como determinar em que ordem construir tudo para que as dependências sejam usadas para construir os alvos na ordem certa.

A ordem de compilação de destino é determinada pelo uso dos BeforeTargetsatributos , DependsOnTargetse AfterTargets em cada destino. A ordem dos destinos posteriores pode ser influenciada durante a execução de um destino anterior se o destino anterior modificar uma propriedade referenciada nesses atributos.

As regras para ordenação são descritas em Determinar a ordem de compilação de destino. O processo é determinado por uma estrutura de pilha contendo alvos para construir. O destino na parte superior desta tarefa inicia a execução e, se depender de qualquer outra coisa, esses alvos são empurrados para o topo da pilha e começam a ser executados. Quando há um destino sem dependências, ele é executado até a conclusão e seu destino pai é retomado.

Referências do Projeto

Há dois caminhos de código que o MSBuild pode tomar, o normal, descrito aqui, e a opção de gráfico descrita na próxima seção.

Os projetos individuais especificam a sua dependência de outros projetos através de ProjectReference rubricas. Quando um projeto na parte superior da pilha começa a ser construído, ele atinge o ponto em que o ResolveProjectReferences destino é executado, um destino padrão definido nos arquivos de destino comuns.

ResolveProjectReferences invoca a tarefa MSBuild com entradas dos ProjectReference itens para obter as saídas. Os ProjectReference itens são transformados em itens locais, como Reference. A fase de execução do MSBuild para o projeto atual pausa enquanto a fase de execução começa a processar o projeto referenciado (a fase de avaliação é feita primeiro, conforme necessário). O projeto referenciado só é construído depois que você começa a criar o projeto dependente e, portanto, isso cria uma árvore de construção de projetos.

Visual Studio permite criar dependências de projeto em arquivos de solução (.sln). As dependências são especificadas no arquivo de solução e só são respeitadas ao criar uma solução ou ao criar dentro do Visual Studio. Se você criar um único projeto, esse tipo de dependência será ignorado. As referências de solução são transformadas pelo MSBuild em ProjectReference itens e, posteriormente, são tratadas da mesma maneira.

Opção de gráfico

Se você especificar a opção de compilação do gráfico (-graphBuild ou -graph), o ProjectReference se tornará um conceito de primeira classe usado pelo MSBuild. O MSBuild analisará todos os projetos e construirá o gráfico de ordem de construção, um gráfico de dependência real de projetos, que é então percorrido para determinar a ordem de compilação. Tal como acontece com as metas em projetos individuais, o MSBuild garante que os projetos referenciados são construídos após os projetos dos quais dependem.

Execução paralela

Se estiver usando suporte a multiprocessador (-maxCpuCount ou -m switch), o MSBuild cria nós, que são processos do MSBuild que usam os núcleos de CPU disponíveis. Cada projeto é submetido a um nó disponível. Dentro de um nó, as compilações de projetos individuais são executadas em série.

As tarefas podem ser habilitadas para execução paralela definindo uma variável BuildInParallelbooleana , que é definida de acordo com o $(BuildInParallel) valor da propriedade no MSBuild. Para tarefas habilitadas para execução paralela, um agendador de trabalho gerencia nós e atribui trabalho a nós.

Consulte Criando vários projetos em paralelo com o MSBuild

Importações normalizadas

Microsoft.Common.props e Microsoft.Common.targets são importados por arquivos de projeto .NET (explícita ou implicitamente em projetos no estilo SDK) e estão localizados na pasta MSBuild\Current\bin em uma instalação do Visual Studio. Os projetos C++ têm sua própria hierarquia de importações; consulte MSBuild Internals para projetos C++.

O arquivo Microsoft.Common.props define os padrões que você pode substituir. Ele é importado (explícita ou implicitamente) no início de um arquivo de projeto. Dessa forma, as configurações do seu projeto aparecem após os padrões, para que eles os substituam.

O arquivo Microsoft.Common.targets e os arquivos de destino que ele importa definem o processo de compilação padrão para projetos .NET. Ele também fornece pontos de extensão que você pode usar para personalizar a compilação.

Na implementação, Microsoft.Common.targets é um wrapper fino que importa Microsoft.Common.CurrentVersion.targets. Esse arquivo contém configurações para propriedades padrão e define os destinos reais que definem o processo de compilação. A Build meta é definida aqui, mas na verdade está vazia. No entanto, o Build destino contém o DependsOnTargets atributo que especifica os destinos individuais que compõem as etapas de compilação reais, que são BeforeBuild, CoreBuilde AfterBuild. O Build objetivo é definido do seguinte modo:

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

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

BeforeBuild e AfterBuild são pontos de extensão. Eles estão vazios no arquivo Microsoft.Common.CurrentVersion.targets , mas os projetos podem fornecer seus próprios BeforeBuild e AfterBuild destinos com tarefas que precisam ser executadas antes ou depois do processo de compilação principal. AfterBuildé executado antes do destino no-op, Buildporque aparece no DependsOnTargets atributo no Build destino, mas ocorre depois CoreBuildAfterBuild de .

O CoreBuild destino contém as chamadas para as ferramentas de compilação, da seguinte maneira:

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

A tabela a seguir descreve esses alvos; Algumas metas são aplicáveis apenas a determinados tipos de projetos.

Target Description
BuildOnlySettings Configurações apenas para compilações reais, não para quando o MSBuild é invocado no carregamento do projeto pelo Visual Studio.
PrepareForBuild Preparar os pré-requisitos para a construção
PreBuildEvent Ponto de extensão para projetos para definir tarefas a serem executadas antes da compilação
ResolveProjectReferences Analise dependências de projeto e crie projetos referenciados
ResolveAssemblyReferences Localize assemblies referenciados.
ResolveReferences Consiste em ResolveProjectReferences e ResolveAssemblyReferences para encontrar todas as dependências
PrepararRecursos Processar arquivos de recursos
ResolveKeySource Resolva a chave de nome forte usada para assinar o assembly e o certificado usado para assinar os manifestos ClickOnce .
Compilar Invoca o compilador
ExportWindowsMDFile Gere um arquivo WinMD a partir dos arquivos WinMDModule gerados pelo compilador.
UnmanagedUnregistration Remover/limpar as entradas do Registro COM Interop de uma compilação anterior
GenerateSerializationAssemblies Gere um assembly de serialização XML usando sgen.exe.
CreateSatelliteAssemblies Crie um conjunto de satélites para cada cultura única nos recursos.
Gerar manifestos Gera manifestos de aplicativo e implantação ClickOnce ou um manifesto nativo.
GetTargetPath Retornar um item que contém o produto de compilação (executável ou assembly) para este projeto, com metadados.
PrepareForRun Copie as saídas de compilação para o diretório final, se elas tiverem sido alteradas.
UnmanagedRegistration Definir entradas do Registro para Interoperabilidade COM
IncrementalClean Remova arquivos que foram produzidos em uma compilação anterior, mas não foram produzidos na compilação atual. Isso é necessário para fazer Clean o trabalho em compilações incrementais.
PostBuildEvent Ponto de extensão para projetos para definir tarefas a serem executadas após a compilação

Muitos dos destinos na tabela anterior são encontrados em importações específicas do idioma, como Microsoft.CSharp.targets. Esse arquivo define as etapas no processo de compilação padrão específico para projetos C# .NET. Por exemplo, ele contém o Compile destino que realmente chama o compilador C#.

Importações configuráveis pelo usuário

Além das importações padrão, há várias importações que você pode adicionar para personalizar o processo de compilação.

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

Esses arquivos são lidos pelas importações padrão para quaisquer projetos em qualquer subpasta sob eles. Isso geralmente ocorre no nível da solução para configurações para controlar todos os projetos na solução, mas também pode ser mais alto no sistema de arquivos, até a raiz da unidade.

O arquivo Directory.Build.props é importado por Microsoft.Common.props, portanto, as propriedades definidas nele estão disponíveis no arquivo de projeto. Eles podem ser redefinidos no arquivo de projeto para personalizar os valores por projeto. O arquivo Directory.Build.targets é lido após o arquivo de projeto. Ele geralmente contém destinos, mas aqui você também pode definir propriedades que não deseja que projetos individuais redefinem.

Personalizações em um arquivo de projeto

O Visual Studio atualiza seus arquivos de projeto à medida que você faz alterações no Gerenciador de Soluções, na janela Propriedades ou nas Propriedades do Projeto, mas você também pode fazer suas próprias alterações editando diretamente o arquivo de projeto.

Muitos comportamentos de compilação podem ser configurados definindo propriedades do MSBuild, seja no arquivo de projeto para configurações locais para um projeto, ou como mencionado na seção anterior, criando um arquivo Directory.Build.props para definir propriedades globalmente para pastas inteiras de projetos e soluções. Para compilações ad hoc na linha de comando ou scripts, você também pode usar a /p opção na linha de comando para definir propriedades para uma invocação específica do MSBuild. Consulte Propriedades comuns do projeto MSBuild para obter informações sobre as propriedades que você pode definir.