Personalizzare la compilazione per cartella

È possibile aggiungere determinati file da importare da MSBuild per eseguire l'override delle impostazioni predefinite delle proprietà e aggiungere destinazioni personalizzate. L'ambito di queste personalizzazioni può essere controllato a livello di cartella da dove vengono inseriti questi file.

Questo articolo illustra le personalizzazioni applicabili agli scenari seguenti:

  • Personalizzare le impostazioni di compilazione per molti progetti in una soluzione
  • Personalizzare le impostazioni di compilazione per molte soluzioni in una directory di file comune
  • Personalizzare le impostazioni di compilazione che possono essere diverse per le sottocartelle in una struttura complessa di cartelle
  • Eseguire l'override delle impostazioni predefinite, delle cartelle di compilazione predefinite e di altri comportamenti impostati da un SDK, ad esempio Microsoft.Net.Sdk
  • Aggiungere o personalizzare le destinazioni di compilazione applicabili a un numero qualsiasi di progetti o soluzioni

Se si usano progetti C++, è anche possibile usare i metodi descritti in Personalizzare le compilazioni C++.

Directory.Build.props e Directory.Build.targets

È possibile aggiungere una nuova proprietà a ogni progetto definendola in un singolo file denominato Directory.Build.props nella cartella radice che contiene l'origine.

Durante l'esecuzione di MSBuild, Microsoft.Common.props cerca il file Directory.Build.props nella struttura di directory. Se ne trova uno, importa il file e legge le proprietà definite al suo interno. Directory.Build.props è un file definito dall'utente che specifica le personalizzazioni dei progetti in una directory.

Analogamente, Microsoft.Common.targets cerca Directory.Build.targets.

Directory.Build.props viene importato nelle prime fasi della sequenza di file importati, che può essere importante se è necessario impostare una proprietà usata dalle importazioni, in particolare quelle importate in modo implicito usando l'attributo Sdk , ad esempio quando si usa .NET SDK nella maggior parte dei file di progetto .NET.

Nota

I file system basati su Linux fanno distinzione tra maiuscole e minuscole. Assicurarsi che la combinazione di maiuscole e minuscole del nome file Directory.Build.props corrisponda esattamente o che non venga rilevata durante il processo di compilazione.

Per altre informazioni, vedere questo problema in GitHub.

Esempio di Directory.Build.props

Ad esempio, per consentire a tutti i progetti di accedere alla nuova funzionalità di Roslyn /deterministic (esposta nella destinazione CoreCompile di Roslyn dalla proprietà $(Deterministic)), è possibile eseguire le operazioni indicate di seguito.

  1. Creare un nuovo file nella radice del repository denominato Directory.Build.props.

  2. Aggiungere al file il seguente codice XML.

    <Project>
     <PropertyGroup>
       <Deterministic>true</Deterministic>
     </PropertyGroup>
    </Project>
    
  3. Eseguire MSBuild. Le importazioni esistenti del progetto di Microsoft.Common.props e Microsoft.Common.targets trovano il file e lo importano.

Ambito di ricerca

Quando si cerca un file Directory.Build.props , MSBuild guida la struttura di directory verso l'alto dal percorso $(MSBuildProjectFullPath)del progetto, arrestandosi dopo aver individuato un file Directory.Build.props . Ad esempio, se $(MSBuildProjectFullPath) è c:\users\nomeutente\code\test\case1, MSBuild inizia a cercare da quel punto ed esegue la ricerca nella struttura di directory verso l'alto finché non trova un file Directory.Build.props, come nella struttura di directory riportata di seguito.

c:\users\username\code\test\case1
c:\users\username\code\test
c:\users\username\code
c:\users\username
c:\users
c:\

La posizione del file della soluzione è irrilevante per Directory.Build.props.

Ordine di importazione

Directory.Build.props viene importato nelle prime fasi di Microsoft.Common.props e le proprietà definite in un secondo momento non sono disponibili. Evitare quindi di fare riferimento a proprietà non ancora definite (e restituiranno vuote).

Le proprietà impostate in Directory.Build.props possono essere sostituite altrove nel file di progetto o nei file importati, pertanto è consigliabile considerare le impostazioni in Directory.Build.props come specificare le impostazioni predefinite per i progetti.

Directory.Build.targets viene importato da Microsoft.Common.targets dopo l'importazione di .targets file da pacchetti NuGet. Può quindi eseguire l'override di proprietà e destinazioni definite nella maggior parte della logica di compilazione o impostare proprietà per tutti i progetti indipendentemente dal set dei singoli progetti.

