Bagikan melalui


Mengkustomisasi build menurut folder

Anda dapat menambahkan file tertentu untuk diimpor oleh MSBuild untuk mengambil alih pengaturan properti default dan menambahkan target kustom. Cakupan kustomisasi ini dapat dikontrol di tingkat folder dengan tempat file-file ini ditempatkan.

Artikel ini membahas kustomisasi yang berlaku untuk skenario berikut:

  • Menyesuaikan pengaturan build untuk banyak proyek dalam solusi
  • Menyesuaikan pengaturan build untuk banyak solusi di bawah direktori file umum
  • Menyesuaikan pengaturan build yang mungkin berbeda untuk subfolder dalam struktur folder yang kompleks
  • Mengganti pengaturan default, folder build default, dan perilaku lain yang ditetapkan oleh SDK seperti Microsoft.Net.Sdk
  • Menambahkan atau menyesuaikan target build yang berlaku untuk sejumlah proyek atau solusi

Jika Anda bekerja dengan proyek C++, Anda juga dapat menggunakan metode yang dijelaskan di Menyesuaikan build C++.

Directory.Build.props dan Directory.Build.targets

Anda dapat menambahkan properti baru ke setiap proyek dengan mendefinisikannya dalam satu file yang disebut Directory.Build.props di folder akar yang berisi sumber Anda.

Saat MSBuild berjalan, Microsoft.Common.props mencari struktur direktori Anda untuk file Directory.Build.props . Jika menemukannya, file akan diimpor dan membaca properti yang ditentukan di dalamnya. Directory.Build.props adalah file yang ditentukan pengguna yang menyediakan kustomisasi untuk proyek di bawah direktori.

Demikian pula, Microsoft.Common.targets mencari Directory.Build.targets.

Directory.Build.props diimpor lebih awal dalam urutan file yang diimpor, yang mungkin penting jika Anda perlu mengatur properti yang digunakan oleh impor, terutama yang secara implisit diimpor dengan menggunakan Sdk atribut , seperti saat menggunakan .NET SDK di sebagian besar file proyek .NET.

Catatan

Sistem file berbasis Linux peka huruf besar/kecil. Pastikan casing nama file Directory.Build.props sama persis, atau tidak akan terdeteksi selama proses build.

Untuk informasi lebih lanjut, lihat masalah GitHub ini.

Contoh Directory.Build.props

Misalnya, berikut adalah file Directory.Build.props yang mengatur direktori output untuk semua proyek dalam solusi Visual Studio. Output dari setiap proyek ditempatkan di bawah nama proyeknya sendiri. Dalam contoh ini, file Directory.Build.props berada di folder solusi, dengan banyak proyek dalam subfolder di bawahnya. Properti $(MSBuildProjectName) memberikan nama setiap proyek. Karena file Directory.Build.props diimpor ke setiap proyek selama build sendiri, file tersebut dievaluasi ke nilai yang tepat untuk setiap proyek individu dalam solusi.

  1. Bersihkan solusi untuk menghapus file output lama.

    msbuild /t:Clean SolutionName.sln

  2. Buat file baru di akar repositori Anda bernama Directory.Build.props.

  3. Tambahkan XML berikut ke file.

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

    Catatan

    Properti $(OutDir) adalah jalur absolut ke output, dan menggunakannya melewati pembuatan subfolder untuk konfigurasi, kerangka kerja target, atau runtime yang biasanya digunakan dalam proyek .NET. Coba gunakan properti BaseOutputPath sebagai gantinya jika Anda ingin subfolder biasa dibuat di bawah jalur output kustom.

  4. Jalankan MSBuild. Proyek Anda yang sudah ada mengimpor Microsoft.Common.props dan Microsoft.Common.targets menemukan file Directory.Build.props dan mengimpornya, dan folder output baru digunakan untuk semua proyek di bawah folder tersebut.

Cakupan pencarian

Saat mencari file Directory.Build.props , MSBuild berjalan struktur direktori ke atas dari lokasi $(MSBuildProjectFullPath)proyek Anda , berhenti setelah menemukan file Directory.Build.props . Misalnya, jika $(MSBuildProjectFullPath) Anda adalah c:\users\username\code\test\case1, MSBuild akan mulai mencari dari sana dan kemudian mencari struktur direktori ke atas sampai menemukan file Directory.Build.props, seperti dalam struktur direktori berikut.

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

Lokasi file solusi tidak relevan dengan Directory.Build.props.

Urutan impor

Directory.Build.props diimpor lebih awal di Microsoft.Common.props, dan properti yang ditentukan nanti tidak tersedia untuknya. Jadi, hindari mengacu pada properti yang belum ditentukan (dan akan mengevaluasi ke kosong).

Properti yang diatur dalam Directory.Build.props dapat ditimpa di tempat lain dalam file proyek atau dalam file yang diimpor, jadi Anda harus menganggap pengaturan di Directory.Build.props sebagai menentukan default untuk proyek Anda.

Directory.Build.targets diimpor dari Microsoft.Common.targets setelah mengimpor file .targets dari paket NuGet. Jadi, ia dapat mengambil alih properti dan target yang ditentukan di sebagian besar logika build, atau mengatur properti untuk semua proyek Anda terlepas dari apa yang ditetapkan oleh masing-masing proyek.

Saat Anda perlu mengatur properti atau menentukan target untuk proyek individual yang mengambil alih pengaturan sebelumnya, masukkan logika tersebut ke dalam file proyek setelah impor akhir. Untuk melakukannya dalam proyek bergaya SDK, Anda harus terlebih dahulu mengganti atribut gaya SDK dengan impor yang setara. Lihat Cara menggunakan SDK proyek MSBuild.

