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 di eseguire 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 l'attributo BeforeTargets e AfterTargets.

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

  • Sovrascrivere obiettivi predefiniti specifici definiti negli obiettivi comuni (Microsoft.Common.targets o nei file che importa).

AfterTargets e BeforeTargets

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

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>

Avvertimento

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 a eseguire l'override degli obiettivi predefiniti, descritti 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. Ad esempio, l'ultima AfterBuild destinazione definita nel file di progetto, dopo l'importazione di tutti gli altri progetti, sarà quella usata durante la compilazione.

È possibile proteggersi dagli override imprevisti dei target sostituendo le proprietà DependsOn usate negli attributi DependsOnTargets nei target comuni. Ad esempio, la Build destinazione contiene un DependsOnTargets valore di attributo pari a "$(BuildDependsOn)". Tenere in considerazione:

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

Questa parte di XML indica che prima che la Build destinazione possa essere eseguita, tutte le destinazioni specificate nella BuildDependsOn proprietà devono essere eseguite per prime. La BuildDependsOn proprietà è definita come:

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

È possibile eseguire l'override di questo valore della 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. Includendo la proprietà precedente BuildDependsOn nella nuova proprietà, è possibile aggiungere nuove destinazioni all'inizio e alla fine dell'elenco di destinazione. Per 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. Includere la proprietà originale, ad esempio $(BuildDependsOn), nella nuova proprietà .

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

  4. Compilare il file di progetto.

Proprietà DependsOn comunemente ridefinite

Nome della proprietà Gli obiettivi aggiunti vengono eseguiti prima di questo punto.
BuildDependsOn Punto di ingresso della build principale. Sovrascrivere questa proprietà se si desidera inserire obiettivi personalizzati prima o dopo l'intero processo di compilazione.
RebuildDependsOn Il 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 La creazione degli assemblaggi satellite
CleanDependsOn Obiettivo Clean (eliminazione di tutti gli output di compilazione intermedi e finali). Sostituire questa proprietà se si vuole pulire l'output dal processo di compilazione personalizzato.
PostBuildEventDependsOn Destinazione PostBuildEvent
PublishBuildDependsOn Pubblicazione della build
ResolveAssemblyReferencesDependsOn L'obiettivo ResolveAssemblyReferences (l'individuazione della chiusura transitiva delle dipendenze per una determinata dipendenza). Vedi 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.

Sovrascrivere obiettivi predefiniti

I file comuni .targets contengono un set di target vuoti predefiniti chiamati prima e dopo alcuni dei principali target nel processo di compilazione. Ad esempio, MSBuild chiama il bersaglio BeforeBuild prima del bersaglio principale CoreBuild e il bersaglio AfterBuild dopo il bersaglio CoreBuild. 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 modificare 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. Consultare la tabella seguente per l'elenco completo degli obiettivi che è possibile sovrascrivere 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. Per 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 Sdk sull'elemento Project di primo livello è stato rimosso.

  4. Compilare il file di progetto.

Tabella di destinazioni predefinite

La tabella seguente illustra tutti gli obiettivi negli obiettivi comuni che è possibile sovrascrivere.

Nome di destinazione Descrizione
BeforeCompile, AfterCompile Le attività inserite in una di queste destinazioni vengono eseguite prima o dopo l'esecuzione della compilazione principale. La maggior parte delle personalizzazioni viene eseguita in una di queste due destinazioni.
BeforeBuild, AfterBuild Le attività inserite in uno di questi target verranno eseguite prima o dopo tutto il resto del processo di build. Nota: Le BeforeBuild destinazioni e AfterBuild sono già definite nei commenti alla fine della maggior parte dei file di progetto, consentendo di aggiungere facilmente eventi di pre-compilazione e post-compilazione al file di progetto.
BeforeRebuild, AfterRebuild Le attività inserite in uno di questi obiettivi vengono eseguite prima o dopo l'invocazione della funzionalità di ricompilazione principale. L'ordine di esecuzione della destinazione in Microsoft.Common.targets è: BeforeRebuild, Clean, Builde quindi AfterRebuild.
BeforeClean, AfterClean Le attività inserite in una di queste destinazioni vengono eseguite prima o dopo che viene richiamata la funzionalità di pulizia principale.
BeforePublish, AfterPublish Le attività inserite in una di queste destinazioni vengono eseguite prima o dopo la chiamata della funzionalità di pubblicazione principale.
BeforeResolveReferences, AfterResolveReferences Le attività inserite in uno di questi target vengono eseguite prima o dopo la risoluzione dei riferimenti agli 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.

Migliori pratiche 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. Differiscono per il bersaglio a cui è specificato 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 i target necessari da eseguire prima dell'esecuzione del proprio target. 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. Utilizzare AfterTargets per qualsiasi destinazione che non si controlla e che garantisce che i risultati 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. Vedi Utilizzare 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 specifici file "comuni" .props o .targets che costituiscono un costrutto tipico nei file di progetto più datati, 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. Consulta Personalizza la tua configurazione.