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

Jeśli na przykład chcesz włączyć wszystkie projekty w celu uzyskania dostępu do nowej funkcji Roslyn /deterministycznej (uwidocznionej w obiekcie docelowym Roslyn CoreCompileprzez właściwość $(Deterministic)), możesz wykonać następujące czynności.

  1. Utwórz nowy plik w katalogu głównym repozytorium o nazwie Directory.Build.props.

  2. Dodaj następujący kod XML do pliku .

    <Project>
     <PropertyGroup>
       <Deterministic>true</Deterministic>
     </PropertyGroup>
    </Project>
    
  3. Uruchom program MSBuild. Istniejące importy projektów Microsoft.Common.props i Microsoft.Common.targets znajdują plik i zaimportuj go.

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 , BaseIntermediateOutputPathzobaczysz, 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>
  ...