Delen via


De build op map aanpassen

U kunt bepaalde bestanden toevoegen die door MSBuild moeten worden geïmporteerd om de standaardinstellingen voor eigenschappen te overschrijven en aangepaste doelen toe te voegen. Het bereik van deze aanpassingen kan worden beheerd op mapniveau door waar deze bestanden worden geplaatst.

In dit artikel worden aanpassingen behandeld die van toepassing zijn op de volgende scenario's:

  • Build-instellingen voor veel projecten in een oplossing aanpassen
  • Build-instellingen aanpassen voor veel oplossingen in een algemene bestandsmap
  • Build-instellingen aanpassen die mogelijk verschillen voor submappen in een complexe structuur van mappen
  • Standaardinstellingen, standaard buildmappen en ander gedrag negeren dat is ingesteld door een SDK, zoals Microsoft.Net.Sdk
  • Builddoelen toevoegen of aanpassen die van toepassing zijn op een willekeurig aantal projecten of oplossingen

Als u met C++-projecten werkt, kunt u ook de methoden gebruiken die worden beschreven in C++-builds aanpassen.

Directory.Build.props en Directory.Build.targets

U kunt een nieuwe eigenschap toevoegen aan elk project door deze te definiëren in één bestand met de naam Directory.Build.props in de hoofdmap die uw bron bevat.

Wanneer MSBuild wordt uitgevoerd, Microsoft.Common.props uw mapstructuur doorzoekt op het bestand Directory.Build.props. Als er een wordt gevonden, wordt het bestand geïmporteerd en worden de eigenschappen gelezen die erin zijn gedefinieerd. Directory.Build.props is een door de gebruiker gedefinieerd bestand dat aanpassingen aan projecten onder een map biedt.

Op dezelfde manier zoekt Microsoft.Common.targets naar Directory.Build.targets.

Directory.Build.props vroeg in de volgorde van geïmporteerde bestanden wordt geïmporteerd. Dit kan belangrijk zijn als u een eigenschap moet instellen die wordt gebruikt door importbewerkingen, met name de eigenschappen die impliciet worden geïmporteerd met behulp van het kenmerk Sdk, bijvoorbeeld wanneer u de .NET SDK in de meeste .NET-projectbestanden gebruikt.

Notitie

Bestandssystemen op basis van Linux zijn hoofdlettergevoelig. Zorg ervoor dat de behuizing van de Directory.Build.props bestandsnaam exact overeenkomt of dat deze niet wordt gedetecteerd tijdens het buildproces.

Zie dit GitHub-probleemvoor meer informatie.

Voorbeeld van Directory.Build.props

Hier volgt bijvoorbeeld een Directory.Build.props bestand waarmee de uitvoermap voor alle projecten in een Visual Studio-oplossing wordt ingesteld. De uitvoer van elk project wordt onder een eigen projectnaam geplaatst. In dit voorbeeld bevindt het bestand Directory.Build.props zich in een oplossingsmap, met veel projecten in submappen eronder. De eigenschap $(MSBuildProjectName) geeft de naam van elk project. Omdat het bestand Directory.Build.props in elk project wordt geïmporteerd tijdens een eigen build, wordt het geëvalueerd op de juiste waarde voor elk afzonderlijk project in de oplossing.

  1. Schoon de oplossing op om oude uitvoerbestanden te verwijderen.

    msbuild /t:Clean SolutionName.sln

  2. Maak een nieuw bestand in de hoofdmap van uw opslagplaats met de naam Directory.Build.props.

  3. Voeg de volgende XML toe aan het bestand.

    <Project>
       <PropertyGroup>
          <OutDir>C:\output\$(MSBuildProjectName)</OutDir>
       </PropertyGroup>
    </Project>
    

    Notitie

    De eigenschap $(OutDir) is een absoluut pad naar de uitvoer en het gebruik ervan omzeilt het maken van submappen voor de configuratie, het doelframework of de runtime die normaal gesproken worden gebruikt in .NET-projecten. Gebruik in plaats daarvan de eigenschap BaseOutputPath als u wilt dat de gebruikelijke submappen worden gemaakt onder een aangepast uitvoerpad.

  4. Voer MSBuild uit. De bestaande importbewerkingen van Microsoft.Common.props en Microsoft.Common.targets vinden en importeren het bestand Directory.Build.props, en de nieuwe uitvoermap wordt gebruikt voor alle projecten in die map.