Quando è necessario impostare una proprietà o definire una destinazione per un singolo progetto che esegue l'override di tutte le impostazioni precedenti, inserire tale logica nel file di progetto dopo l'importazione finale. Per eseguire questa operazione in un progetto in stile SDK, è prima necessario sostituire l'attributo in stile SDK con le importazioni equivalenti. Vedere Come usare gli SDK del progetto MSBuild.

Nota

Il motore MSBuild legge in tutti i file importati durante la valutazione, prima di avviare l'esecuzione della compilazione per un progetto (incluso qualsiasi PreBuildEvent), quindi questi file non devono essere modificati da o da PreBuildEvent qualsiasi altra parte del processo di compilazione. Tutte le modifiche non vengono applicate fino alla chiamata successiva di MSBuild.exe o alla successiva compilazione di Visual Studio. Inoltre, se il processo di compilazione contiene molte compilazioni di progetto (come con il multitargeting o la compilazione di progetti dipendenti), i file importati, tra cui Directory.build.props, vengono letti quando viene eseguita la valutazione per ogni singola compilazione di progetto.

Caso d'uso: unione a più livelli

Si supponga di avere questa struttura della soluzione standard:

\
  MySolution.sln
  Directory.Build.props     (1)
  \src
    Directory.Build.props   (2-src)
    \Project1
    \Project2
  \test
    Directory.Build.props   (2-test)
    \Project1Tests
    \Project2Tests

Potrebbe essere preferibile usare proprietà comuni per tutti i progetti (1), proprietà comuni per i progetti src(2-src) e proprietà comuni per i progetti test(2-test).

Per consentire a MSBuild di unire correttamente i file "interni"(2-src e 2-test) e il file "esterno" (1), è necessario tenere presente che quando MSBuild trova un file Directory.Build.props, interrompe l'analisi. Per continuare l'analisi e completare l'unione con il file esterno, inserire questo codice in entrambi i file interni:

<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />

L'approccio generale di MSBuild può essere riepilogato come segue:

  • Per un determinato progetto, MSBuild trova il primo oggetto Directory.Build.props verso l'alto nella struttura della soluzione, lo unisce con le impostazioni predefinite e interrompe l'analisi per ulteriori informazioni.
  • Se si desidera trovare e unire più livelli, quindi <Import...> (illustrato in precedenza) il file "esterno" dal file "interno".
  • Se il file "esterno" non importa anche qualcosa sopra di esso, l'analisi si interrompe.

O più semplicemente: il primo file Directory.Build.props che non esegue alcuna importazione è il punto in cui MSBuild si arresta.

Per controllare il processo di importazione in modo più esplicito, usare le proprietà $(DirectoryBuildPropsPath), $(ImportDirectoryBuildProps), $(DirectoryBuildTargetsPath)e $(ImportDirectoryBuildTargets). La proprietà $(DirectoryBuildPropsPath) specifica il percorso del Directory.Build.props file da utilizzare. In modo analogo, $(DirectoryBuildTargetsPath) specifica il percorso del Directory.Build.targets file.

Le proprietà $(ImportDirectoryBuildProps) booleane e $(ImportDirectoryBuildTargets) sono impostate su true per impostazione predefinita, pertanto MSBuild cerca normalmente questi file, ma è possibile impostarli per false impedire a MSBuild di importarli.

Esempio

In questo esempio viene illustrato l'utilizzo dell'output pre-elaborato per determinare dove impostare una proprietà.

Per analizzare l'utilizzo di una determinata proprietà da impostare, è possibile eseguire MSBuild con l'argomento /preprocess o /pp . Il testo di output è il risultato di tutte le importazioni, incluse le importazioni di sistema come Microsoft.Common.props importate in modo implicito e qualsiasi importazione personalizzata. Con questo output è possibile vedere dove deve essere impostata la proprietà in relazione alla posizione in cui viene usato il relativo valore.

Si supponga, ad esempio, di avere un semplice progetto app console .NET Core o .NET 5 o versione successiva e di voler personalizzare la cartella di output intermedia, in genere obj. La proprietà che specifica questo percorso è BaseIntermediateOutput. Se si tenta di inserirlo in un PropertyGroup elemento nel file di progetto insieme alle varie altre proprietà già impostate, ad esempio TargetFramework, si scoprirà quando si compila il progetto che la proprietà non ha effetto. Se si esegue MSBuild con l'opzione /pp e si cerca l'output per BaseIntermediateOutputPath, è possibile vedere perché. In questo caso, BaseIntermediateOutput viene letto e usato in Microsoft.Common.props.

