Share via


폴더별 빌드 사용자 지정

MSBuild에서 가져올 수 있는 특정 파일을 추가하여 기본 속성 설정을 재정의하고 사용자 지정 대상을 추가할 수 있습니다. 사용자 지정의 범위는 이러한 파일이 배치되는 위치별로 폴더 수준에서 제어할 수 있습니다.

해당 문서에서는 다음 시나리오에 적용할 수 있는 사용자 지정에 대해 설명합니다.

  • 솔루션의 여러 프로젝트에 대한 빌드 설정 사용자 지정
  • 일반 파일 디렉터리에서 여러 솔루션에 대한 빌드 설정 사용자 지정
  • 폴더의 복잡한 구조에서 하위 폴더에 대해 다를 수 있는 빌드 설정을 사용자 지정
  • 기본 설정, 기본 빌드 폴더 및 SDK에서 설정한 기타 동작(예: Microsoft.Net.Sdk)을 재정의
  • 원하는 수 만큼의 프로젝트 또는 솔루션에 적용되는 빌드 대상 추가 또는 사용자 지정

C++ 프로젝트를 사용하는 경우 C++ 빌드 사용자 지정에 설명된 메서드를 사용할 수도 있습니다.

Directory.Build.props 및 Directory.Build.targets

소스를 포함하는 루트 폴더에서 Directory.Build.props라는 단일 파일에 속성을 정의하여 모든 프로젝트에 새 속성을 추가할 수 있습니다.

MSBuild가 실행되면 Microsoft.Common.props는 디렉터리 구조에서 Directory.Build.props 파일을 검색합니다. 파일을 찾은 경우 파일을 가져오고 그 안에 정의된 속성을 읽습니다. Directory.Build.props는 디렉터리에 있는 프로젝트에 대한 사용자 지정을 제공하는 사용자 지정 파일입니다.

유사하게, Microsoft.Common.targetsDirectory.Build.targets를 찾습니다.

Directory.Build.props는 가져온 파일의 시퀀스에서 초기에 가져오며, 가져오기에서 사용되는 속성, 특히 대부분의 .NET 프로젝트 파일에서 .NET SDK를 사용하는 경우와 같이 Sdk 특성을 사용하여 묵시적으로 가져오는 속성을 설정해야 하는 경우 중요할 수 있습니다.

참고

Linux 기반 파일 시스템은 대/소문자를 구분합니다. Directory.Build.props 파일 이름의 대/소문자가 정확히 일치하는지 확인합니다. 일치하지 않는 경우, 빌드 프로세스 중 파일이 검색되지 않습니다.

자세한 내용은 해당 GitHub 이슈를 참조하세요.

Directory.Build.props 예제

예를 들어 모든 프로젝트가 새 Roslyn /deterministic 기능(속성 CoreCompile에 의해 Roslyn $(Deterministic) 대상에 표시됨)에 액세스하도록 허용하려면 다음을 수행하십시오.

  1. Directory.Build.props라는 리포지토리의 루트에 새 파일을 만듭니다.

  2. 파일에 다음 XML을 추가합니다.

    <Project>
     <PropertyGroup>
       <Deterministic>true</Deterministic>
     </PropertyGroup>
    </Project>
    
  3. MSBuild를 실행합니다. 프로젝트의 기존 Microsoft.Common.propsMicrosoft.Common.targets 가져오기의 경우, 파일을 찾아서 가져옵니다.

검색 범위

Directory.Build.props 파일을 검색할 때, MSBuild는 프로젝트 위치($(MSBuildProjectFullPath))에서 위쪽으로 디렉터리 구조를 이동하여 Directory.Build.props 파일을 찾은 후 멈춥니다. 예를 들어, $(MSBuildProjectFullPath)가/이 c:\users\username\code\test\case1인 경우 MSBuild는 여기에서 검색을 시작하고 Directory.Build.props 파일을 찾을 때까지 위쪽으로 다음 디렉터리 구조와 같은 디렉터리 구조를 검색합니다.

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

솔루션 파일 위치는 Directory.Build.props와 관련이 없습니다.

가져오기 순서

Directory.Build.props은(는) Microsoft.Common.props에서 매우 초기에 가져오기 되며, 후에 정의된 속성은 적용할 수 없습니다. 따라서 아직 정의되지 않은 속성을 참조하지 마세요. 참조할 경우 비어 있는 것으로 평가됩니다.

Directory.Build.props에 설정된 속성은 프로젝트 파일이나 가져온 파일의 다른 위치에서 재정의할 수 있으므로 Directory.Build.props의 설정은 프로젝트의 기본값을 지정하는 것으로 간주할 수 있습니다.

Directory.Build.targets는 NuGet 패키지에서 파일을 가져온 후 Microsoft.Common.targets.targets에서 가져옵니다. 따라서 개별 프로젝트에서 설정하는 것과 관계없이 대부분의 빌드 로직에 정의된 속성과 대상을 재정의하거나, 모든 프로젝트의 속성을 설정할 수 있습니다.

이전 설정을 모두 재정의하는 개별 프로젝트의 속성을 설정하거나 대상을 정의해야 하는 경우, 최종 가져오기 후 프로젝트 파일에 해당 로직을 넣습니다. SDK 스타일 프로젝트에서 이 작업을 수행하려면 우선 SDK 스타일 특성을 해당하는 가져오기로 바꾸어야 합니다. MSBuild 프로젝트 SDK 사용 방법을 참조하세요.

참고

