Condividi tramite


Estendere il processo di compilazione di Visual Studio

Il processo di compilazione di Visual Studio è definito da una serie di file MSBuild .targets importati nel file di progetto. Queste importazioni sono implicite, se si usa un SDK come progetti di Visual Studio in genere. Uno di questi file importati, Microsoft.Common.targets, può essere esteso per consentire l'esecuzione di attività personalizzate in diversi punti del processo di compilazione. Questo articolo illustra tre metodi che è possibile usare per estendere il processo di compilazione di Visual Studio:

  • Creare una destinazione personalizzata e specificare quando deve essere eseguita usando BeforeTargets gli attributi e AfterTargets .

  • Eseguire l'override delle DependsOn proprietà definite nelle destinazioni comuni.

  • Eseguire l'override di destinazioni predefinite specifiche definite nelle destinazioni comuni (Microsoft.Common.targets o i file importati).

AfterTargets e BeforeTargets

È possibile usare AfterTargets gli attributi e BeforeTargets nella destinazione personalizzata per specificare quando deve essere eseguito.

Nell'esempio seguente viene illustrato come usare l'attributo AfterTargets per aggiungere una destinazione personalizzata che esegue operazioni con i file di output. In questo caso, copia i file di output in una nuova cartella CustomOutput. Nell'esempio viene inoltre illustrato come pulire i file creati dall'operazione di compilazione personalizzata con una CustomClean destinazione usando un BeforeTargets attributo e specificando che l'operazione pulita personalizzata viene eseguita prima della CoreClean destinazione.

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

Avviso

Assicurarsi di usare nomi diversi rispetto alle destinazioni predefinite (ad esempio, la destinazione di compilazione personalizzata qui è CustomAfterBuild, non AfterBuild), poiché tali destinazioni predefinite vengono sostituite dall'importazione dell'SDK che le definisce anche. Fare riferimento alla tabella alla fine di questo articolo per un elenco di destinazioni predefinite.

Estendere le proprietà DependsOn

Un altro modo per estendere il processo di compilazione consiste nell'usare le DependsOn proprietà ,ad esempio BuildDependsOn, per specificare le destinazioni che devono essere eseguite prima di una destinazione standard.

Questo metodo è preferibile eseguire l'override delle destinazioni predefinite, descritte nella sezione successiva. L'override delle destinazioni predefinite è un metodo meno recente ancora supportato, ma poiché MSBuild valuta la definizione di destinazioni in sequenza, non è possibile impedire a un altro progetto che importa il progetto di eseguire l'override delle destinazioni già sottoposte a override. Di conseguenza, ad esempio, l'ultima destinazione AfterBuild definita nel file di progetto, al termine dell'importazione di tutti gli altri progetti, sarà quella usata durante la compilazione.

È possibile proteggersi dagli override imprevisti delle destinazioni eseguendo l'override delle DependsOn proprietà usate negli DependsOnTargets attributi in tutte le destinazioni comuni. Nella destinazione Build, ad esempio, il valore dell'attributo DependsOnTargets è "$(BuildDependsOn)". Tenere in considerazione:

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

Questa parte di codice XML indica che la destinazione Build può essere eseguita solo dopo l'esecuzione di tutte le destinazioni specificate nella proprietà BuildDependsOn. La proprietà BuildDependsOn è definita come segue:

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

È possibile eseguire l'override di questo valore di proprietà dichiarando un'altra proprietà denominata BuildDependsOn alla fine del file di progetto. In un progetto in stile SDK, questo significa che è necessario usare importazioni esplicite. Vedere Importazioni implicite ed esplicite, in modo da poter inserire la DependsOn proprietà dopo l'ultima importazione. L'inclusione della proprietà BuildDependsOn precedente nella nuova proprietà consente di aggiungere nuove destinazioni all'inizio e alla fine dell'elenco di destinazioni. Ad esempio:

<PropertyGroup>
    <BuildDependsOn>
        MyCustomTarget1;
        $(BuildDependsOn);
        MyCustomTarget2
    </BuildDependsOn>
</PropertyGroup>

<Target Name="MyCustomTarget1">
    <Message Text="Running MyCustomTarget1..."/>
</Target>
<Target Name="MyCustomTarget2">
    <Message Text="Running MyCustomTarget2..."/>
</Target>

I progetti che importano il file di progetto possono estendere ulteriormente queste proprietà senza sovrascrivere le personalizzazioni apportate.

