Dostosowywanie kompilacji według folderu
Możesz dodać określone pliki do zaimportowania przez program MSBuild, aby zastąpić domyślne ustawienia właściwości i dodać niestandardowe elementy docelowe. Zakres tych dostosowań można kontrolować na poziomie folderu, gdzie te pliki są umieszczane.
W tym artykule opisano dostosowania dotyczące następujących scenariuszy:
- Dostosowywanie ustawień kompilacji dla wielu projektów w rozwiązaniu
- Dostosowywanie ustawień kompilacji dla wielu rozwiązań w ramach wspólnego katalogu plików
- Dostosowywanie ustawień kompilacji, które mogą być różne dla podfolderów w złożonej strukturze folderów
- Przesłaniaj ustawienia domyślne, domyślne foldery kompilacji i inne zachowania ustawione przez zestaw SDK, takie jak
Microsoft.Net.Sdk
- Dodaj lub dostosuj cele kompilacji, które mają zastosowanie do dowolnej liczby projektów lub rozwiązań.
Jeśli pracujesz z projektami języka C++, możesz również użyć metod opisanych w Dostosowywanie kompilacji języka C++.
Directory.Build.props i Directory.Build.targets
Do każdego projektu można dodać nową właściwość, definiując ją w jednym pliku o nazwie Directory.Build.props w folderze głównym zawierającym źródło.
Po uruchomieniu programu MSBuild Microsoft.Common.props przeszukuje strukturę katalogu w poszukiwaniu pliku Directory.Build.props. Jeśli go znajdzie, importuje plik i odczytuje zdefiniowane w nim właściwości. Directory.Build.props to plik zdefiniowany przez użytkownika, który zapewnia dostosowania projektów w katalogu.
Podobnie Microsoft.Common.targets szuka Directory.Build.targets.
Directory.Build.props jest importowany na wczesnym etapie sekwencji importowanych plików, co może być ważne, jeśli trzeba ustawić właściwość używaną przez importy, zwłaszcza te, które są niejawnie importowane przy użyciu atrybutu Sdk
, na przykład podczas korzystania z zestawu SDK platformy .NET w większości plików projektów platformy .NET.
Notatka
W systemach plików opartych na systemie Linux uwzględniana jest wielkość liter. Upewnij się, że wielkość liter w nazwie pliku Directory.Build.props jest dokładnie taka sama, w przeciwnym razie nie zostanie on wykryty podczas procesu kompilacji.
Aby uzyskać więcej informacji, zobacz ten problem na GitHubie.
Przykład Directory.Build.props
Na przykład poniżej znajduje się plik Directory.Build.props, który ustawia katalog wyjściowy dla wszystkich projektów w rozwiązaniu Visual Studio. Dane wyjściowe każdego projektu są umieszczane pod własną nazwą projektu. W tym przykładzie plik Directory.Build.props znajduje się w folderze rozwiązania z wieloma projektami w podfolderach. Właściwość $(MSBuildProjectName)
zawiera nazwę każdego projektu. Ponieważ plik Directory.Build.props jest importowany do każdego projektu podczas kompilacji, jest on obliczany jako odpowiednia wartość dla każdego projektu z osobna w rozwiązaniu.
Wyczyść rozwiązanie, aby usunąć wszystkie stare pliki wyjściowe.
msbuild /t:Clean SolutionName.sln
Utwórz nowy plik w katalogu głównym repozytorium o nazwie Directory.Build.props.
Dodaj następujący kod XML do pliku .
<Project> <PropertyGroup> <OutDir>C:\output\$(MSBuildProjectName)</OutDir> </PropertyGroup> </Project>
Notatka
Właściwość
$(OutDir)
jest ścieżką bezwzględną do danych wyjściowych, a jej użycie pomija tworzenie podfolderów dla konfiguracji, platformy docelowej lub środowiska uruchomieniowego, które są zwykle używane w projektach platformy .NET. Spróbuj użyć właściwościBaseOutputPath
zamiast tego, jeśli chcesz, aby zwykłe podfoldery zostały utworzone w niestandardowej ścieżce wyjściowej.Uruchom program MSBuild. Istniejące importy projektu Microsoft.Common.props i Microsoft.Common.targets znajdują plik Directory.Build.props i importują go, a nowy folder wyjściowy jest używany dla wszystkich projektów w tym folderze.
Zakres wyszukiwania
Podczas wyszukiwania pliku Directory.Build.props program MSBuild przechodzi przez strukturę katalogów w górę od lokalizacji projektu $(MSBuildProjectFullPath)
, zatrzymując się, gdy zlokalizuje plik Directory.Build.props. Jeśli na przykład $(MSBuildProjectFullPath)
jest c:\users\username\code\test\case1, program MSBuild rozpocznie wyszukiwanie w tym miejscu, a następnie przeszuka strukturę katalogów w górę, dopóki nie znajdzie pliku Directory.Build.props, jak w poniższej strukturze katalogów.
c:\users\username\code\test\case1
c:\users\username\code\test
c:\users\username\code
c:\users\username
c:\users
c:\
Lokalizacja pliku rozwiązania nie ma znaczenia dla Directory.Build.props.
Kolejność importowania
Directory.Build.props jest importowany wczesnym etapie Microsoft.Common.props, a zdefiniowane później właściwości są niedostępne dla niego. Dlatego należy unikać odwoływania się do właściwości, które nie są jeszcze zdefiniowane (i będą oceniane jako puste).
Właściwości ustawione w Directory.Build.props można zastąpić w innym miejscu w pliku projektu lub w zaimportowanych plikach, więc należy traktować ustawienia w Directory.Build.props jako określenie wartości domyślnych dla projektów.
Directory.Build.targets jest importowany z Microsoft.Common.targets po zaimportowaniu plików z pakietów NuGet .targets
. Może więc zastąpić właściwości i cele zdefiniowane w większej części logiki budowania lub ustawić właściwości dla wszystkich projektów, niezależnie od tego, jakie właściwości ustalają poszczególne projekty.
Jeśli musisz ustawić właściwość lub zdefiniować element docelowy dla pojedynczego projektu, który zastępuje wszelkie wcześniejsze ustawienia, umieść tę logikę w pliku projektu po ostatecznym zaimportowaniu. Aby to zrobić w projekcie w stylu zestawu SDK, należy najpierw zastąpić atrybut stylu zestawu SDK równoważnymi importami. Zobacz Jak używać zestawów SDK projektu MSBuild.
Notatka
Silnik MSBuild odczytuje wszystkie zaimportowane pliki w procesie oceny, przed rozpoczęciem wykonywania budowy dla projektu (w tym PreBuildEvent
), więc te pliki nie powinny być modyfikowane przez PreBuildEvent
ani jakąkolwiek inną część procesu budowy. Wszelkie modyfikacje nie zostaną wprowadzone do momentu następnego wywołania MSBuild.exe lub następnej kompilacji programu Visual Studio. Ponadto jeśli proces kompilacji zawiera wiele kompilacji projektu (podobnie jak w przypadku wielotargetowania lub kompilowania projektów zależnych), importowane pliki, w tym Directory.build.props, są odczytywane podczas oceny poszczególnych kompilacji projektu.
Przypadek użycia: scalanie wielopoziomowe
Załóżmy, że masz tę standardową strukturę rozwiązań:
\
MySolution.sln
Directory.Build.props (1)
\src
Directory.Build.props (2-src)
\Project1
\Project2
\test
Directory.Build.props (2-test)
\Project1Tests
\Project2Tests
Może być pożądane posiadanie wspólnych właściwości dla wszystkich projektów (1), wspólnych właściwości dla projektów src(2-src)i wspólnych właściwości dla projektów testowych(2-test).
Aby program MSBuild poprawnie scalił pliki "wewnętrzne" (2-src i 2-test) z plikiem "zewnętrznym" (1), należy wziąć pod uwagę, że po znalezieniu pliku Directory.Build.props, zatrzymuje dalsze skanowanie. Aby kontynuować skanowanie i scalanie z plikiem zewnętrznym, umieść ten kod w obu plikach wewnętrznych:
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
Podsumowanie ogólnego podejścia msBuild jest następujące:
- W przypadku dowolnego projektu program MSBuild znajduje pierwszy Directory.Build.props w górę w strukturze rozwiązania, scala go z wartościami domyślnymi i zatrzymuje skanowanie w celu uzyskania większej liczby.
- Jeśli chcesz odnaleźć i scalić wiele poziomów, najpierw wybierz plik "zewnętrzny" z pliku "wewnętrznego" według instrukcji
<Import...>
(pokazany wcześniej). - Jeśli plik "zewnętrzny" sam także nie importuje niczego powyżej, wtedy skanowanie jest powstrzymywane tam.
Lub po prostu: pierwszy Directory.Build.props, który nie importuje niczego, to miejsce, w którym program MSBuild zatrzymuje się.
Aby jawnie kontrolować proces importowania, użyj właściwości $(DirectoryBuildPropsPath)
, $(ImportDirectoryBuildProps)
, $(DirectoryBuildTargetsPath)
i $(ImportDirectoryBuildTargets)
. Właściwość $(DirectoryBuildPropsPath)
określa ścieżkę do pliku Directory.Build.props
do użycia; podobnie $(DirectoryBuildTargetsPath)
określa ścieżkę do pliku Directory.Build.targets
.
Właściwości logiczne $(ImportDirectoryBuildProps)
i $(ImportDirectoryBuildTargets)
są domyślnie ustawione na true
, więc program MSBuild zwykle wyszukuje te pliki, ale można ustawić je na false
, aby zapobiec importowaniu ich przez program MSBuild.
Przykład
W tym przykładzie pokazano użycie wstępnie przetworzonych danych wyjściowych w celu określenia, gdzie ustawić właściwość.
Aby ułatwić analizowanie użycia określonej właściwości, którą chcesz ustawić, możesz uruchomić program MSBuild przy użyciu argumentu /preprocess
lub /pp
. Tekst wyjściowy jest wynikiem wszystkich importów, w tym importów systemowych, takich jak Microsoft.Common.props, które są niejawnie importowane, i dowolny z własnych importów. Dzięki tym wynikom, możesz zobaczyć, gdzie należy ustawić właściwość względem miejsca, w którym jest używana jej wartość.
Załóżmy na przykład, że masz prosty projekt aplikacji konsolowej platformy .NET Core lub .NET 5 lub nowszej i chcesz dostosować pośredni folder wyjściowy, zwykle obj
. Właściwość określająca tę ścieżkę jest BaseIntermediateOutput
. Jeśli spróbujesz umieścić to w elemencie PropertyGroup
w pliku projektu wraz z różnymi innymi właściwościami, które zostały już tam ustawione, takimi jak TargetFramework
, przekonasz się podczas budowania projektu, że właściwość nie wchodzi w życie. Jeśli uruchomisz program MSBuild z opcją /pp
i przeszukasz wynik dla BaseIntermediateOutputPath
, zobaczysz, dlaczego. W tym przypadku BaseIntermediateOutput
jest odczytywany i używany w Microsoft.Common.props
.
Istnieje komentarz w Microsoft.Common.props, który mówi, że właściwość BaseIntermediateOutput
musi być ustawiona tutaj, zanim będzie używana przez inną właściwość, MSBuildProjectExtensionsPath
. Można również zobaczyć, że gdy BaseIntermediateOutputPath
jest początkowo ustawione, sprawdzana jest wcześniej istniejąca wartość, a jeśli nie jest zdefiniowana, zostanie ustawiona na obj
.
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">obj\</BaseIntermediateOutputPath>
To umiejscowienie wskazuje, że aby określić tę właściwość, musi być ona podana gdzieś wcześniej niż to. Tuż przed tym kodem we wstępnie przetworzonych danych wyjściowych można zauważyć, że Directory.Build.props
jest importowany, więc można tam ustawić BaseIntermediateOutputPath
i będzie on ustawiony wystarczająco wcześnie, aby osiągnąć pożądany efekt.
Następujące skrócone wstępnie przetworzone dane wyjściowe pokazują wynik umieszczenia ustawienia BaseIntermediateOutput
w Directory.Build.props
. Komentarze w górnej części standardowych importów obejmują nazwę pliku i zazwyczaj kilka przydatnych informacji o tym, dlaczego ten plik jest importowany.
<?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>
...