È presente un commento in Microsoft.Common.props che indica che la proprietà BaseIntermediateOutput deve essere impostata qui, prima che venga usata da un'altra proprietà, MSBuildProjectExtensionsPath. È anche possibile notare che, quando BaseIntermediateOutputPath è impostato inizialmente, è presente un controllo per un valore preesistente e, se non è definito, viene impostato su obj.

<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">obj\</BaseIntermediateOutputPath>

Quindi, questo posizionamento indica che per impostare questa proprietà, deve essere specificato in un punto precedente a questo. Poco prima di questo codice nell'output pre-elaborato, è possibile vedere che Directory.Build.props è stato importato, quindi è possibile impostarlo in modo che sia impostato BaseIntermediateOutputPath in anticipo per avere l'effetto desiderato.

L'output pre-elaborato abbreviato seguente mostra il risultato dell'inserimento dell'impostazione BaseIntermediateOutput in Directory.Build.props. I commenti all'inizio delle importazioni standard includono il nome file e in genere alcune informazioni utili sul motivo per cui il file viene importato.

<?xml version="1.0" encoding="IBM437"?>
<!--
============================================================================================================================================
c:\source\repos\ConsoleApp9\ConsoleApp9\ConsoleApp9.csproj
============================================================================================================================================
-->
<Project DefaultTargets="Build">
  <!--
============================================================================================================================================
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk">
  This import was added implicitly because the Project element's Sdk attribute specified "Microsoft.NET.Sdk".

C:\Program Files\dotnet\sdk\7.0.200-preview.22628.1\Sdks\Microsoft.NET.Sdk\Sdk\Sdk.props
============================================================================================================================================
-->
  <!--
***********************************************************************************************
Sdk.props

WARNING:  DO NOT MODIFY this file unless you are knowledgeable about MSBuild and have
          created a backup copy.  Incorrect changes to this file will make it
          impossible to load or build your projects from the command-line or the IDE.

Copyright (c) .NET Foundation. All rights reserved.
***********************************************************************************************
-->
  <PropertyGroup xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <!--
      Indicate to other targets that Microsoft.NET.Sdk is being used.

      This must be set here (as early as possible, before Microsoft.Common.props)
      so that everything that follows can depend on it.

      In particular, Directory.Build.props and nuget package props need to be able
      to use this flag and they are imported by Microsoft.Common.props.
    -->
    <UsingMicrosoftNETSdk>true</UsingMicrosoftNETSdk>
    <!--
      Indicate whether the set of SDK defaults that makes SDK style project concise are being used.
      For example: globbing, importing msbuild common targets.

      Similar to the property above, it must be set here.
    -->
    <UsingNETSdkDefaults>true</UsingNETSdkDefaults>
  </PropertyGroup>
  <PropertyGroup Condition="'$(MSBuildProjectFullPath)' == '$(ProjectToOverrideProjectExtensionsPath)'" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <MSBuildProjectExtensionsPath>$(ProjectExtensionsPathForSpecifiedProject)</MSBuildProjectExtensionsPath>
  </PropertyGroup>
  <!--<Import Project="$(AlternateCommonProps)" Condition="'$(AlternateCommonProps)' != ''" />-->
  <!--
============================================================================================================================================
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="'$(AlternateCommonProps)' == ''">

C:\Program Files\Microsoft Visual Studio\2022\Preview\MSBuild\Current\Microsoft.Common.props
============================================================================================================================================
-->
  <!--
***********************************************************************************************
Microsoft.Common.props

WARNING:  DO NOT MODIFY this file unless you are knowledgeable about MSBuild and have
          created a backup copy.  Incorrect changes to this file will make it
          impossible to load or build your projects from the command-line or the IDE.