Per eseguire l'override di una proprietà DependsOn

  1. Identificare una proprietà predefinita DependsOn nelle destinazioni comuni di cui si vuole eseguire l'override. Per un elenco delle proprietà comunemente sottoposte DependsOn a override, vedere la tabella seguente.

  2. Definire un'altra istanza della proprietà o delle proprietà alla fine del file di progetto. Nella nuova proprietà includere la proprietà originale, ad esempio $(BuildDependsOn).

  3. Definire le destinazioni personalizzate prima o dopo la definizione della proprietà.

  4. Compilare il file di progetto.

Proprietà DependsOn comunemente sottoposte a override

Nome della proprietà Le destinazioni aggiunte vengono eseguite prima di questo punto:
BuildDependsOn Punto di ingresso della build principale. Eseguire l'override di questa proprietà se si desidera inserire destinazioni personalizzate prima o dopo l'intero processo di compilazione.
RebuildDependsOn Oggetto Rebuild
RunDependsOn Esecuzione dell'output di compilazione finale (se si tratta di un .EXE)
CompileDependsOn Compilazione (Compile destinazione). Eseguire l'override di questa proprietà se si desidera inserire processi personalizzati prima o dopo il passaggio di compilazione.
CreateSatelliteAssembliesDependsOn Creazione degli assembly satellite
CleanDependsOn Destinazione Clean (eliminazione di tutti gli output di compilazione intermedi e finali). Eseguire l'override di questa proprietà se si vuole pulire l'output dal processo di compilazione personalizzato.
PostBuildEventDependsOn Destinazione PostBuildEvent
PublishBuildDependsOn Pubblicazione della compilazione
ResolveAssemblyReferencesDependsOn Destinazione ResolveAssemblyReferences (individuazione della chiusura transitiva delle dipendenze per una determinata dipendenza). Vedere ResolveAssemblyReference.

Esempio: BuildDependsOn e CleanDependsOn

L'esempio seguente è simile all'esempio BeforeTargets e AfterTargets , ma mostra come ottenere funzionalità simili. Estende la compilazione usando BuildDependsOn per aggiungere un'attività CustomAfterBuild personalizzata che copia i file di output dopo la compilazione e aggiunge anche l'attività corrispondente CustomClean usando CleanDependsOn.

In questo esempio si tratta di un progetto di tipo SDK. Come accennato nella nota sui progetti in stile SDK in precedenza in questo articolo, è necessario usare il metodo di importazione manuale anziché l'attributo Sdk usato da Visual Studio quando genera i file di progetto.

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

L'ordine degli elementi è importante. Gli BuildDependsOn elementi e CleanDependsOn devono essere visualizzati dopo l'importazione del file di destinazione dell'SDK standard.

Eseguire l'override di destinazioni predefinite

I file comuni .targets contengono un set di destinazioni vuote predefinite chiamate prima e dopo alcune delle principali destinazioni nel processo di compilazione. Ad esempio, MSBuild chiama la destinazione prima della BeforeBuild destinazione principale CoreBuild e della AfterBuild destinazione dopo la CoreBuild destinazione. Per impostazione predefinita, le destinazioni vuote nelle destinazioni comuni non eseguono alcuna operazione, ma è possibile eseguire l'override del comportamento predefinito definendo le destinazioni desiderate in un file di progetto. I metodi descritti in precedenza in questo articolo sono preferiti, ma si potrebbe riscontrare codice meno recente che usa questo metodo.

Se il progetto usa un SDK (ad esempio Microsoft.Net.Sdk), è necessario apportare una modifica da importazioni implicite a esplicite, come descritto in Importazioni esplicite e implicite.

Per eseguire l'override di una destinazione predefinita

  1. Se il progetto usa l'attributo Sdk , impostarlo sulla sintassi di importazione esplicita. Vedere Importazioni esplicite e implicite.

  2. Identificare una destinazione predefinita nelle destinazioni comuni di cui si vuole eseguire l'override. Vedere la tabella seguente per l'elenco completo delle destinazioni di cui è possibile eseguire l'override in modo sicuro.

  3. Definire la destinazione o le destinazioni alla fine del file di progetto, immediatamente prima del </Project> tag e dopo l'importazione esplicita dell'SDK. Ad esempio:

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

    Si noti che l'attributo nell'elemento Sdk di primo livello Project è stato rimosso.

  4. Compilare il file di progetto.

Tabella di destinazioni predefinite

La tabella seguente illustra tutte le destinazioni nelle destinazioni comuni di cui è possibile eseguire l'override.

