자습서: 코드 생성을 위한 사용자 지정 작업 만들기
이 자습서에서는 코드 생성을 처리하는 C#의 MSBuild에서 사용자 지정 작업을 만든 다음 빌드에서 작업을 사용합니다. 이 예제에서는 MSBuild를 사용하여 정리 및 다시 빌드 작업을 처리하는 방법을 보여 줍니다. 이 예제에서는 입력 파일이 변경된 경우에만 코드가 생성되도록 증분 빌드를 지원하는 방법도 보여줍니다. 설명된 기술은 다양한 코드 생성 시나리오에 적용할 수 있습니다. 또한 이 단계에서는 NuGet을 사용하여 배포 작업을 패키징하는 방법을 보여 줍니다. 이 자습서에는 BinLog 뷰어를 사용하여 문제 해결 환경을 개선하는 선택적 단계가 포함되어 있습니다.
필수 구성 요소
작업, 대상 및 속성과 같은 MSBuild 개념을 이해해야 합니다. MSBuild 개념참조하세요.
이 예제에는 Visual Studio와 함께 설치되지만 별도로 설치할 수도 있는 MSBuild가 필요합니다. Visual Studio 없이 MSBuild 다운로드참조하세요.
코드 예제 소개
이 예제에서는 설정할 값이 포함된 입력 텍스트 파일을 가져와서 이러한 값을 만드는 코드를 사용하여 C# 코드 파일을 만듭니다. 간단한 예제이지만 더 복잡한 코드 생성 시나리오에 동일한 기본 기술을 적용할 수 있습니다.
이 자습서에서는 AppSettingStronglyTyped라는 MSBuild 사용자 지정 작업을 만듭니다. 작업은 텍스트 파일 집합과 다음 형식의 줄이 있는 각 파일을 읽습니다.
propertyName:type:defaultValue
이 코드는 모든 상수와 함께 C# 클래스를 생성합니다. 문제는 빌드를 중지하고 사용자에게 문제를 진단할 수 있는 충분한 정보를 제공해야 합니다.
이 자습서의 전체 샘플 코드는 GitHub의 .NET 샘플 리포지토리에서 사용자 지정 태스크 - 코드 생성에 있습니다.
AppSettingStronglyTyped 프로젝트 만들기
.NET Standard 클래스 라이브러리를 만듭니다. 프레임워크는 .NET Standard 2.0이어야 합니다.
전체 MSBuild(Visual Studio에서 사용하는 MSBuild)와 .NET Core 명령줄에 번들로 제공되는 이식 가능한 MSBuild 간의 차이점을 확인합니다.
- 전체 MSBuild: 이 버전의 MSBuild는 일반적으로 Visual Studio 내에 있습니다. .NET Framework에서 실행됩니다. Visual Studio는 솔루션 또는 프로젝트에서 빌드 실행할 때 이를 사용합니다. 이 버전은 Visual Studio 개발자 명령 프롬프트 또는 PowerShell과 같은 명령줄 환경에서도 사용할 수 있습니다.
- .NET MSBuild: 이 버전의 MSBuild는 .NET Core 명령줄에 번들로 제공됩니다. .NET Core에서 실행됩니다. Visual Studio는 이 버전의 MSBuild를 직접 호출하지 않습니다. Microsoft.NET.Sdk를 사용하여 빌드하는 프로젝트만 지원합니다.
.NET Framework와 다른 .NET 구현(예: .NET Core) 간에 코드를 공유하려는 경우 라이브러리는 .NET Standard 2.0대상으로 지정하고 .NET Framework에서 실행되는 Visual Studio 내에서 실행하려고 합니다. .NET Framework는 .NET Standard 2.1을 지원하지 않습니다.
참조할 MSBuild API 버전 선택
사용자 지정 작업을 컴파일할 때 지원하려는 Visual Studio 및/또는 .NET SDK의 최소 버전과 일치하는 MSBuild API(Microsoft.Build.*
) 버전을 참조해야 합니다. 예를 들어 Visual Studio 2019에서 사용자를 지원하려면 MSBuild 16.11에 대해 빌드해야 합니다.
AppSettingStronglyTyped MSBuild 사용자 지정 작업 만들기
첫 번째 단계는 MSBuild 사용자 지정 작업을 만드는 것입니다. MSBuild 사용자 지정 작업 작성하는 방법에 대한 정보는 다음 단계를 이해하는 데 도움이 될 수 있습니다. MSBuild 사용자 지정 작업은 ITask 인터페이스를 구현하는 클래스입니다.
Microsoft.Build.Utilities.Core NuGet 패키지에 대한 참조를 추가한 다음, Microsoft.Build.Utilities.Task에서 파생된 AppSettingStronglyTyped라는 클래스를 만듭니다.
세 가지 속성을 추가합니다. 이러한 속성은 사용자가 클라이언트 프로젝트에서 작업을 사용할 때 설정하는 작업의 매개 변수를 정의합니다.
//The name of the class which is going to be generated [Required] public string SettingClassName { get; set; } //The name of the namespace where the class is going to be generated [Required] public string SettingNamespaceName { get; set; } //List of files which we need to read with the defined format: 'propertyName:type:defaultValue' per line [Required] public ITaskItem[] SettingFiles { get; set; }
태스크는 SettingFiles 처리하고 클래스
SettingNamespaceName.SettingClassName
생성합니다. 생성된 클래스에는 텍스트 파일의 내용에 따라 상수 집합이 있습니다.작업 출력은 생성된 코드의 파일 이름을 제공하는 문자열이어야 합니다.
// The filename where the class was generated [Output] public string ClassNameFile { get; set; }
사용자 지정 작업을 만들 때 Microsoft.Build.Utilities.Task에서 상속받습니다. 작업을 구현하려면 Execute() 메서드를 재정의합니다.
Execute
메서드는 작업이 성공하면true
반환하고, 그렇지 않으면false
.Task
Microsoft.Build.Framework.ITask 구현하고 일부ITask
멤버의 기본 구현을 제공하며 또한 일부 로깅 기능을 제공합니다. 특히 문제가 발생하고 태스크가 오류 결과(false
)를 반환해야 하는 경우 작업을 진단하고 문제를 해결하기 위해 로그에 상태를 출력하는 것이 중요합니다. 오류 발생 시 클래스는 TaskLoggingHelper.LogError호출하여 오류를 알릴 수 있습니다.public override bool Execute() { //Read the input files and return a IDictionary<string, object> with the properties to be created. //Any format error it will return false and log an error var (success, settings) = ReadProjectSettingFiles(); if (!success) { return !Log.HasLoggedErrors; } //Create the class based on the Dictionary success = CreateSettingClass(settings); return !Log.HasLoggedErrors; }
작업 API를 사용하면 잘못된 내용을 사용자에게 표시하지 않고 오류를 나타내는 false를 반환할 수 있습니다. 부울 코드 대신
!Log.HasLoggedErrors
반환하고 문제가 발생할 때 오류를 기록하는 것이 가장 좋습니다.
로그 오류
오류를 로깅할 때 가장 좋은 방법은 오류를 로깅할 때 줄 번호 및 고유 오류 코드와 같은 세부 정보를 제공하는 것입니다. 다음 코드는 텍스트 입력 파일을 구문 분석하고 오류를 생성한 텍스트 파일의 줄 번호와 함께 TaskLoggingHelper.LogError 메서드를 사용합니다.
private (bool, IDictionary<string, object>) ReadProjectSettingFiles()
{
var values = new Dictionary<string, object>();
foreach (var item in SettingFiles)
{
int lineNumber = 0;
var settingFile = item.GetMetadata("FullPath");
foreach (string line in File.ReadLines(settingFile))
{
lineNumber++;
var lineParse = line.Split(':');
if (lineParse.Length != 3)
{
Log.LogError(subcategory: null,
errorCode: "APPS0001",
helpKeyword: null,
file: settingFile,
lineNumber: lineNumber,
columnNumber: 0,
endLineNumber: 0,
endColumnNumber: 0,
message: "Incorrect line format. Valid format prop:type:defaultvalue");
return (false, null);
}
var value = GetValue(lineParse[1], lineParse[2]);
if (!value.Item1)
{
return (value.Item1, null);
}
values[lineParse[0]] = value.Item2;
}
}
return (true, values);
}
이전 코드에 표시된 기술을 사용하면 텍스트 입력 파일 구문의 오류가 유용한 진단 정보와 함께 빌드 오류로 표시됩니다.
Microsoft (R) Build Engine version 17.2.0 for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.
Build started 2/16/2022 10:23:24 AM.
Project "S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild" on node 1 (default targets).
S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\error-prop.setting(1): error APPS0001: Incorrect line format. Valid format prop:type:defaultvalue [S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild]
Done Building Project "S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild" (default targets) -- FAILED.
Build FAILED.
"S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild" (default target) (1) ->
(generateSettingClass target) ->
S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\error-prop.setting(1): error APPS0001: Incorrect line format. Valid format prop:type:defaultvalue [S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild]
0 Warning(s)
1 Error(s)
작업 중 예외를 처리할 때 TaskLoggingHelper.LogErrorFromException 메서드를 사용합니다. 예를 들어 예외가 발생한 호출 스택을 가져와 오류 출력을 개선하는 데 도움이 됩니다.
catch (Exception ex)
{
// This logging helper method is designed to capture and display information
// from arbitrary exceptions in a standard way.
Log.LogErrorFromException(ex, showStackTrace: true);
return false;
}
이러한 입력을 사용하여 생성된 코드 파일의 텍스트를 빌드하는 다른 메서드의 구현은 여기에 표시되지 않습니다. 샘플 리포지토리의 AppSettingStronglyTyped.cs 참조하세요.
예제 코드는 빌드 프로세스 중에 C# 코드를 생성합니다. 이 작업은 다른 C# 클래스와 같으므로 이 자습서를 완료하면 사용자 지정하고 사용자 고유의 시나리오에 필요한 기능을 추가할 수 있습니다.
콘솔 앱 생성 및 사용자 지정 작업 사용
이 섹션에서는 작업을 사용하는 표준 .NET Core 콘솔 앱을 만듭니다.
중요하다
사용하려는 동일한 MSBuild 프로세스에서 MSBuild 사용자 지정 작업을 생성하지 않도록 하는 것이 중요합니다. 새 프로젝트는 완전히 다른 Visual Studio 솔루션에 있어야 합니다. 또는 새 프로젝트는 미리 생성된 dll을 사용하고 표준 출력에서 다시 배치합니다.
새 Visual Studio 솔루션에서 .NET 콘솔 프로젝트 MSBuildConsoleExample을 만듭니다.
작업을 배포하는 일반적인 방법은 NuGet 패키지를 사용하는 것이지만 개발 및 디버깅 중에는
.props
및.targets
대한 모든 정보를 애플리케이션의 프로젝트 파일에 직접 포함하고 작업을 다른 사용자에게 배포할 때 NuGet 형식으로 이동할 수 있습니다.코드 생성 작업을 사용하도록 프로젝트 파일을 수정합니다. 이 섹션의 코드 목록에는 작업을 참조하고, 작업에 대한 입력 매개 변수를 설정하고, 생성된 코드 파일이 예상대로 제거되도록 정리 및 다시 작성 작업을 처리하기 위한 대상을 작성한 후 수정된 프로젝트 파일이 표시됩니다.
작업은 UsingTask 요소를 사용하여 MSBuild에서 등록됩니다.
UsingTask
요소는 작업을 등록합니다. MSBuild에 태스크의 이름과 작업 클래스가 포함된 어셈블리를 찾아서 실행하는 방법을 알려줍니다. 어셈블리 경로는 프로젝트 파일을 기준으로 합니다.PropertyGroup
태스크에 정의된 속성에 해당하는 속성 정의를 포함합니다. 이러한 속성은 특성을 사용하여 설정되며 작업 이름은 요소 이름으로 사용됩니다.TaskName
어셈블리에서 참조할 작업의 이름입니다. 이 특성은 항상 완전히 지정된 네임스페이스를 사용해야 합니다.AssemblyFile
어셈블리의 파일 경로입니다.작업을 호출하려면 해당 대상에 작업을 추가합니다. 이 경우
GenerateSetting
.대상
ForceGenerateOnRebuild
생성된 파일을 삭제하여 정리 및 다시 작성 작업을 처리합니다.CoreClean
특성을AfterTargets
설정하여CoreClean
대상 이후에 실행되도록 설정됩니다.<Project Sdk="Microsoft.NET.Sdk"> <UsingTask TaskName="AppSettingStronglyTyped.AppSettingStronglyTyped" AssemblyFile="..\..\AppSettingStronglyTyped\AppSettingStronglyTyped\bin\Debug\netstandard2.0\AppSettingStronglyTyped.dll"/> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net6.0</TargetFramework> <RootFolder>$(MSBuildProjectDirectory)</RootFolder> <SettingClass>MySetting</SettingClass> <SettingNamespace>MSBuildConsoleExample</SettingNamespace> <SettingExtensionFile>mysettings</SettingExtensionFile> </PropertyGroup> <ItemGroup> <SettingFiles Include="$(RootFolder)\*.mysettings" /> </ItemGroup> <Target Name="GenerateSetting" BeforeTargets="CoreCompile" Inputs="@(SettingFiles)" Outputs="$(RootFolder)\$(SettingClass).generated.cs"> <AppSettingStronglyTyped SettingClassName="$(SettingClass)" SettingNamespaceName="$(SettingNamespace)" SettingFiles="@(SettingFiles)"> <Output TaskParameter="ClassNameFile" PropertyName="SettingClassFileName" /> </AppSettingStronglyTyped> <ItemGroup> <Compile Remove="$(SettingClassFileName)" /> <Compile Include="$(SettingClassFileName)" /> </ItemGroup> </Target> <Target Name="ForceReGenerateOnRebuild" AfterTargets="CoreClean"> <Delete Files="$(RootFolder)\$(SettingClass).generated.cs" /> </Target> </Project>
메모
이 코드는
CoreClean
같은 대상을 재정의하는 대신 대상 (BeforeTarget 및 AfterTarget)순서를 지정하는 다른 방법을 사용합니다. SDK 스타일 프로젝트는 프로젝트 파일의 마지막 줄 뒤의 대상을 암시적으로 가져옵니다. 즉, 가져오기를 수동으로 지정하지 않으면 기본 대상을 재정의할 수 없습니다. 사전 정의된 대상 을 재정의하는 것에 대한 내용은을 참조하세요.Inputs
및Outputs
특성은 증분 빌드에 대한 정보를 제공하여 MSBuild의 효율성을 높이는 데 도움이 됩니다. 입력 날짜를 출력과 비교하여 대상을 실행해야 하는지 또는 이전 빌드의 출력을 다시 사용할 수 있는지 확인합니다.검색할 확장이 정의된 입력 텍스트 파일을 만듭니다. 기본 확장을 사용하여 루트에
MyValues.mysettings
을 만들고, 다음 내용을 포함하십시오.Greeting:string:Hello World!
다시 빌드하고 생성된 파일을 만들고 빌드해야 합니다. 프로젝트 폴더에서 MySetting.generated.cs 파일을 확인합니다.
MySetting 클래스가 잘못된 네임스페이스에 있으므로 이제 앱 네임스페이스를 사용하도록 변경합니다. 프로젝트 파일을 열고 다음 코드를 추가합니다.
<PropertyGroup> <SettingNamespace>MSBuildConsoleExample</SettingNamespace> </PropertyGroup>
다시 빌드하고 클래스가
MSBuildConsoleExample
네임스페이스에 있는지 확인합니다. 이러한 방식으로 입력으로 사용할 생성된 클래스 이름(SettingClass
), 텍스트 확장 파일(SettingExtensionFile
) 및 원하는 경우 해당 클래스의 위치(RootFolder
)를 다시 정의할 수 있습니다.Program.cs
열고 하드 코딩된 'Hello World!'를 변경합니다. 사용자 정의 상수로:static void Main(string[] args) { Console.WriteLine(MySetting.Greeting); }
프로그램을 실행합니다. 생성된 클래스에서 인사말을 출력합니다.
(선택 사항) 빌드 프로세스 중 이벤트 기록
명령줄 명령을 사용하여 컴파일할 수 있습니다. 프로젝트 폴더로 이동합니다.
-bl
(이진 로그) 옵션을 사용하여 이진 로그를 생성합니다. 이진 로그에는 빌드 프로세스 중에 진행 중인 작업을 파악하는 데 유용한 정보가 있습니다.
# Using dotnet MSBuild (run core environment)
dotnet build -bl
# or full MSBuild (run on net framework environment; this is used by Visual Studio)
msbuild -bl
두 명령 모두 로그 파일 msbuild.binlog
생성합니다. 이 파일은 MSBuild 이진 및 구조적 로그 뷰어사용하여 열 수 있습니다.
/t:rebuild
옵션은 다시 빌드 대상을 실행하는 것을 의미합니다. 생성된 코드 파일을 강제로 다시 생성합니다.
축하합니다! 코드를 생성하는 작업을 만들어서 빌드 과정에서 사용했습니다.
배포 작업을 패키지합니다.
일부 프로젝트 또는 단일 솔루션에서만 사용자 지정 작업을 사용해야 하는 경우 작업을 원시 어셈블리로 사용하는 것이 필요할 수 있지만 작업을 다른 곳에서 사용하거나 다른 사용자와 공유하도록 준비하는 가장 좋은 방법은 NuGet 패키지입니다.
MSBuild 작업 패키지에는 라이브러리 NuGet 패키지와 몇 가지 주요 차이점이 있습니다.
- 이러한 종속성을 소비 프로젝트에 노출하는 대신 자체 어셈블리 종속성을 번들로 묶어야 합니다.
- 필요한 어셈블리를
lib/<target framework>
폴더에 패키징하지 않습니다. NuGet이 작업을 사용하는 모든 패키지에 어셈블리를 포함하게 되므로 - Microsoft.Build 어셈블리에 대한 컴파일하기만. 런타임 시 실제 MSBuild 엔진에서 제공되므로 패키지에 포함될 필요가 없습니다.
- MSBuild가 작업의 종속성(특히 네이티브 종속성)을 일관된 방식으로 로드하는 데 도움이 되는 특수
.deps.json
파일을 생성합니다.
이러한 모든 목표를 달성하려면 익숙한 것 이상으로 표준 프로젝트 파일을 몇 가지 변경해야 합니다.
NuGet 패키지 만들기
NuGet 패키지를 만드는 것이 사용자 지정 작업을 다른 사람에게 배포하는 권장 방법입니다.
패키지 생성 준비
NuGet 패키지 생성을 준비하려면 프로젝트 파일을 일부 변경하여 패키지를 설명하는 세부 정보를 지정합니다. 만든 초기 프로젝트 파일은 다음 코드와 유사합니다.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.0.0" />
</ItemGroup>
</Project>
NuGet 패키지를 생성하려면 다음 코드를 추가하여 패키지의 속성을 설정합니다. Pack 설명서지원되는 MSBuild 속성의 전체 목록을 볼 수 있습니다.
<PropertyGroup>
...
<IsPackable>true</IsPackable>
<Version>1.0.0</Version>
<Title>AppSettingStronglyTyped</Title>
<Authors>Your author name</Authors>
<Description>Generates a strongly typed setting class base on a text file.</Description>
<PackageTags>MyTags</PackageTags>
<Copyright>Copyright ©Contoso 2022</Copyright>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
...
</PropertyGroup>
종속성이 출력 디렉터리에 복사되도록 하려면 CopyLocalLockFileAssemblies 속성이 필요합니다.
종속성을 프라이빗으로 표시
MSBuild 작업의 종속성은 패키지 내에 패키징되어야 합니다. 일반 패키지 참조로 표현할 수 없습니다. 패키지는 외부 사용자에게 일반 종속성을 노출하지 않습니다. 이렇게 하려면 두 단계를 수행합니다. 즉, 어셈블리를 프라이빗으로 표시하고 실제로 생성된 패키지에 포함합니다. 이 예제에서, 작업이 Microsoft.Extensions.DependencyInjection
에 의존한다고 가정하므로, 버전 PackageReference
에서 Microsoft.Extensions.DependencyInjection
을 6.0.0
에 추가합니다.
<ItemGroup>
<PackageReference
Include="Microsoft.Build.Utilities.Core"
Version="17.0.0" />
<PackageReference
Include="Microsoft.Extensions.DependencyInjection"
Version="6.0.0" />
</ItemGroup>
이제 이 작업 프로젝트의 모든 종속성을 PackageReference
과 ProjectReference
로 표시하고, PrivateAssets="all"
특성을 부여합니다. 이렇게 하면 NuGet에서 이러한 종속성을 사용 중인 프로젝트에 노출하지 않도록 합니다. 종속성 자산 제어하는 방법에 대한 자세한 내용은NuGet 설명서에서 확인할 수 있습니다.
<ItemGroup>
<PackageReference
Include="Microsoft.Build.Utilities.Core"
Version="17.0.0"
PrivateAssets="all"
/>
<PackageReference
Include="Microsoft.Extensions.DependencyInjection"
Version="6.0.0"
PrivateAssets="all"
/>
</ItemGroup>
패키지에 종속성을 번들로 묶다
또한 종속성의 런타임 자산을 작업 패키지에 포함해야 합니다. 여기에는 BuildOutputInPackage
ItemGroup에 종속성을 추가하는 MSBuild 대상과 해당 BuildOutputInPackage
항목의 레이아웃을 제어하는 몇 가지 속성이 있습니다. 이 프로세스 에 대한 자세한 내용은 NuGet 설명서에서 확인할 수 있습니다.
<PropertyGroup>
...
<!-- This target will run when MSBuild is collecting the files to be packaged, and we'll implement it below. This property controls the dependency list for this packaging process, so by adding our custom property we hook ourselves into the process in a supported way. -->
<TargetsForTfmSpecificBuildOutput>
$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage
</TargetsForTfmSpecificBuildOutput>
<!-- This property tells MSBuild where the root folder of the package's build assets should be. Because we are not a library package, we should not pack to 'lib'. Instead, we choose 'tasks' by convention. -->
<BuildOutputTargetFolder>tasks</BuildOutputTargetFolder>
<!-- NuGet does validation that libraries in a package are exposed as dependencies, but we _explicitly_ do not want that behavior for MSBuild tasks. They are isolated by design. Therefore we ignore this specific warning. -->
<NoWarn>NU5100</NoWarn>
<!-- Suppress NuGet warning NU5128. -->
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
...
</PropertyGroup>
...
<!-- This is the target we defined above. It's purpose is to add all of our PackageReference and ProjectReference's runtime assets to our package output. -->
<Target
Name="CopyProjectReferencesToPackage"
DependsOnTargets="ResolveReferences">
<ItemGroup>
<!-- The TargetPath is the path inside the package that the source file will be placed. This is already precomputed in the ReferenceCopyLocalPaths items' DestinationSubPath, so reuse it here. -->
<BuildOutputInPackage
Include="@(ReferenceCopyLocalPaths)"
TargetPath="%(ReferenceCopyLocalPaths.DestinationSubPath)" />
</ItemGroup>
</Target>
Microsoft.Build.Utilities.Core 어셈블리를 번들로 묶지 마세요.
위에서 설명한 대로 이 종속성은 런타임에 MSBuild 자체에서 제공되므로 패키지에 번들로 묶을 필요가 없습니다. 이렇게 하려면 ExcludeAssets="Runtime"
에 PackageReference
특성을 추가합니다.
...
<PackageReference
Include="Microsoft.Build.Utilities.Core"
Version="17.0.0"
PrivateAssets="all"
ExcludeAssets="Runtime"
/>
...
deps.json 파일 생성 및 포함
MSBuild에서 deps.json
파일을 사용하여 올바른 버전의 종속성이 로드되도록 할 수 있습니다. 라이브러리에 대해 기본적으로 생성되지 않으므로 파일이 생성되도록 일부 MSBuild 속성을 추가해야 합니다. 그런 다음 패키지 종속성에 대해 했던 것과 비슷하게 패키지 출력에 포함할 대상을 추가합니다.
<PropertyGroup>
...
<!-- Tell the SDK to generate a deps.json file -->
<GenerateDependencyFile>true</GenerateDependencyFile>
...
</PropertyGroup>
...
<!-- This target adds the generated deps.json file to our package output -->
<Target
Name="AddBuildDependencyFileToBuiltProjectOutputGroupOutput"
BeforeTargets="BuiltProjectOutputGroup"
Condition=" '$(GenerateDependencyFile)' == 'true'">
<ItemGroup>
<BuiltProjectOutputGroupOutput
Include="$(ProjectDepsFilePath)"
TargetPath="$(ProjectDepsFileName)"
FinalOutputPath="$(ProjectDepsFilePath)" />
</ItemGroup>
</Target>
패키지에 MSBuild 속성 및 대상 포함
이 섹션에 대한 배경 지식은 먼저 속성과 대상에 대해 읽고, 그런 다음 방법으로 NuGet 패키지에 속성과 대상을 포함하는 방법을 알아보세요.
경우에 따라 빌드 중에 사용자 지정 도구 또는 프로세스를 실행하는 등 패키지를 사용하는 프로젝트에 사용자 지정 빌드 대상 또는 속성을 추가할 수 있습니다. 프로젝트의 <package_id>.targets
폴더 안에 파일을 <package_id>.props
형식 또는 build
형식으로 배치하여 이를 수행합니다.
프로젝트 루트 빌드 폴더의 파일은 모든 대상 프레임워크에 적합한 것으로 간주됩니다.
이 섹션에서는 nuGet 패키지에 포함되고 참조 프로젝트에서 자동으로 로드되는 .props
및 .targets
파일에서 작업 구현을 연결합니다.
작업의 프로젝트 파일에서 AppSettingStronglyTyped.csproj 다음 코드를 추가합니다.
<ItemGroup> <!-- these lines pack the build props/targets files to the `build` folder in the generated package. by convention, the .NET SDK will look for build\<Package Id>.props and build\<Package Id>.targets for automatic inclusion in the build. --> <Content Include="build\AppSettingStronglyTyped.props" PackagePath="build\" /> <Content Include="build\AppSettingStronglyTyped.targets" PackagePath="build\" /> </ItemGroup>
빌드 폴더를 만들고 해당 폴더에
AppSettingStronglyTyped.props
및 AppSettingStronglyTyped.targets두 개의 텍스트 파일을 추가합니다.AppSettingStronglyTyped.props
는 Microsoft.Common.props초기에 불러와져서, 이후에 정의된 속성들은 사용할 수 없습니다. 따라서 아직 정의되지 않은 속성을 참조하지 마세요. 비어 있는 것으로 평가합니다.Directory.Build.targets은 NuGet 패키지에서 파일을 가져온 후
.targets
에서 가져옵니다. 따라서 대부분의 빌드 논리에 정의된 속성과 대상을 재정의하거나 개별 프로젝트 집합에 관계없이 모든 프로젝트에 대한 속성을 설정할 수 있습니다. 가져오기 순서을 참조하세요.AppSettingStronglyTyped.props 태스크를 포함하고 기본값을 사용하여 일부 속성을 정의합니다.
<?xml version="1.0" encoding="utf-8" ?> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <!--defining properties interesting for my task--> <PropertyGroup> <!--The folder where the custom task will be present. It points to inside the nuget package. --> <_AppSettingsStronglyTyped_TaskFolder>$(MSBuildThisFileDirectory)..\tasks\netstandard2.0</_AppSettingsStronglyTyped_TaskFolder> <!--Reference to the assembly which contains the MSBuild Task--> <CustomTasksAssembly>$(_AppSettingsStronglyTyped_TaskFolder)\$(MSBuildThisFileName).dll</CustomTasksAssembly> </PropertyGroup> <!--Register our custom task--> <UsingTask TaskName="$(MSBuildThisFileName).AppSettingStronglyTyped" AssemblyFile="$(CustomTasksAssembly)"/> <!--Task parameters default values, this can be overridden--> <PropertyGroup> <RootFolder Condition="'$(RootFolder)' == ''">$(MSBuildProjectDirectory)</RootFolder> <SettingClass Condition="'$(SettingClass)' == ''">MySetting</SettingClass> <SettingNamespace Condition="'$(SettingNamespace)' == ''">example</SettingNamespace> <SettingExtensionFile Condition="'$(SettingExtensionFile)' == ''">mysettings</SettingExtensionFile> </PropertyGroup> </Project>
AppSettingStronglyTyped.props
파일은 패키지가 설치될 때 자동으로 포함됩니다. 그런 다음 클라이언트에 사용 가능한 작업과 일부 기본값이 있습니다. 그러나, 그것은 결코 사용 되지 않습니다. 이 코드를 실행하려면 패키지가 설치될 때 자동으로 포함되는AppSettingStronglyTyped.targets
파일에 일부 대상을 정의합니다.<?xml version="1.0" encoding="utf-8" ?> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <!--Defining all the text files input parameters--> <ItemGroup> <SettingFiles Include="$(RootFolder)\*.$(SettingExtensionFile)" /> </ItemGroup> <!--A target that generates code, which is executed before the compilation--> <Target Name="BeforeCompile" Inputs="@(SettingFiles)" Outputs="$(RootFolder)\$(SettingClass).generated.cs"> <!--Calling our custom task--> <AppSettingStronglyTyped SettingClassName="$(SettingClass)" SettingNamespaceName="$(SettingNamespace)" SettingFiles="@(SettingFiles)"> <Output TaskParameter="ClassNameFile" PropertyName="SettingClassFileName" /> </AppSettingStronglyTyped> <!--Our generated file is included to be compiled--> <ItemGroup> <Compile Remove="$(SettingClassFileName)" /> <Compile Include="$(SettingClassFileName)" /> </ItemGroup> </Target> <!--The generated file is deleted after a general clean. It will force the regeneration on rebuild--> <Target Name="AfterClean"> <Delete Files="$(RootFolder)\$(SettingClass).generated.cs" /> </Target> </Project>
첫 번째 단계는 읽을 텍스트 파일(둘 이상일 수 있음)을 나타내는 ItemGroup만드는 것이며, 이는 작업 매개 변수 중 일부가 될 것입니다. 찾는 위치 및 확장에 대한 기본값이 있지만 클라이언트 MSBuild 프로젝트 파일의 속성을 정의하는 값을 재정의할 수 있습니다.
그런 다음 두 개의 MSBuild 대상을정의합니다. MSBuild 프로세스를 확장하고 미리 정의된 대상을재정의하면서.
BeforeCompile
: 목표는 사용자 지정 작업을 호출하여 클래스를 생성하고 컴파일할 클래스를 포함하는 것입니다. 이 대상의 태스크는 코어 컴파일이 완료되기 전에 삽입됩니다. 입력 및 출력 필드는 증분 빌드 과 관련이 있습니다. 모든 출력 항목이 up-to-date인 경우 MSBuild는 대상을 건너뜁니다. 대상의 증분 빌드를 사용하면 빌드 성능을 크게 향상시킬 수 있습니다. 출력 파일이 입력 파일 또는 파일과 같은 나이 또는 최신인 경우 항목은 up-to-date로 간주됩니다.AfterClean
: 일반적인 정리가 수행된 후 생성된 클래스 파일을 삭제하는 것이 목표입니다. 이 대상의 작업은 핵심 정리 기능이 호출된 후에 삽입됩니다. 다시 빌드 대상이 실행될 때 코드 생성 단계가 반복되도록 합니다.
NuGet 패키지 생성
NuGet 패키지를 생성하려면 Visual Studio를 사용할 수 있습니다(솔루션 탐색기프로젝트 노드를 마우스 오른쪽 단추로 클릭하고 팩 선택). 명령줄을 사용하여 수행할 수도 있습니다. 작업 프로젝트 파일 AppSettingStronglyTyped.csproj
있는 폴더로 이동하고 다음 명령을 실행합니다.
// -o is to define the output; the following command chooses the current folder.
dotnet pack -o .
축하합니다! NuGet 패키지 \AppSettingStronglyTyped\AppSettingStronglyTyped\AppSettingStronglyTyped.1.0.0.nupkg를 생성했습니다.
패키지에는 확장 .nupkg
있으며 압축된 zip 파일입니다. zip 도구를 사용하여 열 수 있습니다.
.target
및 .props
파일은 build
폴더에 있습니다.
.dll
파일은 lib\netstandard2.0\
폴더에 있습니다.
AppSettingStronglyTyped.nuspec
파일은 루트 수준에 있습니다.
(선택 사항) 다중 대상 지정 지원
가능한 가장 광범위한 사용자 기반을 지원하려면 Full
(.NET Framework) 및 Core
(.NET 5 이상 포함) MSBuild 배포를 모두 지원하는 것이 좋습니다.
'normal' .NET SDK 프로젝트의 경우 다중 대상 지정은 프로젝트 파일에서 여러 TargetFrameworks를 설정하는 것을 의미합니다. 이렇게 하면 두 TargetFrameworkMonikers 모두에 대해 빌드가 트리거되고 전체 결과를 단일 아티팩트로 패키지할 수 있습니다.
MSBuild의 전체 스토리는 아닙니다. MSBuild에는 Visual Studio와 .NET SDK라는 두 가지 기본 배송 차량이 있습니다. 이러한 환경은 매우 다른 런타임 환경입니다. 하나는 .NET Framework 런타임에서 실행되고 다른 하나는 CoreCLR에서 실행됩니다. 즉, 코드가 netstandard2.0을 대상으로 할 수 있지만 작업 논리는 현재 사용 중인 MSBuild 런타임 형식에 따라 차이가 있을 수 있습니다. 실제로 .NET 5.0 이상에는 새 API가 너무 많기 때문에 여러 TargetFrameworkMonikers에 대해 MSBuild 작업 소스 코드를 다중 대상 지정하고 여러 MSBuild 런타임 형식에 대한 MSBuild 대상 논리를 다중 대상 지정하는 것이 좋습니다.
다중 타겟팅에 필요한 변경 사항
여러 TFM(TargetFrameworkMonikers)을 대상으로 지정하려면:
net472
및net6.0
TFM을 사용하도록 프로젝트 파일을 변경합니다(후자는 대상으로 지정할 SDK 수준에 따라 변경될 수 있음). .NET Core 3.1이 지원에서 벗어날 때까지netcoreapp3.1
대상으로 지정할 수 있습니다. 이렇게 하면 패키지 폴더 구조가tasks/
tasks/<TFM>/
변경합니다.<TargetFrameworks>net472;net6.0</TargetFrameworks>
올바른 TFM을 사용하여 작업을 로드하도록
.targets
파일을 업데이트합니다. 필요한 TFM은 위에서 선택한 .NET TFM에 따라 변경되지만net472
및net6.0
대상으로 하는 프로젝트의 경우 다음과 같은 속성이 있습니다.
<AppSettingStronglyTyped_TFM Condition=" '$(MSBuildRuntimeType)' != 'Core' ">net472</AppSettingStronglyTyped_TFM>
<AppSettingStronglyTyped_TFM Condition=" '$(MSBuildRuntimeType)' == 'Core' ">net6.0</AppSettingStronglyTyped_TFM>
이 코드는 MSBuildRuntimeType
속성을 활성 호스팅 환경의 프록시로 사용합니다. 이 속성이 설정되면 UsingTask
사용하여 올바른 AssemblyFile
로드할 수 있습니다.
<UsingTask
AssemblyFile="$(MSBuildThisFileDirectory)../tasks/$(AppSettingStronglyTyped_TFM)/AppSettingStronglyTyped.dll"
TaskName="AppSettingStrongTyped.AppSettingStronglyTyped" />
다음 단계
대부분의 작업에는 실행 파일 호출이 포함됩니다. 일부 시나리오에서는 Exec 작업을 사용할 수 있지만 Exec 작업의 제한 사항이 문제인 경우 사용자 지정 작업을 만들 수도 있습니다. 다음 자습서에서는 보다 현실적인 코드 생성 시나리오를 사용하여 REST API에 대한 클라이언트 코드를 생성하는 사용자 지정 작업을 만드는 두 옵션을 안내합니다.
또는 사용자 지정 작업을 테스트하는 방법을 알아봅니다.