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
- Dodawanie lub dostosowywanie obiektów docelowych 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 temacie 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 wyszukuje strukturę katalogu dla 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 obiektów Directory.Build.targets.
Plik Directory.Build.props jest importowany na wczesnym etapie sekwencji importowanych plików, co może być ważne, jeśli musisz ustawić właściwość używaną przez import, 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.
Uwaga
W systemach plików opartych na systemie Linux uwzględniana jest wielkość liter. Upewnij się, że wielkość pliku Directory.Build.props jest dokładnie zgodna lub nie zostanie wykryta podczas procesu kompilacji.
Aby uzyskać więcej informacji, zobacz ten problem w serwisie GitHub.
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 programu 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)
nadaje nazwę każdego projektu. Ponieważ plik Directory.Build.props jest importowany do każdego projektu podczas własnej kompilacji, jest on oceniany na odpowiednią wartość dla każdego pojedynczego projektu 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>
Uwaga
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 projektów Microsoft.Common.props i Microsoft.Common.targets znajdują plik Directory.Build.props i zaimportuj 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 przeprowadzi strukturę katalogu w górę z lokalizacji $(MSBuildProjectFullPath)
projektu , zatrzymując się po zlokalizowaniu pliku Directory.Build.props. Jeśli na przykład plik $(MSBuildProjectFullPath)
c:\users\username\code\test\case1, program MSBuild zacznie tam przeszukiwać, a następnie przeszukiwać strukturę katalogów w górę, dopóki nie znajdzie pliku Directory.Build.props , tak 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 pliku Directory.Build.props.
Kolejność importowania
Plik Directory.Build.props jest importowany na początku pliku Microsoft.Common.props, a zdefiniowane później właściwości są niedostępne. Dlatego należy unikać odwoływania się do właściwości, które nie są jeszcze zdefiniowane (i będzie oceniać wartość pustą).
Właściwości ustawione w pliku Directory.Build.props można zastąpić w innym miejscu w pliku projektu lub w zaimportowanych plikach, więc należy traktować ustawienia w pliku Directory.Build.props jako określanie wartości domyślnych dla projektów.
Pliki Directory.Build.targets są importowane z obiektów Microsoft.Common.targets po zaimportowaniu .targets
plików z pakietów NuGet. Dlatego może zastąpić właściwości i obiekty docelowe zdefiniowane w większości logiki kompilacji lub ustawić właściwości dla wszystkich projektów niezależnie od tego, co ustawić 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 How to use MSBuild project SDKs (Jak używać zestawów SDK projektu MSBuild).
Uwaga
Aparat MSBuild odczytuje we wszystkich zaimportowanych plikach podczas oceny przed rozpoczęciem wykonywania kompilacji dla projektu (w tym dowolnego PreBuildEvent
), więc te pliki nie powinny być modyfikowane przez PreBuildEvent
lub inną część procesu kompilacji. 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 dla każdej kompilacji poszczególnych projektów.
Przypadek użycia: scalanie wieloeziomowe
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 projektów src (2-src) i wspólnych właściwości projektów testowych (2-testowych).
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 program MSBuild 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 katalog.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 znaleźć i scalić wiele poziomów, to
<Import...>
(pokazano wcześniej) plik "zewnętrzny" z pliku "wewnętrznego". - Jeśli plik "zewnętrzny" nie jest również importem czegoś powyżej, skanowanie zatrzymuje się tam.
Lub po prostu: pierwszy element Directory.Build.props , który nie importuje niczego, to miejsce zatrzymania programu MSBuild.
Aby bardziej jawnie kontrolować proces importowania, użyj właściwości $(DirectoryBuildPropsPath)
, $(ImportDirectoryBuildProps)
, $(DirectoryBuildTargetsPath)
i $(ImportDirectoryBuildTargets)
. Właściwość $(DirectoryBuildPropsPath)
określa ścieżkę do Directory.Build.props
pliku do użycia; podobnie $(DirectoryBuildTargetsPath)
określa ścieżkę do Directory.Build.targets
pliku.
Właściwości $(ImportDirectoryBuildProps)
logiczne i $(ImportDirectoryBuildTargets)
są domyślnie ustawione na true
wartość , więc program MSBuild zwykle wyszukuje te pliki, ale można je ustawić tak, aby false
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 za pomocą 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. Za pomocą tych danych wyjściowych można zobaczyć, gdzie należy ustawić właściwość względem miejsca, w którym jest używana jego 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ę to BaseIntermediateOutput
. Jeśli spróbujesz umieścić go w elemecie PropertyGroup
w pliku projektu wraz z różnymi innymi właściwościami, które zostały już tam ustawione, takie jak TargetFramework
, można wykryć podczas kompilowania projektu, którego właściwość nie zaczyna obowiązywać. Jeśli uruchomisz program MSBuild z opcją /pp
i wyszukasz dane wyjściowe , BaseIntermediateOutputPath
zobaczysz, dlaczego. W tym przypadku BaseIntermediateOutput
parametr jest odczytywany i używany w pliku Microsoft.Common.props
.
W pliku Microsoft.Common.props znajduje się komentarz z informacją, że właściwość BaseIntermediateOutput
musi być tutaj ustawiona, zanim będzie używana przez inną właściwość MSBuildProjectExtensionsPath
. Możesz również zobaczyć, że po BaseIntermediateOutputPath
początkowym ustawieniu jest sprawdzanie, czy istnieje wstępnie istniejąca wartość, a jeśli jest ona niezdefiniowana, zostanie ustawiona wartość obj
.
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">obj\</BaseIntermediateOutputPath>
Dlatego umieszczenie informuje o tym, że aby ustawić tę właściwość, musi być określona gdzieś wcześniej niż ta. Tuż przed tym kodem w wstępnie przetworzonych danych wyjściowych można zobaczyć, że Directory.Build.props
jest importowany, dzięki czemu można je ustawić BaseIntermediateOutputPath
i zostanie ustawiony wystarczająco wcześnie, aby uzyskać pożądany efekt.
Następujące skrócone wstępnie przetworzone dane wyjściowe pokazują wynik umieszczenia BaseIntermediateOutput
ustawienia w pliku 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>
...