Leggere in inglese

Condividi tramite


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
  • Aggiungi o personalizza obiettivi di compilazione applicabili a qualsiasi numero 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 nella struttura delle directory il file Directory.Build.props. Se ne trova uno, importa il file e legge le proprietà definite al suo interno. Directory.Build.props è un file definito dall'utente che fornisce personalizzazioni ai 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 sono sensibili alle maiuscole e minuscole. Assicurati che la combinazione di maiuscole e minuscole del nome file Directory.Build.props corrisponda esattamente, altrimenti non verrà rilevato durante il processo di compilazione.

Per ulteriori informazioni, consultare questo ticket di GitHub .

Esempio di Directory.Build.props

Ecco ad esempio un file directory.build.props che imposta la directory di output per tutti i progetti in una soluzione di Visual Studio. L'output di ogni progetto viene inserito sotto il proprio nome di progetto. In questo esempio, il file Directory.Build.props si trova in una cartella della soluzione, con molti progetti in sottocartelle al suo interno. La proprietà $(MSBuildProjectName) assegna il nome di ogni progetto. Poiché il file directory.build.props viene importato in ogni progetto durante la propria compilazione, viene valutato al valore corretto per ogni singolo progetto nella soluzione.

  1. Pulire la soluzione per rimuovere eventuali file di output precedenti.

    msbuild /t:Clean SolutionName.sln

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

  3. Aggiungere il codice XML seguente al file .

    XML
    <Project>
       <PropertyGroup>
          <OutDir>C:\output\$(MSBuildProjectName)</OutDir>
       </PropertyGroup>
    </Project>
    

    Nota

    La proprietà $(OutDir) è un percorso assoluto per l'output e il suo utilizzo consente di evitare la creazione di sottocartelle per configurazioni, framework di destinazione o runtime che vengono normalmente utilizzati nei progetti .NET. Prova a utilizzare la proprietà BaseOutputPath se desideri che le usuali sottocartelle siano create in un percorso di output personalizzato.

  4. Eseguire MSBuild. Le importazioni esistenti del tuo progetto di Microsoft.Common.props e Microsoft.Common.targets trovano il file Directory.Build.props e lo importano. La nuova cartella di output viene utilizzata per tutti i progetti che si trovano sotto quella cartella.

Ambito di ricerca

Quando si cerca un file Directory.Build.props, MSBuild scansiona la struttura di directory verso l'alto dal percorso della tua cartella di progetto $(MSBuildProjectFullPath), fermandosi dopo aver individuato un file Directory.Build.props. Ad esempio, se il $(MSBuildProjectFullPath) è stato c:\users\username\code\test\case1, MSBuild inizierà la ricerca in tale posizione e quindi eseguirà una ricerca verso l'alto fino a quando non trova un file Directory.Build.props, come nella struttura di directory seguente.

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

Il percorso del file di soluzione è irrilevante per Directory.Build.props.

Ordine di importazione

Directory.Build.props viene importato all'inizio di Microsoft.Common.propse le proprietà definite successivamente non sono disponibili. Evitare quindi di fare riferimento a proprietà non ancora definite (e verranno valutate come 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 specificando le impostazioni predefinite per i progetti.

Directory.Build.targets viene importato da Microsoft.Common.targets dopo l'importazione di file .targets da pacchetti NuGet. Può quindi sovrascrivere proprietà e destinazioni definite nella maggior parte della logica di compilazione o impostare proprietà per tutti i progetti indipendentemente da ciò che impostano i 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 tutti i file importati durante l'analisi e prima di avviare l'esecuzione della compilazione per un progetto (incluso qualsiasi PreBuildEvent), pertanto è previsto che questi file non vengano modificati dal PreBuildEvent o da nessun'altra parte del processo di compilazione. Eventuali 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, inclusi 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 di 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 consigliabile avere proprietà comuni per tutti i progetti (1), proprietà comuni per progetti progetti (2-src)e proprietà comuni per progetti di test(2 test).

Per fare in modo che MSBuild unisce correttamente i file "interni" ( 2-src e 2 test) con il file "esterno" (1), è necessario tenere conto che dopo che MSBuild trova un file Directory.Build.props, interrompe l'analisi. Per continuare l'analisi e l'unione nel file esterno, inserire questo codice in entrambi i file interni:

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

Di seguito è riportato un riepilogo dell'approccio generale di MSBuild:

  • Per un determinato progetto, MSBuild trova il primo Directory.Build.props a livello superiore nella gerarchia della soluzione, lo unisce con le impostazioni predefinite e interrompe la ricerca di ulteriori file.
  • Se si desidera trovare e unire più livelli, <Import...> (illustrato in precedenza) il file "esterno" dal file "interno".
  • Se il file "esterno" non importa a sua volta qualcosa che lo preceda, l'analisi si interrompe.

O più semplicemente: il primo Directory.Build.props che non importa nulla è dove 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 file Directory.Build.props da utilizzare; analogamente, $(DirectoryBuildTargetsPath) specifica il percorso del file Directory.Build.targets.

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

Esempio

In questo esempio viene illustrato l'uso 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à rispetto al punto in cui viene utilizzato il suo 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 elemento PropertyGroup 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.

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

Quindi, questa disposizione indica che per impostare questa proprietà, deve essere specificata in un punto precedente a questo. Poco prima di questo codice nell'output pre-elaborato, è possibile osservare che Directory.Build.props viene importato, in modo da poter impostare BaseIntermediateOutputPath lì e verrà impostato abbastanza presto 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
<?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>
  ...