Copyright (C) Microsoft Corporation. All rights reserved.
***********************************************************************************************
-->
  <PropertyGroup>
    <ImportByWildcardBeforeMicrosoftCommonProps Condition="'$(ImportByWildcardBeforeMicrosoftCommonProps)' == ''">true</ImportByWildcardBeforeMicrosoftCommonProps>
    <ImportByWildcardAfterMicrosoftCommonProps Condition="'$(ImportByWildcardAfterMicrosoftCommonProps)' == ''">true</ImportByWildcardAfterMicrosoftCommonProps>
    <ImportUserLocationsByWildcardBeforeMicrosoftCommonProps Condition="'$(ImportUserLocationsByWildcardBeforeMicrosoftCommonProps)' == ''">true</ImportUserLocationsByWildcardBeforeMicrosoftCommonProps>
    <ImportUserLocationsByWildcardAfterMicrosoftCommonProps Condition="'$(ImportUserLocationsByWildcardAfterMicrosoftCommonProps)' == ''">true</ImportUserLocationsByWildcardAfterMicrosoftCommonProps>
    <ImportDirectoryBuildProps Condition="'$(ImportDirectoryBuildProps)' == ''">true</ImportDirectoryBuildProps>
  </PropertyGroup>
  <!--
      Determine the path to the directory build props file if the user did not disable $(ImportDirectoryBuildProps) and
      they did not already specify an absolute path to use via $(DirectoryBuildPropsPath)
  -->
  <PropertyGroup Condition="'$(ImportDirectoryBuildProps)' == 'true' and '$(DirectoryBuildPropsPath)' == ''">
    <_DirectoryBuildPropsFile Condition="'$(_DirectoryBuildPropsFile)' == ''">Directory.Build.props</_DirectoryBuildPropsFile>
    <_DirectoryBuildPropsBasePath Condition="'$(_DirectoryBuildPropsBasePath)' == ''">$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), '$(_DirectoryBuildPropsFile)'))</_DirectoryBuildPropsBasePath>
    <DirectoryBuildPropsPath Condition="'$(_DirectoryBuildPropsBasePath)' != '' and '$(_DirectoryBuildPropsFile)' != ''">$([System.IO.Path]::Combine('$(_DirectoryBuildPropsBasePath)', '$(_DirectoryBuildPropsFile)'))</DirectoryBuildPropsPath>
  </PropertyGroup>
  <!--
============================================================================================================================================
  <Import Project="$(DirectoryBuildPropsPath)" Condition="'$(ImportDirectoryBuildProps)' == 'true' and exists('$(DirectoryBuildPropsPath)')">

c:\source\repos\ConsoleApp9\Directory.Build.props
============================================================================================================================================
-->
  <!-- Directory.build.props
-->
  <PropertyGroup>
    <BaseIntermediateOutputPath>myBaseIntermediateOutputPath</BaseIntermediateOutputPath>
  </PropertyGroup>
  <!--
============================================================================================================================================
  </Import>

C:\Program Files\Microsoft Visual Studio\2022\Preview\MSBuild\Current\Microsoft.Common.props
============================================================================================================================================
-->
  <!--
      Prepare to import project extensions which usually come from packages.  Package management systems will create a file at:
        $(MSBuildProjectExtensionsPath)\$(MSBuildProjectFile).<SomethingUnique>.props

      Each package management system should use a unique moniker to avoid collisions.  It is a wild-card import so the package
      management system can write out multiple files but the order of the import is alphabetic because MSBuild sorts the list.
  -->
  <PropertyGroup>
    <!--
        The declaration of $(BaseIntermediateOutputPath) had to be moved up from Microsoft.Common.CurrentVersion.targets
        in order for the $(MSBuildProjectExtensionsPath) to use it as a default.
    -->
    <BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">obj\</BaseIntermediateOutputPath>
    <BaseIntermediateOutputPath Condition="!HasTrailingSlash('$(BaseIntermediateOutputPath)')">$(BaseIntermediateOutputPath)\</BaseIntermediateOutputPath>
    <_InitialBaseIntermediateOutputPath>$(BaseIntermediateOutputPath)</_InitialBaseIntermediateOutputPath>
    <MSBuildProjectExtensionsPath Condition="'$(MSBuildProjectExtensionsPath)' == '' ">$(BaseIntermediateOutputPath)</MSBuildProjectExtensionsPath>
    <!--
        Import paths that are relative default to be relative to the importing file.  However, since MSBuildExtensionsPath
        defaults to BaseIntermediateOutputPath we expect it to be relative to the project directory.  So if the path is relative
        it needs to be made absolute based on the project directory.
    -->
    <MSBuildProjectExtensionsPath Condition="'$([System.IO.Path]::IsPathRooted($(MSBuildProjectExtensionsPath)))' == 'false'">$([System.IO.Path]::Combine('$(MSBuildProjectDirectory)', '$(MSBuildProjectExtensionsPath)'))</MSBuildProjectExtensionsPath>
    <MSBuildProjectExtensionsPath Condition="!HasTrailingSlash('$(MSBuildProjectExtensionsPath)')">$(MSBuildProjectExtensionsPath)\</MSBuildProjectExtensionsPath>
    <ImportProjectExtensionProps Condition="'$(ImportProjectExtensionProps)' == ''">true</ImportProjectExtensionProps>
    <_InitialMSBuildProjectExtensionsPath Condition=" '$(ImportProjectExtensionProps)' == 'true' ">$(MSBuildProjectExtensionsPath)</_InitialMSBuildProjectExtensionsPath>
  </PropertyGroup>
  ...