Condividi tramite


Modalità di compilazione dei progetti di MSBuild

Come funziona effettivamente MSBuild? In questo articolo si apprenderà come MSBuild elabora i file di progetto, se richiamati da Visual Studio o da una riga di comando o da uno script. Conoscere il funzionamento di MSBuild consente di diagnosticare meglio i problemi e personalizzare meglio il processo di compilazione. Questo articolo descrive il processo di compilazione ed è in gran parte applicabile a tutti i tipi di progetto.

Il processo di build completo è costituito dall'inizializzazione, valutazionee esecuzione degli obiettivi e delle attività che costituiscono il progetto. Oltre a questi input, le importazioni esterne definiscono i dettagli del processo di compilazione, incluse sia le importazioni standard come Microsoft.Common.targets e importazioni configurabili dall'utente a livello di soluzione o progetto.

Startup

MSBuild può essere richiamato da Visual Studio tramite il modello a oggetti MSBuild in Microsoft.Build.dlloppure richiamando il file eseguibile (MSBuild.exe o dotnet build) direttamente nella riga di comando o in uno script, ad esempio nei sistemi CI. In entrambi i casi, gli input che influiscono sul processo di compilazione includono il file di progetto (o l'oggetto progetto interno a Visual Studio), possibilmente un file di soluzione, variabili di ambiente e opzioni della riga di comando o equivalenti al modello a oggetti. Durante la fase di avvio, vengono usate le opzioni della riga di comando o gli equivalenti del modello a oggetti per configurare le impostazioni di MSBuild, ad esempio la configurazione dei logger. Le proprietà impostate nella riga di comando usando l'opzione -property o -p vengono impostate come proprietà globali, che eseguono l'override di tutti i valori che verrebbero impostati nei file di progetto, anche se i file di progetto vengono letti in un secondo momento.

Le sezioni successive riguardano i file di input, ad esempio i file di soluzione o i file di progetto.

Soluzioni e progetti

Le istanze di MSBuild possono essere costituite da un progetto o da molti progetti come parte di una soluzione. Il file della soluzione non è un file XML MSBuild, ma MSBuild lo interpreta per conoscere tutti i progetti che devono essere compilati per le impostazioni di configurazione e piattaforma specificate. Quando MSBuild elabora questo input XML, viene definito la compilazione della soluzione. Include alcuni punti estendibili che consentono di eseguire qualcosa in ogni compilazione della soluzione, ma poiché questa compilazione è un'esecuzione separata dalle singole compilazioni di progetto, nessuna impostazione delle proprietà o delle definizioni di destinazione dalla compilazione della soluzione è rilevante per ogni compilazione di progetto.

È possibile scoprire come estendere la compilazione della soluzione in Personalizzare la compilazione della soluzione.

Compilazioni di Visual Studio e compilazioni MSBuild.exe

Esistono alcune differenze significative tra quando i progetti vengono compilati in Visual Studio e quando si richiama MSBuild direttamente, tramite l'eseguibile MSBuild o quando si usa il modello a oggetti MSBuild per avviare una compilazione. Visual Studio gestisce l'ordine di compilazione del progetto per le compilazioni di Visual Studio; chiama solo MSBuild a livello di singolo progetto e, quando lo fa, vengono impostate alcune proprietà booleane (BuildingInsideVisualStudio, BuildProjectReferences) che influiscono in modo significativo sulle operazioni eseguite da MSBuild. All'interno di ogni progetto, l'esecuzione si verifica come quando viene richiamata tramite MSBuild, ma la differenza si verifica con i progetti a cui si fa riferimento. In MSBuild, quando sono necessari progetti a cui si fa riferimento, si verifica effettivamente una compilazione; ovvero esegue attività e strumenti e genera l'output. Quando una compilazione di Visual Studio trova un progetto a cui si fa riferimento, MSBuild restituisce solo gli output previsti dal progetto a cui si fa riferimento; consente a Visual Studio di controllare la compilazione di tali altri progetti. Visual Studio determina l'ordine di compilazione e le chiamate in MSBuild separatamente (in base alle esigenze), tutte completamente sotto il controllo di Visual Studio.

Un'altra differenza si verifica quando MSBuild viene richiamato con un file di soluzione, MSBuild analizza il file di soluzione, crea un file di input XML standard, lo valuta ed esegue come progetto. La compilazione della soluzione viene eseguita prima di qualsiasi progetto. Quando si compila da Visual Studio, nessuno di questi si verifica; MSBuild non vede mai il file della soluzione. Di conseguenza, la personalizzazione della compilazione della soluzione (usando prima. SolutionName.sln.targets e dopo. SolutionName.sln.targets) si applica solo alle compilazioni basate su MSBuild.exe, dotnet buildo basate su modello a oggetti, non alle compilazioni di Visual Studio.

SDK di progetto

La funzionalità SDK per i file di progetto MSBuild è relativamente nuova. Prima di questa modifica, i file di progetto importano in modo esplicito i .targets e file con estensione props che hanno definito il processo di compilazione per un determinato tipo di progetto.

I progetti .NET Core importano la versione di .NET SDK appropriata. Vedere la panoramica, SDK di progetto .NET Coree il riferimento alle proprietà .

Fase di valutazione

Questa sezione illustra come questi file di input vengono elaborati e analizzati per produrre oggetti in memoria che determinano cosa verrà compilato.

Lo scopo della fase di valutazione è creare le strutture degli oggetti in memoria in base ai file XML di input e all'ambiente locale. La fase di valutazione è costituita da sei passaggi che elaborano i file di input, ad esempio i file XML del progetto o i file XML importati, generalmente denominati come file con estensione .props o .targets, a seconda che impostino principalmente proprietà o definiscano destinazioni di compilazione. Ogni passaggio compila una parte degli oggetti in memoria usati successivamente nella fase di esecuzione per compilare i progetti, ma non si verificano azioni di compilazione effettive durante la fase di valutazione. All'interno di ogni passaggio, gli elementi vengono elaborati nell'ordine in cui vengono visualizzati.

I passaggi nella fase di valutazione sono i seguenti:

  • Valutare le variabili di ambiente
  • Valutare le importazioni e le proprietà
  • Valutare le definizioni degli elementi
  • Valutare gli elementi
  • Valutare elementi utilizzando UsingTask
  • Valutare le destinazioni

Le importazioni e le proprietà vengono valutate nello stesso ordine di apparizione, come se le importazioni vengono espanse sul posto. Di conseguenza, le impostazioni delle proprietà nei file importati in precedenza sono disponibili all'interno di file importati in un secondo momento.

L'ordine di questi passaggi ha implicazioni significative ed è importante sapere quando si personalizza il file di progetto. Vedere Ordine di valutazione delle proprietà e degli elementi.

Valutare le variabili di ambiente

In questa fase, le variabili di ambiente vengono usate per impostare proprietà equivalenti. Ad esempio, la variabile di ambiente PATH viene resa disponibile come proprietà $(PATH). Quando viene eseguito dalla riga di comando o da uno script, l'ambiente dei comandi viene usato come di consueto e quando viene eseguito da Visual Studio, l'ambiente in vigore quando viene avviato Visual Studio.

Valutare le importazioni e le proprietà

In questa fase l'intero codice XML di input viene letto, inclusi i file di progetto e l'intera catena di importazioni. MSBuild crea una struttura XML in memoria che rappresenta il codice XML del progetto e tutti i file importati. In questo momento, le proprietà che non sono presenti negli obiettivi vengono valutate e impostate.

In seguito alla lettura di tutti i file di input XML all'inizio del processo, tutte le modifiche apportate a tali input durante il processo di compilazione non influiscono sulla compilazione corrente.

Le proprietà esterne a qualsiasi destinazione vengono gestite in modo diverso dalle proprietà all'interno delle destinazioni. In questa fase vengono valutate solo le proprietà definite all'esterno di qualsiasi destinazione.

Poiché le proprietà vengono elaborate in ordine nel passaggio delle proprietà, una proprietà in qualsiasi punto dell'input può accedere ai valori delle proprietà visualizzati in precedenza nell'input, ma non alle proprietà visualizzate in un secondo momento.

Poiché le proprietà vengono elaborate prima della valutazione degli elementi, non è possibile accedere al valore di alcun elemento in nessuna fase del passaggio delle proprietà.

Valutare le definizioni degli elementi

In questa fase, definizioni di elementi vengono interpretate e viene creata una rappresentazione in memoria di queste definizioni.

Valutare gli elementi

Gli elementi definiti all'interno di una destinazione vengono gestiti in modo diverso dagli elementi esterni a qualsiasi destinazione. In questa fase vengono elaborati gli elementi esterni a qualsiasi destinazione e i relativi metadati associati. I metadati impostati dalle definizioni degli elementi sono sovrascritti dai metadati impostati sugli elementi. Poiché gli elementi vengono elaborati nell'ordine in cui vengono visualizzati, è possibile fare riferimento agli elementi definiti in precedenza, ma non a quelli visualizzati in un secondo momento. Poiché il passaggio degli elementi avviene dopo il passaggio delle proprietà, gli elementi possono accedere a qualsiasi proprietà se questa è definita al di fuori di qualsiasi target, indipendentemente dal fatto che la definizione della proprietà appaia successivamente.

Valuta gli elementi UsingTask

In questa fase, gli elementi UsingTask vengono letti e le attività vengono dichiarate per essere utilizzate successivamente durante la fase di esecuzione.

Valutare le destinazioni

In questa fase, tutte le strutture degli oggetti di destinazione vengono create in memoria, in preparazione all'esecuzione. Non viene eseguita alcuna esecuzione effettiva.

Fase di esecuzione

Nella fase di esecuzione, gli obiettivi vengono ordinati e avviati, e tutte le attività sono eseguite. Prima di tutto, le proprietà e gli elementi definiti all'interno delle destinazioni vengono valutati insieme in una singola fase nell'ordine in cui vengono visualizzati. L'ordine di elaborazione è in particolare diverso dal modo in cui vengono elaborate le proprietà e gli elementi che non si trovano in una destinazione: tutte le proprietà prima e quindi tutti gli elementi, in passaggi separati. Le modifiche apportate alle proprietà e agli elementi all'interno di una destinazione possono essere osservate dopo la destinazione in cui sono state modificate.

Ordine di compilazione per la destinazione

In un singolo progetto, gli obiettivi vengono eseguiti in modo seriale. Il problema centrale è come determinare l'ordine di compilazione di tutti gli elementi in modo che le dipendenze vengano usate per costruire gli obiettivi nell'ordine giusto.

L'ordine di compilazione di destinazione è determinato dall'uso degli attributi BeforeTargets, DependsOnTargetse AfterTargets in ogni destinazione. L'ordine delle destinazioni successive può essere influenzato durante l'esecuzione di una destinazione precedente se la destinazione precedente modifica una proprietà a cui viene fatto riferimento in questi attributi.

Le regole per determinare l'ordine di costruzione di destinazione sono descritte in Determinare l'ordine di costruzione di destinazione. Il processo è determinato da una struttura dello stack contenente le destinazioni da compilare. La destinazione all'inizio di questa attività avvia l'esecuzione e, se dipende da qualsiasi altro elemento, tali destinazioni vengono spostate nella parte superiore dello stack e iniziano l'esecuzione. Quando c'è un obiettivo senza dipendenze, viene eseguito fino al completamento e successivamente riprende l'obiettivo padre.

Riferimenti al progetto

Esistono due percorsi di codice che MSBuild può accettare, quello normale, descritto qui e l'opzione del grafo descritta nella sezione successiva.

I singoli progetti specificano la dipendenza da altri progetti tramite ProjectReference elementi. Quando un progetto nella parte superiore dello stack inizia la compilazione, raggiunge il punto in cui viene eseguita la destinazione ResolveProjectReferences, una destinazione standard definita nei file di destinazione comuni.

ResolveProjectReferences richiama l'attività MSBuild con gli elementi di input ProjectReference per generare gli output. Gli elementi ProjectReference vengono trasformati in elementi locali, ad esempio Reference. La fase di esecuzione di MSBuild per il progetto corrente viene sospesa mentre la fase di esecuzione inizia a elaborare il progetto a cui si fa riferimento (la fase di valutazione viene eseguita prima in base alle esigenze). Il progetto a cui si fa riferimento viene compilato solo dopo aver avviato la compilazione del progetto dipendente e quindi crea un albero di compilazione dei progetti.

Visual Studio consente di creare dipendenze di progetto nei file della soluzione (.sln). Le dipendenze vengono specificate nel file della soluzione e vengono rispettate solo durante la compilazione di una soluzione o durante la compilazione all'interno di Visual Studio. Se si compila un singolo progetto, questo tipo di dipendenza viene ignorato. I riferimenti alla soluzione vengono trasformati da MSBuild in ProjectReference elementi e successivamente vengono trattati nello stesso modo.

Opzione Graph

Se si specifica l'opzione di compilazione del grafo (-graphBuild o -graph), il ProjectReference diventa un concetto di prima classe usato da MSBuild. MSBuild analizzerà tutti i progetti e creerà il grafico dell'ordine di compilazione, un grafico delle dipendenze effettivo dei progetti, che viene quindi attraversato per determinare l'ordine di compilazione. Come per gli obiettivi nei singoli progetti, MSBuild garantisce che i progetti a cui si fa riferimento siano costruiti dopo i progetti dai quali dipendono.

Esecuzione parallela

Se si usa il supporto multiprocessore (-maxCpuCount o -m switch), MSBuild crea nodi, ovvero processi MSBuild che usano i core CPU disponibili. Ogni progetto viene inviato a un nodo disponibile. All'interno di un nodo, le compilazioni di singoli progetti eseguono in modo seriale.

Le attività possono essere abilitate per l'esecuzione parallela impostando una variabile booleana BuildInParallel, impostata in base al valore della proprietà $(BuildInParallel) in MSBuild. Per le attività abilitate per l'esecuzione parallela, un'utilità di pianificazione del lavoro gestisce i nodi e assegna il lavoro ai nodi.

Vedere Compilazione di più progetti in parallelo con MSBuild

Importazioni standard

Microsoft.Common.props e Microsoft.Common.targets vengono entrambi importati dai file di progetto .NET (in modo esplicito o implicito nei progetti in stile SDK) e si trovano nella cartella MSBuild\Current\bin in un'installazione di Visual Studio. I progetti C++ hanno una propria gerarchia di importazioni; vedere MSBuild Internals for C++ projects.

Le impostazioni predefinite del file Microsoft.Common.props possono essere sovrascritte. Viene importato (in modo esplicito o implicito) all'inizio di un file di progetto. In questo modo, le impostazioni del progetto vengono visualizzate dopo le impostazioni predefinite, per poterle sovrascrivere.

Il file Microsoft.Common.targets e i file di destinazione importati definiscono il processo di compilazione standard per i progetti .NET. Fornisce anche punti di estensione che è possibile usare per personalizzare la compilazione.

Durante l'implementazione, Microsoft.Common.targets è un wrapper sottile che importa Microsoft.Common.CurrentVersion.targets. Questo file contiene le impostazioni per le proprietà standard e definisce le destinazioni effettive che definiscono il processo di compilazione. La destinazione Build è definita qui, ma è in realtà essa stessa vuota. Tuttavia, la destinazione Build contiene l'attributo DependsOnTargets che specifica le singole destinazioni che costituiscono i passaggi di compilazione effettivi, che sono BeforeBuild, CoreBuilde AfterBuild. La destinazione Build è definita nei seguenti termini.

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

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

BeforeBuild e AfterBuild sono punti di estensione. Sono vuoti nel file Microsoft.Common.CurrentVersion.targets, ma i progetti possono fornire destinazioni BeforeBuild e AfterBuild con attività da eseguire prima o dopo il processo di compilazione principale. AfterBuild viene eseguito prima della destinazione no-op, Build, perché AfterBuild appare nell'attributo DependsOnTargets sul target Build, ma viene applicato dopo CoreBuild.

La destinazione CoreBuild contiene le chiamate agli strumenti di compilazione, come indicato di seguito:

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

La tabella seguente descrive queste destinazioni; alcune destinazioni sono applicabili solo a determinati tipi di progetto.

Target Descrizione
BuildOnlySettings Impostazioni solo per le compilazioni reali, non per quando MSBuild viene richiamato al caricamento del progetto da Visual Studio.
PrepareForBuild Preparare i prerequisiti per la compilazione
PreBuildEvent Punto di estensione per i progetti per definire le attività da eseguire prima della compilazione
ResolveProjectReferences Analizzare le dipendenze del progetto e compilare progetti a cui si fa riferimento
ResolveAssemblyReferences Individuare gli assembly a cui si fa riferimento.
ResolveReferences Consiste in ResolveProjectReferences e ResolveAssemblyReferences per trovare tutte le dipendenze
PrepareResources Elaborare i file di risorse
ResolveKeySource Risolvere la chiave di nome forte usata per firmare l'assembly e il certificato usato per firmare i manifesti ClickOnce .
Compilare Richiama il compilatore
ExportWindowsMDFile Generare un file winMD dai file WinMDModule generati dal compilatore.
DisiscrizioneNonGestita Rimuovere e pulire le voci del registro di sistema interoperabilità COM da una versione precedente
Generazione di Assembly di Serializzazione Generare un assembly di serializzazione XML usando sgen.exe.
CreateSatelliteAssemblies Creare un assembly satellite per ogni cultura univoca nelle risorse.
Generare manifesti Genera ClickOnce manifesti dell'applicazione e della distribuzione o un manifesto nativo.
GetTargetPath Restituisce un elemento contenente il prodotto di compilazione (eseguibile o assembly) per questo progetto, con metadati.
PrepareForRun Copiare gli output di compilazione nella directory finale se sono stati modificati.
RegistrazioneNonGestita Impostare le voci del Registro di sistema per l'interoperabilità COM di
IncrementalClean Rimuovere i file prodotti in una build precedente ma che non sono stati prodotti nella build corrente. Ciò è necessario per rendere Clean funzionare nelle compilazioni incrementali.
Evento Post-Costruzione Punto di estensione per i progetti per definire le attività da eseguire dopo la compilazione

Molte delle destinazioni della tabella precedente sono disponibili nelle importazioni specifiche del linguaggio, ad esempio Microsoft.CSharp.targets. Questo file definisce i passaggi del processo di compilazione standard specifico per i progetti .NET C#. Ad esempio, contiene la destinazione Compile che chiama effettivamente il compilatore C#.

Importazioni configurabili dall'utente

Oltre alle importazioni standard, sono disponibili diverse importazioni che è possibile aggiungere per personalizzare il processo di compilazione.

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

Questi file vengono letti dalle importazioni standard per qualsiasi progetto in qualsiasi sottocartella. Questo è in genere a livello di soluzione per le impostazioni per controllare tutti i progetti nella soluzione, ma potrebbe anche essere superiore nel file system, fino alla radice dell'unità.

Il file directory.build.props viene importato da Microsoft.Common.props, in modo che le proprietà definite siano disponibili nel file di progetto. Possono essere ridefiniti nel file di progetto per personalizzare i valori in base al progetto. Il file Directory.Build.targets viene letto dopo il file di progetto. In genere contiene destinazioni, ma qui è anche possibile definire proprietà che non si desidera che i singoli progetti ridefiniscano.

Personalizzazioni in un file di progetto

Visual Studio aggiorna i file di progetto quando si apportano modifiche in Esplora soluzioni , nella finestra Proprietà o in Proprietà progetto, ma è anche possibile apportare modifiche personalizzate modificando direttamente il file di progetto.

Molti comportamenti di compilazione possono essere configurati impostando le proprietà di MSBuild, nel file di progetto per le impostazioni locali di un progetto o, come indicato nella sezione precedente, creando un file Directory.Build.props per impostare le proprietà a livello globale per intere cartelle di progetti e soluzioni. Per le compilazioni ad hoc sulla riga di comando o sugli script, è anche possibile usare l'opzione /p nella riga di comando per impostare le proprietà per una chiamata specifica di MSBuild. Vedere proprietà comuni del progetto MSBuild per informazioni sulle proprietà che è possibile impostare.