Zoekbereik

Wanneer u zoekt naar een Directory.Build.props bestand, leidt MSBuild de mapstructuur omhoog vanaf uw projectlocatie $(MSBuildProjectFullPath), stoppen nadat het een Directory.Build.props bestand heeft gevonden. Als uw $(MSBuildProjectFullPath) bijvoorbeeld c:\users\username\code\test\case1, begint MSBuild daar te zoeken en de mapstructuur naar boven te doorzoeken totdat het een Directory.Build.props bestand heeft gevonden, zoals in de volgende mapstructuur.

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

De locatie van het oplossingsbestand is niet relevant voor Directory.Build.props.

Import order

Directory.Build.props wordt vroeg in Microsoft.Common.propsgeïmporteerd, en eigenschappen die later worden gedefinieerd, zijn dan niet beschikbaar. Zorg er dus voor dat u niet verwijst naar eigenschappen die nog niet zijn gedefinieerd (en die leeg zullen zijn).

Eigenschappen die zijn ingesteld in Directory.Build.props kunnen elders in het projectbestand of in geïmporteerde bestanden worden overschreven. U moet de instellingen in Directory.Build.props daarom beschouwen als het specificeren van de standaardwaarden voor uw projecten.

Directory.Build.targets wordt geïmporteerd uit Microsoft.Common.targets na het importeren van .targets bestanden uit NuGet-pakketten. Het kan dus eigenschappen en doelen overschrijven die zijn gedefinieerd in de meeste buildlogica of eigenschappen instellen voor al uw projecten, ongeacht wat de afzonderlijke projecten hebben ingesteld.

Wanneer u een eigenschap wilt instellen of een doel wilt definiëren voor een afzonderlijk project dat eventuele eerdere instellingen overschrijft, plaatst u die logica in het projectbestand na de laatste import. Als u dit wilt doen in een SDK-project, moet u eerst het kenmerk SDK-stijl vervangen door de equivalente importbewerkingen. Zie Hoe MSBuild-project-SDK's te gebruiken.

Notitie

De MSBuild-engine leest tijdens de evaluatie alle geïmporteerde bestanden voordat de uitvoering van een build voor een project (inclusief PreBuildEvent) wordt gestart, zodat verwacht wordt dat deze bestanden niet worden gewijzigd door PreBuildEvent of een ander deel van het buildproces. Wijzigingen worden pas van kracht nadat de volgende aanroep van MSBuild.exe of de volgende Visual Studio-build is doorgevoerd. Als uw buildproces veel projectbuilds bevat (zoals bij multitargeting of het bouwen van afhankelijke projecten), worden geïmporteerde bestanden, waaronder Directory.build.props, gelezen wanneer evaluatie plaatsvindt voor elke afzonderlijke projectbuild.

Use case: samenvoegen op meerdere niveaus

Stel dat u deze standaardoplossingsstructuur hebt:

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

Het kan wenselijk zijn om algemene eigenschappen te hebben voor alle projecten (1), algemene eigenschappen voor src projecten (2-src)en algemene eigenschappen voor test projecten (2-test).

Als u MSBuild de "inner" bestanden (2-src en 2-test) correct wilt samenvoegen met het 'outer'-bestand (1), moet u er rekening mee houden dat zodra MSBuild een Directory.Build.props bestand vindt, het verdere scannen stopt. Als u wilt doorgaan met scannen en samenvoegen in het buitenste bestand, plaatst u deze code in beide interne bestanden:

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