Nome di destinazione Descrizione
BeforeCompile, AfterCompile Le attività inserite in una di queste destinazioni vengono eseguite prima o dopo il completamento della compilazione principale. La maggior parte delle personalizzazioni avviene in una di queste due destinazioni.
BeforeBuild, AfterBuild Le attività inserite in una di queste destinazioni vengono eseguite prima o dopo qualsiasi altra attività nella compilazione. Nota: le destinazioni BeforeBuild e AfterBuild sono già definite nei commenti alla fine della maggior parte dei file di progetto, consentendo di aggiungere facilmente eventi di pre e post-compilazione nel file di progetto.
BeforeRebuild, AfterRebuild Le attività inserite in una di queste destinazioni vengono eseguite prima o dopo il richiamo della funzionalità di base per la ricompilazione. L'ordine di esecuzione delle destinazioni in Microsoft.Common.targets è: BeforeRebuild, Clean, Build e quindi AfterRebuild.
BeforeClean, AfterClean Le attività inserite in una di queste destinazioni vengono eseguite prima o dopo il richiamo della funzionalità di base per la pulitura.
BeforePublish, AfterPublish Le attività inserite in una di queste destinazioni vengono eseguite prima o dopo il richiamo della funzionalità di base per la pubblicazione.
BeforeResolveReferences, AfterResolveReferences Le attività inserite in una di queste destinazioni vengono eseguite prima o dopo la risoluzione dei riferimenti all'assembly.
BeforeResGen, AfterResGen Le attività inserite in una di queste destinazioni vengono eseguite prima o dopo la generazione delle risorse.

Esistono molte altre destinazioni nel sistema di compilazione e .NET SDK, vedere Destinazioni MSBuild - SDK e destinazioni di compilazione predefinite.

Procedure consigliate per destinazioni personalizzate

Le proprietà DependsOnTargets e BeforeTargets possono specificare che una destinazione deve essere eseguita prima di un'altra destinazione, ma sono entrambi necessari in scenari diversi. Si differenziano per specificare il requisito di dipendenza. Si ha solo il controllo sulle destinazioni personalizzate e non è possibile modificare in modo sicuro le destinazioni di sistema o altre destinazioni importate, in modo che vincoli la scelta dei metodi.

Quando si crea una destinazione personalizzata, seguire queste linee guida generali per assicurarsi che la destinazione venga eseguita nell'ordine previsto.

  1. Usare l'attributo DependsOnTargets per specificare le destinazioni che è necessario eseguire prima dell'esecuzione della destinazione. Per una catena di destinazioni controllate, ogni destinazione può specificare il membro precedente della catena in DependsOnTargets.

  2. Usare BeforeTargets per qualsiasi destinazione che non è possibile controllare prima di eseguire , ad esempio BeforeTargets="PrepareForBuild" per una destinazione che deve essere eseguita all'inizio della compilazione.

  3. Usare AfterTargets per qualsiasi destinazione che non si controlla che garantisce che gli output necessari siano disponibili. Ad esempio, specificare AfterTargets="ResolveReferences" per un elemento che modificherà un elenco di riferimenti.

  4. È possibile usarli in combinazione. Ad esempio: DependsOnTargets="GenerateAssemblyInfo" BeforeTargets="BeforeCompile".

Importazioni esplicite e implicite

I progetti generati da Visual Studio usano in genere l'attributo Sdk nell'elemento del progetto. Questi tipi di progetti sono denominati progetti in stile SDK. Vedere Usare GLI SDK del progetto MSBuild. Ecco un esempio:

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

Quando il progetto usa l'attributo Sdk , due importazioni vengono aggiunte in modo implicito, una all'inizio del file di progetto e una alla fine.

Le importazioni implicite sono equivalenti alla presenza di un'istruzione import simile alla seguente come prima riga nel file di progetto, dopo l'elemento Project :

<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />

e l'istruzione import seguente come ultima riga nel file di progetto:

<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />

Questa sintassi viene definita importazione esplicita dell'SDK. Quando si usa questa sintassi esplicita, è necessario omettere l'attributo Sdk nell'elemento del progetto.

L'importazione implicita dell'SDK equivale all'importazione dei file o "comuni" .props .targets specifici che sono un costrutto tipico nei file di progetto meno recenti, ad esempio:

<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />

e

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

Eventuali riferimenti precedenti devono essere sostituiti con la sintassi esplicita dell'SDK illustrata in precedenza in questa sezione.

Usando la sintassi esplicita dell'SDK è possibile aggiungere codice personalizzato prima della prima importazione o dopo l'importazione finale dell'SDK. Ciò significa che è possibile modificare il comportamento impostando le proprietà prima della prima importazione che avrà effetto nel file importato .props ed è possibile eseguire l'override di una destinazione definita in uno dei file SDK .targets dopo l'importazione finale. Usando questo metodo, è possibile eseguire l'override BeforeBuild o AfterBuild come illustrato di seguito.

Passaggi successivi

È possibile eseguire molte altre operazioni con MSBuild per personalizzare la compilazione. Vedere Personalizzare la compilazione.