MSBuild 엔진은 프로젝트(모든 PreBuildEvent 포함)의 빌드 실행을 시작하기 전, 평가 중 가져온 모든 파일을 읽으므로 이러한 파일은 PreBuildEvent 또는 빌드 프로세스의 다른 부분에 의해 수정될 수 없습니다. 모든 수정 사항은 MSBuild.exe의 다음 호출 또는 다음 Visual Studio 빌드까지 적용되지 않습니다. 또한 빌드 프로세스에 많은 프로젝트 빌드(예: 멀티 타기팅 또는 종속 프로젝트 빌드)가 포함된 경우, 각 개별 프로젝트 빌드에 대해 평가 수행 시 Directory.build.props를 포함하여 가져온 파일을 읽습니다.

사용 사례: 다단계 병합

해당 표준 솔루션 구조체가 있다고 가정합니다.

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

모든 프로젝트 (1) 의 공통 속성, src 프로젝트 (2-src) 의 공통 속성 및 테스트 프로젝트 (2-test) 의 공통 속성이 있는 것이 바람직할 수도 있습니다.

MSBuild에서 '내부' 파일(2-src2-test)을 '외부' 파일(1)과 올바르게 병합하려면 MSBuild가 Directory.Build.props 파일을 찾았을 때 추가 검사를 중지하도록 고려해야 합니다. 검색을 계속하고 외부 파일에 병합하려면 해당 코드를 모든 내부 파일에 배치합니다.

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

MSBuild의 일반적인 접근 방법의 요약은 다음과 같습니다.

  • 지정된 프로젝트의 경우 MSBuild는 솔루션 구조체에서 위쪽으로 첫 번째 Directory.Build.props를 검색하고, 기본값과 병합한 다음 추가 검사를 중단합니다.
  • 다단계를 찾고 병합하려는 경우 '내부' 파일에서 '외부' 파일을 <Import...>(앞에서 표시)합니다.
  • '외부' 파일이 자체로 상위 항목을 가져오지 않는 경우, 해당 부분에서 스캔을 중단합니다.

간단히 말하면 아무것도 가져오지 않는 첫 번째 Directory.Build.props에서 MSBuild가 중단되는 것입니다.

가져오기 프로세스를 보다 명시적으로 제어하려면 $(DirectoryBuildPropsPath), $(ImportDirectoryBuildProps), $(DirectoryBuildTargetsPath)$(ImportDirectoryBuildTargets) 속성을 사용합니다. $(DirectoryBuildPropsPath) 속성은 사용할 Directory.Build.props 파일의 경로를 지정합니다. 마찬가지로 $(DirectoryBuildTargetsPath) 속성은 Directory.Build.targets 파일의 경로를 지정합니다.

부울 속성 $(ImportDirectoryBuildProps)$(ImportDirectoryBuildTargets)은(는) 기본적으로 true로 설정되므로 MSBuild는 일반적으로 이러한 파일을 검색하지만 MSBuild에서 가져오지 못하도록 false로 설정할 수 있습니다.

예시

해당 예제에서는 전처리된 출력을 사용하여 속성을 설정할 위치의 결정 방법을 보여 줍니다.

설정하려는 특정 속성의 사용량을 분석하는 데 도움이 되도록 MSBuild를 /preprocess 또는 /pp 인수로 실행할 수 있습니다. 출력 텍스트는 묵시적으로 가져온 Microsoft.Common.props와 같은 시스템 가져오기 및 사용자 고유의 가져오기를 포함한 모든 가져오기의 결과입니다. 이 출력을 사용하면 해당 값이 사용되는 위치를 기준으로 속성을 설정해야 하는 위치를 확인할 수 있습니다.

예를 들어, 간단한 .NET Core 또는 .NET 5 이상 콘솔 앱 프로젝트가 있고 일반적으로 obj인 중간 출력 폴더를 사용자 지정하려고 합니다. 이 경로를 지정하는 속성은 BaseIntermediateOutput입니다. 프로젝트 파일의 PropertyGroup 요소에 이미 설정된 다양한 속성(예: TargetFramework)에 배치하려고 하면 프로젝트를 빌드할 때 속성이 적용되지 않는 것을 발견할 수 있습니다. /pp 옵션을 사용하여 MSBuild를 실행하고 BaseIntermediateOutputPath에 대한 출력을 검색하는 경우 그 이유를 확인할 수 있습니다. 이 경우, BaseIntermediateOutput을(를) Microsoft.Common.props에서 읽고 사용합니다.

Microsoft.Common.props에는 BaseIntermediateOutput 속성이 다른 속성 MSBuildProjectExtensionsPath에서 사용되기 전에 해당 위치에서 설정해야 한다는 메모가 있습니다. 또한 BaseIntermediateOutputPath이(가) 처음 설정되면 기존 값에 대한 검사가 있으며, 정의되지 않은 경우 해당 값이 obj로 설정되는 것을 확인할 수 있습니다.

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

따라서 해당 배치는 이와 같은 속성을 설정하기 위해 이보다 이전의 위치에 지정해야 한다는 것을 알려줍니다. 전처리된 출력에서 해당 코드 바로 앞에 Directory.Build.props을(를) 가져온 것을 확인할 수 있으므로 BaseIntermediateOutputPath을(를) 설정할 수 있으며 원하는 효과를 가질 수 있을 만큼 일찍 설정됩니다.

다음 약식 전처리 출력은 BaseIntermediateOutput 설정을 Directory.Build.props에 배치한 결과를 보여 줍니다. 표준 가져오기의 상단에 있는 메모에는 파일 이름 및 해당 파일을 가져오는 이유에 대한 유용한 정보가 포함되어 있습니다.

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