Een samenvatting van de algemene benadering van MSBuild is als volgt:

  • Voor een bepaald project vindt MSBuild de eerste Directory.Build.props hogerop in de oplossingsstructuur, voegt deze samen met de standaardinstellingen en stopt met verder scannen.
  • Als u meerdere niveaus wilt vinden en samenvoegen, <Import...> (eerder weergegeven) het buitenste bestand uit het 'inner'-bestand.
  • Als het 'outer'-bestand niet zelf ook iets erboven importeert, stopt het scannen daar.

Of meer eenvoudig: de eerste Directory.Build.props die niets importeert, is waar MSBuild stopt.

Als u het importproces explicieter wilt beheren, gebruikt u de eigenschappen $(DirectoryBuildPropsPath), $(ImportDirectoryBuildProps), $(DirectoryBuildTargetsPath)en $(ImportDirectoryBuildTargets). De eigenschap $(DirectoryBuildPropsPath) geeft het pad naar het Directory.Build.props-bestand op dat moet worden gebruikt; op dezelfde manier geeft $(DirectoryBuildTargetsPath) het pad naar het Directory.Build.targets-bestand op.

De Booleaanse eigenschappen $(ImportDirectoryBuildProps) en $(ImportDirectoryBuildTargets) zijn standaard ingesteld op true, dus MSBuild zoekt normaal naar deze bestanden, maar u kunt ze instellen op false om te voorkomen dat MSBuild ze importeert.

Voorbeeld

In dit voorbeeld ziet u het gebruik van de vooraf verwerkte uitvoer om te bepalen waar een eigenschap moet worden ingesteld.

Om u te helpen bij het analyseren van het gebruik van een bepaalde eigenschap die u wilt instellen, kunt u MSBuild uitvoeren met het argument /preprocess of /pp. De uitvoertekst is het resultaat van alle importbewerkingen, inclusief de systeemimport zoals Microsoft.Common.props die impliciet worden geïmporteerd en een van uw eigen importbewerkingen. Met deze uitvoer kunt u zien waar uw eigenschap moet worden ingesteld ten opzichte van waar de waarde wordt gebruikt.

Stel dat u een eenvoudig console-app-project van .NET Core of .NET 5 of hoger hebt en u de tussenliggende uitvoermap wilt aanpassen, normaal gesproken obj. De eigenschap die dit pad aangeeft, is BaseIntermediateOutput. Als u dit in een PropertyGroup element in uw projectbestand plaatst, samen met de verschillende andere eigenschappen die daar al zijn ingesteld, zoals TargetFramework, ontdekt u wanneer u het project bouwt dat de eigenschap niet van kracht wordt. Als u MSBuild uitvoert met de optie /pp en de uitvoer zoekt naar BaseIntermediateOutputPath, kunt u zien waarom. In dit geval wordt BaseIntermediateOutput gelezen en gebruikt in Microsoft.Common.props.

Er is een opmerking in Microsoft.Common.props met de mededeling dat de eigenschap BaseIntermediateOutput hier moet worden ingesteld, voordat deze wordt gebruikt door een andere eigenschap, MSBuildProjectExtensionsPath. U kunt ook zien dat wanneer BaseIntermediateOutputPath in eerste instantie is ingesteld, er een controle is op een vooraf bestaande waarde en als deze niet is gedefinieerd, wordt deze ingesteld op obj.

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

Deze plaatsing geeft dus aan dat u deze eigenschap wilt instellen, deze ergens eerder moet worden opgegeven dan deze. Net voordat deze code in de voorverwerkte uitvoer wordt geïmporteerd, ziet u dat Directory.Build.props wordt geïmporteerd, zodat u BaseIntermediateOutputPath daar kunt instellen en deze vroeg genoeg wordt ingesteld om het gewenste effect te hebben.

In de verkorte, vooraf verwerkte uitvoer ziet u het resultaat van het toepassen van de BaseIntermediateOutput instellingen in Directory.Build.props. De opmerkingen bovenaan de standaardimport bevatten de bestandsnaam en meestal enkele nuttige informatie over waarom dat bestand wordt geïmporteerd.

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