Come vengono compilati i progetti in 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 compilazione completo è costituito dall'avvio iniziale, dalla valutazione e dall'esecuzione delle destinazioni e delle attività che compilano il progetto. Oltre a questi input, le importazioni esterne definiscono i dettagli del processo di compilazione, incluse le importazioni standard, ad esempio Microsoft.Common.targets e le importazioni configurabili dall'utente a livello di soluzione o progetto.
MSBuild può essere richiamato da Visual Studio tramite il modello a oggetti MSBuild in Microsoft.Build.dll oppure 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 tramite 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.
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 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.
Per informazioni su come estendere la compilazione della soluzione, vedere Personalizzare la compilazione della soluzione.
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 build
, o basate su modello a oggetti, non alle compilazioni di Visual Studio.
La funzionalità SDK per i file di progetto MSBuild è relativamente nuova. Prima di questa modifica, i file di progetto importano in modo esplicito i file con estensione targets e 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, gli SDK del progetto .NET Core e il riferimento alle proprietà.
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, e i file XML importati, generalmente denominati come file con estensione props o targets , a seconda che impostino principalmente proprietà o definiscino 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 gli elementi UsingTask
- Valutare le destinazioni
Le importazioni e le proprietà vengono valutate nella stessa sequenza di aspetto, come se le importazioni vengano 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.
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.
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. Al momento, le proprietà che non sono presenti nelle destinazioni 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 durante qualsiasi parte del passaggio delle proprietà.
In questa fase, le definizioni degli elementi vengono interpretate e viene creata una rappresentazione in memoria di queste definizioni.
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 vengono sottoposti a override dal set di metadati 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 è dopo il passaggio delle proprietà, gli elementi possono accedere a qualsiasi proprietà se definita all'esterno di qualsiasi destinazione, indipendentemente dal fatto che la definizione della proprietà venga visualizzata in un secondo momento.
In questa fase, gli elementi UsingTask vengono letti e le attività vengono dichiarate per un uso successivo durante la fase di esecuzione.
In questa fase, tutte le strutture degli oggetti di destinazione vengono create in memoria, in preparazione all'esecuzione. Non viene eseguita alcuna esecuzione effettiva.
Nella fase di esecuzione, le destinazioni vengono ordinate ed eseguite e tutte le attività vengono 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.
In un singolo progetto, le destinazioni vengono eseguite in modo seriale. Il problema centrale è come determinare l'ordine di compilazione di tutti gli elementi in in modo che le dipendenze vengano usate per compilare le destinazioni nell'ordine corretto.
L'ordine di compilazione di destinazione è determinato dall'uso degli BeforeTargets
attributi , DependsOnTargets
e 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 l'ordinamento sono descritte in Determinare l'ordine di compilazione 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 è presente una destinazione senza dipendenze, viene eseguita fino al completamento e la destinazione padre riprende.
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 ResolveProjectReferences
destinazione, una destinazione standard definita nei file di destinazione comuni.
ResolveProjectReferences
richiama l'attività MSBuild con input degli ProjectReference
elementi per ottenere gli output. Gli ProjectReference
elementi vengono trasformati in elementi locali, Reference
ad esempio . 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 viene creato un albero di progetti che compila.
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.
Se si specifica l'opzione di compilazione del grafo (-graphBuild
o -graph
), diventa ProjectReference
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 le destinazioni nei singoli progetti, MSBuild garantisce che i progetti a cui si fa riferimento vengano compilati dopo i progetti da cui dipendono.
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 BuildInParallelbooleana , impostata in base al valore della $(BuildInParallel)
proprietà 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
Microsoft.Common.props e Microsoft.Common.targets sono 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 Internals di MSBuild per i progetti C++.
Il file Microsoft.Common.props imposta le impostazioni predefinite di cui è possibile eseguire l'override. 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, in modo da eseguirne l'override.
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.
Nell'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 Build
destinazione è definita qui, ma in realtà è vuota. Tuttavia, la Build
destinazione contiene l'attributo DependsOnTargets
che specifica le singole destinazioni che costituiscono i passaggi di compilazione effettivi, ovvero BeforeBuild
, CoreBuild
e AfterBuild
. La Build
destinazione è definita come segue:
<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 le proprie destinazioni e AfterBuild
le attività BeforeBuild
che devono essere eseguite prima o dopo il processo di compilazione principale. AfterBuild
viene eseguito prima della destinazione no-op, , Build
perché AfterBuild
viene visualizzato nell'attributo DependsOnTargets
nella Build
destinazione, ma si verifica dopo CoreBuild
.
La CoreBuild
destinazione 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.
Destinazione | Descrizione |
---|---|
BuildOnly Impostazioni | 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 | ResolveProjectReferences È costituito da e ResolveAssemblyReferences per trovare tutte le dipendenze |
PrepareResources | Elaborare i file di risorse |
Resolvekeysource | Risolvere la chiave con nome sicuro usata per firmare l'assembly e il certificato usato per firmare i manifesti ClickOnce . |
Compile | Richiama il compilatore |
ExportWindowsMDFile | Generare un file WinMD dai file WinMDModule generati dal compilatore. |
UnmanagedUnregistration | Rimuovere/pulire le voci del Registro di sistema di interoperabilità COM da una compilazione precedente |
GenerateSerializationAssemblies | Generare un assembly di serializzazione XML usando sgen.exe. |
CreateSatelliteAssemblies | Creare un assembly satellite per ogni cultura univoca nelle risorse. |
Generare manifesti | Genera manifesti dell'applicazione ClickOnce 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. |
UnmanagedRegistration | Impostare le voci del Registro di sistema per l'interoperabilità COM |
IncrementalClean | Rimuovere i file prodotti in una build precedente ma che non sono stati prodotti nella build corrente. Questa operazione è necessaria per eseguire Clean operazioni nelle compilazioni incrementali. |
PostBuildEvent | Punto di estensione per i progetti per definire le attività da eseguire dopo la compilazione |
Molte delle destinazioni della tabella precedente si trovano in 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 Compile
destinazione che chiama effettivamente il compilatore C#.
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, pertanto le proprietà definite sono 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 ridefinire i singoli progetti.
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.
È possibile configurare molti comportamenti di compilazione 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. Per informazioni sulle proprietà che è possibile impostare, vedere Proprietà comuni del progetto MSBuild.
- Personalizzare la compilazione
- Come estendere il processo di compilazione di Visual Studio.
- MSBuild