Catatan

Mesin MSBuild membaca semua file yang diimpor selama evaluasi, sebelum memulai eksekusi build untuk proyek (termasuk apa pun PreBuildEvent), sehingga file-file ini tidak diharapkan untuk dimodifikasi oleh PreBuildEvent atau bagian lain dari proses build. Modifikasi apa pun tidak berlaku hingga pemanggilan MSBuild.exe berikutnya atau build Visual Studio berikutnya. Selain itu, jika proses build Anda berisi banyak build proyek (seperti halnya proyek dependen multitargeting atau bangunan), maka file yang diimpor, termasuk Directory.build.props, dibaca saat evaluasi terjadi untuk setiap build proyek individual.

Kasus penggunaan: penggabungan multi-tingkat

Misalkan Anda memiliki struktur solusi standar ini:

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

Memiliki properti umum untuk semua proyek (1), properti umum untuk proyek src(2-src), dan properti umum untuk proyek pengujian(2-tets) mungkin diinginkan.

Untuk membuat MSBuild menggabungkan file "dalam" (2-src dan 2-test) dengan file "luar" (1) dengan benar, Anda harus memperhitungkan bahwa setelah MSBuild menemukan file Directory.Build.props, MSBuild menghentikan pemindaian lebih lanjut. Untuk terus memindai dan menggabungkan ke file luar, tempatkan kode ini ke dalam kedua file dalam:

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

Berikut adalah ringkasan pendekatan umum MSBuild:

  • Untuk proyek tertentu, MSBuild menemukan Directory.Build.props pertama ke atas dalam struktur solusi, menggabungkannya dengan default, dan menghentikan pemindaian lebih lanjut.
  • Jika Anda ingin beberapa tingkat ditemukan dan digabungkan, maka <Import...> (diperlihatkan sebelumnya) file "luar" dari file "dalam".
  • Jika file "luar" tidak sendiri juga mengimpor sesuatu di atasnya, pemindaian berhenti di sana.

Atau lebih sederhananya: Directory.Build.props pertama yang tidak mengimpor apa pun adalah tempat MSBuild berhenti.

Untuk mengontrol proses impor secara lebih eksplisit, gunakan properti $(DirectoryBuildPropsPath), , $(DirectoryBuildTargetsPath)$(ImportDirectoryBuildProps), dan $(ImportDirectoryBuildTargets). Properti $(DirectoryBuildPropsPath) menentukan jalur ke file yang Directory.Build.props akan digunakan; demikian pula, $(DirectoryBuildTargetsPath) menentukan jalur ke Directory.Build.targets file.

Properti $(ImportDirectoryBuildProps) Boolean dan $(ImportDirectoryBuildTargets) diatur ke true secara default, sehingga MSBuild biasanya mencari file-file ini, tetapi Anda dapat mengaturnya false untuk mencegah MSBuild mengimpornya.

Contoh

Contoh ini menunjukkan penggunaan output yang telah diproscesikan sebelumnya untuk menentukan tempat mengatur properti.

Untuk membantu Anda menganalisis penggunaan properti tertentu yang ingin Anda atur, Anda dapat menjalankan MSBuild dengan /preprocess argumen atau /pp . Teks output adalah hasil dari semua impor, termasuk impor sistem seperti Microsoft.Common.props yang diimpor secara implisit, dan impor Anda sendiri. Dengan output ini, Anda dapat melihat di mana properti Anda perlu diatur relatif terhadap tempat nilainya digunakan.

Sebagai contoh, misalkan Anda memiliki proyek Aplikasi Konsol .NET Core atau .NET 5 atau yang lebih baru, dan Anda ingin menyesuaikan folder output perantara, biasanya obj. Properti yang menentukan jalur ini adalah BaseIntermediateOutput. Jika Anda mencoba menempatkan ini dalam PropertyGroup elemen dalam file proyek Anda bersama dengan berbagai properti lain yang sudah diatur di sana, seperti TargetFramework, Anda akan menemukan ketika Anda membangun proyek yang propertinya tidak berlaku. Jika Anda menjalankan MSBuild dengan /pp opsi dan mencari output untuk BaseIntermediateOutputPath, Anda dapat melihat alasannya. Dalam hal ini, BaseIntermediateOutput dibaca dan digunakan dalam Microsoft.Common.props.

Ada komentar di Microsoft.Common.props yang mengatakan properti BaseIntermediateOutput harus diatur di sini, sebelum digunakan oleh properti lain, MSBuildProjectExtensionsPath. Anda juga dapat melihat bahwa ketika BaseIntermediateOutputPath awalnya diatur, ada pemeriksaan untuk nilai yang sudah ada sebelumnya, dan jika tidak ditentukan, nilai tersebut akan diatur ke obj.

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

Jadi, penempatan ini memberi tahu Anda bahwa untuk mengatur properti ini, itu harus ditentukan di suatu tempat yang lebih awal dari ini. Tepat sebelum kode ini dalam output yang telah diolah sebelumnya, Anda dapat melihat bahwa diimpor Directory.Build.props , sehingga Anda dapat mengaturnya BaseIntermediateOutputPath di sana dan itu akan diatur cukup awal untuk memiliki efek yang diinginkan.

Output praproses yang disingkat BaseIntermediateOutput berikut menunjukkan hasil menempatkan pengaturan di Directory.Build.props. Komentar di bagian atas impor standar mencakup nama file dan biasanya beberapa informasi bermanfaat tentang mengapa file tersebut diimpor.

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