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ůsobit nastavení sestavení pro velké množství projektů v řešení
- Přizpůsobení nastavení pro sestavení mnoha řešení v rámci společného adresáře
- Přizpůsobte nastavení sestavení, která se mohou lišit ve složité struktuře složek pro jednotlivé podsložky.
- Přepsání výchozích nastavení, složek sestavení a jiných chování, které nastavuje sada SDK, jako například
Microsoft.Net.Sdk
- Přidat nebo přizpůsobit cíle 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 prohledá 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 přizpůsobení projektů v adresáři.
Podobně Microsoft.Common.targets vyhledává Directory.Build.targets.
Directory.Build.props se importuje dříve v 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 písmen názvu souboru Directory.Build.props přesně odpovídá, jinak nebude během procesu sestavení rozpoznán.
Příklad Directory.Build.props
Tady je například soubor Directory.Build.props, který nastaví výstupní adresář pro všechny projekty v řešení sady Visual Studio. Výstup každého projektu je umístěn pod vlastním názvem projektu. V tomto příkladu je soubor Directory.Build.props ve složce řešení s mnoha projekty v podsložkách. Vlastnost $(MSBuildProjectName)
dává název každého projektu. Protože se soubor Directory.Build.props naimportuje během sestavování každého projektu, je pro každý projekt v řešení vyhodnocena správná hodnota.
Vyčistíte řešení tak, aby se odebraly všechny staré výstupní soubory.
msbuild /t:Clean SolutionName.sln
V kořenovém adresáři úložiště vytvořte nový soubor s názvem Directory.Build.props.
Do souboru přidejte následující kód XML.
<Project> <PropertyGroup> <OutDir>C:\output\$(MSBuildProjectName)</OutDir> </PropertyGroup> </Project>
Poznámka
Vlastnost
$(OutDir)
je absolutní cesta k výstupu a jeho použití obchází vytváření podsložek pro konfiguraci, cílovou architekturu nebo modul runtime, které se obvykle používají v projektech .NET. Zkuste místo toho použít vlastnostBaseOutputPath
, pokud chcete, aby se v rámci vlastní výstupní cesty vytvořily obvyklé podsložky.Spusťte nástroj MSBuild. Existující importy Microsoft.Common.props a Microsoft.Common.targets najdou soubor Directory.Build.props a poté ho importují, a nová výstupní složka se použije pro všechny projekty v této složce.
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í projektu $(MSBuildProjectFullPath)
, zastaví se po vyhledání souboru Directory.Build.props. Pokud se například nacházel $(MSBuildProjectFullPath)
c:\users\username\code\test\case1, nástroj MSBuild by tam začal hledat a pak prohledával adresářovou strukturu směrem nahoru, dokud nenalezl soubor Directory.Build.props, jak uvidíte 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í není relevantní pro Directory.Build.props.
Pořadí importu
Directory.Build.props je naimportován brzy v Microsoft.Common.propsa později definované vlastnosti pro něj nejsou dostupné. Vyhněte se tedy odkazování na vlastnosti, které ještě nejsou definované (a vyhodnotí se jako prázdné).
Vlastnosti, které jsou nastavené v Directory.Build.props lze přepsat jinde v souboru projektu nebo v importovaných souborech, takže byste měli zvážit nastavení v Directory.Build.props tak, že zadáte výchozí hodnoty pro vaše projekty.
Directory.Build.targets se naimportují z Microsoft.Common.targets po importu souborů .targets
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 Jak používat sady SDK projektu MSBuild.
Poznámka
Před zahájením provádění sestavení projektu (včetně všech PreBuildEvent
) engine MSBuild čte všechny importované soubory během vyhodnocování, takže se neočekává, že by tyto soubory byly upravovány PreBuildEvent
ani žádnou jinou částí procesu sestavení. Změny se neprojeví až do dalšího vyvolání MSBuild.exe nebo dalšího sestavení sady Visual Studio. Pokud váš proces sestavení obsahuje mnoho sestavení projektů (například při vícecílém sestavování nebo při sestavování závislých projektů), importované soubory, včetně Directory.build.props, se čtou při hodnocení každého jednotlivého sestavení projektu.
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 pro projekty projekty (2-src)a společné vlastnosti pro projekty(2-test).
Chcete-li, aby nástroj MSBuild správně sloučil "vnitřní" soubory ( 2-src a 2-test) se souborem "vnější" (1), je nutné vzít v úvahu, že jakmile nástroj MSBuild najde Directory.Build.props soubor, 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í Directory.Build.props ve struktuře řešení směrem nahoru, sloučí ho s výchozími nastaveními a přestane vyhledávat další.
- Pokud chcete najít a sloučit více úrovní,
<Import...>
(viz předchozí) "vnější" soubor z "vnitřního" souboru. - Pokud "vnější" soubor sám o sobě nenahrává nic nad sebou, prohledávání se tam zastaví.
Nebo jednodušeji: první Directory.Build.props, který neimportuje nic, je místo, kde MSBuild zastaví.
Chcete-li řídit proces importu explicitněji, použijte vlastnosti $(DirectoryBuildPropsPath)
, $(ImportDirectoryBuildProps)
, $(DirectoryBuildTargetsPath)
a $(ImportDirectoryBuildTargets)
. Vlastnost $(DirectoryBuildPropsPath)
určuje cestu k souboru Directory.Build.props
, který se má použít; podobně $(DirectoryBuildTargetsPath)
určuje cestu k souboru Directory.Build.targets
.
Logické vlastnosti $(ImportDirectoryBuildProps)
a $(ImportDirectoryBuildTargets)
jsou ve výchozím nastavení nastaveny na true
, takže nástroj MSBuild tyto soubory obvykle hledá, ale můžete je nastavit na false
, aby se zabránilo importu nástroje MSBuild.
Příklad
Tento příklad ukazuje použití předzpracovaného výstupu k určení, kde nastavit vlastnost.
Chcete-li pomoci analyzovat použití konkrétní vlastnosti, kterou chcete nastavit, můžete spustit MSBuild pomocí /preprocess
nebo /pp
argumentu. 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 by měla být nastavena vlastnost vzhledem k tomu, kde se využí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 to zkusíte vložit do PropertyGroup
elementu 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 MSBuild s možností /pp
a vyhledáte výstup BaseIntermediateOutputPath
, můžete zjistit, proč. V tomto případě se BaseIntermediateOutput
přečte a použije 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ž je BaseIntermediateOutputPath
původně nastavená, existuje kontrola předdefinované hodnoty a pokud je nedefinovaná, nastaví se na obj
.
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">obj\</BaseIntermediateOutputPath>
Toto umístění vám tedy říká, že pokud chcete tuto vlastnost nastavit, musí být specifikována někde dříve než zde. Těsně před tímto kódem v předzpracovaném výstupu uvidíte, že Directory.Build.props
se importuje, takže můžete nastavit BaseIntermediateOutputPath
tam a bude dostatečně brzy nastaven, aby se požadovaný efekt projevil.
Následující zkrácený předzpracovaný výstup ukazuje výsledek vložení nastavení BaseIntermediateOutput
do Directory.Build.props
. 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>
...