Přizpůsobení sestavení podle složky

Můžete přidat určité soubory, které má nástroj MSBuild importovat, a přepsat výchozí nastavení vlastností a přidat vlastní cíle. Rozsah těchto přizpůsobení lze řídit na úrovni složky umístěním těchto souborů.

Tento článek se zabývá přizpůsobeními, která platí pro následující scénáře:

  • Přizpůsobení nastavení sestavení pro mnoho projektů v řešení
  • Přizpůsobení nastavení sestavení pro mnoho řešení v rámci společného adresáře souborů
  • Přizpůsobení nastavení sestavení, která se můžou lišit pro podsložky ve složité struktuře složek
  • Přepsání výchozích nastavení, výchozích složek sestavení a dalších chování nastavených sadou SDK, jako je například Microsoft.Net.Sdk
  • Přidání nebo přizpůsobení cílů sestavení, které platí pro libovolný počet projektů nebo řešení

Pokud pracujete s projekty C++, můžete také použít metody popsané v tématu Přizpůsobení sestavení C++.

Directory.Build.props a Directory.Build.targets

Do každého projektu můžete přidat novou vlastnost tak, že ji definujete v jednom souboru s názvem Directory.Build.props v kořenové složce, která obsahuje váš zdroj.

Při spuštění nástroje MSBuild microsoft.Common.props vyhledá adresářovou strukturu souboru Directory.Build.props . Pokud ho najde, naimportuje soubor a přečte vlastnosti definované v něm. Directory.Build.props je soubor definovaný uživatelem, který poskytuje vlastní nastavení projektů v adresáři.

Podobně Microsoft.Common.targets hledá directory.Build.targets.

Adresář.Build.props se naimportuje v rané fázi posloupnosti importovaných souborů, což může být důležité, pokud potřebujete nastavit vlastnost, kterou používají importy, zejména ty, které jsou implicitně importovány pomocí atributu Sdk , například při použití sady .NET SDK ve většině souborů projektu .NET.

Poznámka:

Systémy souborů založené na Linuxu rozlišují malá a velká písmena. Ujistěte se, že velikost výskytu souboru Directory.Build.props přesně odpovídá, nebo se během procesu sestavení nerozpozná.

Další informace najdete u tohoto problému na GitHubu.

Příklad Directory.Build.props

Pokud byste například chtěli povolit všem svým projektům přístup k nové funkci Roslyn /deterministic (která je zpřístupněna v cíli Roslyn CoreCompile vlastností $(Deterministic)), můžete provést následující kroky.

  1. V kořenovém adresáři úložiště vytvořte nový soubor s názvem Directory.Build.props.

  2. Do souboru přidejte následující kód XML.

    <Project>
     <PropertyGroup>
       <Deterministic>true</Deterministic>
     </PropertyGroup>
    </Project>
    
  3. Spusťte nástroj MSBuild. Existující importy microsoft.Common.props a Microsoft.Common.targets vašeho projektu najdou soubor a naimportuje ho.

Obor hledání

Při hledání souboru Directory.Build.props nástroj MSBuild provede adresářovou strukturu směrem nahoru z umístění $(MSBuildProjectFullPath)projektu a zastaví se po vyhledání souboru Directory.Build.props . Pokud jste $(MSBuildProjectFullPath) například c :\users\username\code\test\case1, nástroj MSBuild by tam začal hledat a pak prohledá adresářovou strukturu směrem nahoru, dokud nenaloží soubor Directory.Build.props , jako v následující adresářové struktuře.

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

Umístění souboru řešení je pro Directory.Build.props irelevantní.

Pořadí importu

Adresář.Build.props se naimportuje dříve v Microsoft.Common.props a vlastnosti definované později nejsou pro něj dostupné. Vyhněte se tedy odkazování na vlastnosti, které ještě nejsou definované (a vyhodnotí se jako prázdné).

Vlastnosti, které jsou nastaveny v Directory.Build.props lze přepsat jinde v souboru projektu nebo v importovaných souborech, takže byste měli myslet na nastavení v Directory.Build.props jako určení výchozích hodnot pro vaše projekty.

Adresář.Build.targets se naimportuje z Microsoft.Common.targets po importu .targets souborů z balíčků NuGet. Může tedy přepsat vlastnosti a cíle definované ve většině logiky sestavení nebo nastavit vlastnosti pro všechny projekty bez ohledu na to, co jednotlivé projekty nastaví.

Pokud potřebujete nastavit vlastnost nebo definovat cíl pro jednotlivé projekty, který přepíše všechna předchozí nastavení, vložte tuto logiku do souboru projektu po posledním importu. Aby to bylo možné provést v projektu stylu sady SDK, musíte nejprve nahradit atribut stylu sady SDK ekvivalentními importy. Viz Postup použití sad SDK projektu MSBuild.

Poznámka:

Modul MSBuild před zahájením spuštění sestavení pro projekt (včetně všech PreBuildEvent) čte ve všech importovaných souborech během vyhodnocení, takže se neočekává, že se tyto soubory upraví PreBuildEvent v rámci procesu sestavení ani jiné části procesu sestavení. Změny se neprojeví až do dalšího vyvolání MSBuild.exe nebo dalšího sestavení sady Visual Studio. Pokud proces sestavení obsahuje mnoho sestavení projektu (stejně jako u vícestranných nebo stavebních závislých projektů), importované soubory, včetně Directory.build.props, se při vyhodnocování pro každý jednotlivé sestavení projektu čtou.

Případ použití: sloučení na více úrovních

Předpokládejme, že máte tuto standardní strukturu řešení:

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

Může být žádoucí mít společné vlastnosti pro všechny projekty (1), společné vlastnosti projektů src (2-src) a společné vlastnosti pro projekty testů (2-test).

Pokud chcete, aby nástroj MSBuild správně sloučil "vnitřní" soubory (2-src a 2-test) s "vnějším" souborem (1), musíte vzít v úvahu, že jakmile NÁSTROJ MSBuild najde soubor Directory.Build.props, zastaví další kontrolu. Pokud chcete pokračovat ve skenování a slučování do vnějšího souboru, umístěte tento kód do obou vnitřních souborů:

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

Souhrn obecného přístupu nástroje MSBuild je následující:

  • U každého daného projektu nástroj MSBuild najde první adresář.Build.props vzhůru ve struktuře řešení, sloučí ho s výchozími nastaveními a přestane vyhledávat další.
  • Pokud chcete najít a sloučit více úrovní, pak <Import...> (zobrazený dříve) "vnější" soubor z "vnitřního" souboru.
  • Pokud se samotný "vnější" soubor nenaimportuje ani něco nad ním, kontrola se zastaví.

Nebo jednodušeji: první adresář.Build.props , který neimportuje nic, je místo, kde MSBuild zastaví.

Pokud chcete řídit proces importu explicitněji, použijte vlastnosti $(DirectoryBuildPropsPath), $(ImportDirectoryBuildProps), $(DirectoryBuildTargetsPath)a $(ImportDirectoryBuildTargets). Vlastnost $(DirectoryBuildPropsPath) určuje cestu k Directory.Build.props souboru, který se má použít. Podobně $(DirectoryBuildTargetsPath) určuje cestu k Directory.Build.targets souboru.

Logické vlastnosti $(ImportDirectoryBuildProps) a $(ImportDirectoryBuildTargets) jsou nastaveny na true výchozí, takže MSBuild obvykle vyhledává tyto soubory, ale můžete je nastavit tak, aby false se zabránilo MSBuild v jejich importu.

Příklad

Tento příklad ukazuje použití předzpracovaného výstupu k určení, kde nastavit vlastnost.

Chcete-li pomoct analyzovat použití konkrétní vlastnosti, kterou chcete nastavit, můžete spustit nástroj MSBuild s argumentem nebo /pp argumentem/preprocess. Výstupní text je výsledkem všech importů, včetně importů systému, jako je Microsoft.Common.props , které se implicitně importují, a všech vlastních importů. Pomocí tohoto výstupu můžete zjistit, kde je potřeba nastavit vlastnost vzhledem k tomu, kde se používá její hodnota.

Předpokládejme například, že máte jednoduchý projekt konzolové aplikace .NET Core nebo .NET 5 nebo novější a chcete přizpůsobit zprostředkující výstupní složku, obvykle obj. Vlastnost, která určuje tuto cestu je BaseIntermediateOutput. Pokud se pokusíte vložit tento PropertyGroup prvek do souboru projektu spolu s různými dalšími vlastnostmi, které jsou tam již nastavené, například TargetFramework, zjistíte, kdy vytvoříte projekt, že se tato vlastnost neprojeví. Pokud spustíte nástroj MSBuild s /pp možností a vyhledáte výstup BaseIntermediateOutputPath, můžete zjistit, proč. V tomto případě se BaseIntermediateOutput čte a používá v Microsoft.Common.props.

V Microsoft.Common.props je komentář, který říká, že vlastnost BaseIntermediateOutput musí být nastavena zde, než ji použije jiná vlastnost, MSBuildProjectExtensionsPath. Uvidíte také, že když BaseIntermediateOutputPath je původně nastavená, je kontrola předdefinované hodnoty a pokud není definovaná, nastaví se na objhodnotu .

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

Toto umístění vám tedy řekne, že chcete nastavit tuto vlastnost, musí být zadána někde dříve, než je tato. Těsně před tímto kódem v předzpracovaném výstupu uvidíte, že Directory.Build.props se importuje, takže tam můžete nastavit BaseIntermediateOutputPath , aby byl dostatečně brzo nastavený, aby se požadovaný efekt projevil.

Následující zkrácený předzpracovaný výstup ukazuje výsledek vložení BaseIntermediateOutput nastavení do Directory.Build.propssouboru . Komentáře v horní části standardních importů zahrnují název souboru a obvykle některé užitečné informace o tom, proč je soubor importován.

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