.NET 프로젝트 구조 및 빌드 구성
이 스킬을 사용해야 하는 경우
다음과 같은 경우 이 스킬을 사용하세요:
- 최신 모범 사례로 새 .NET 솔루션을 설정할 때
- 여러 프로젝트에 걸쳐 중앙 집중식 빌드 속성을 구성할 때
- 중앙 패키지 버전 관리를 구현할 때
- 디버깅 및 NuGet 패키지를 위해 SourceLink를 설정할 때
- 릴리스 노트로 버전 관리를 자동화할 때
- 일관된 빌드를 위해 SDK 버전을 고정할 때
관련 스킬
dotnet-local-tools- dotnet-tools.json으로 로컬 .NET 도구 관리microsoft-extensions-configuration- 구성 유효성 검사 패턴
솔루션 파일 형식 (.slnx)
.slnx 형식은 .NET 9에서 도입된 최신 XML 기반 솔루션 파일 형식입니다. 기존 .sln 형식을 대체합니다.
기존 .sln 대비 장점
| 항목 | .sln (레거시) | .slnx (최신) |
|---|---|---|
| 형식 | 사용자 지정 텍스트 형식 | 표준 XML |
| 가독성 | GUID, 난해한 구문 | 깔끔하고 사람이 읽기 쉬움 |
| 버전 관리 | diff/merge 어려움 | diff/merge 용이 |
| 편집 | IDE 필요 | 모든 텍스트 편집기 가능 |
버전 요구 사항
| 도구 | 최소 버전 |
|---|---|
| .NET SDK | 9.0.200 |
| Visual Studio | 17.13 |
| MSBuild | Visual Studio Build Tools 17.13 |
참고: .NET 10부터 dotnet new sln은 기본적으로 .slnx 파일을 생성합니다. .NET 9에서는 명시적으로 마이그레이션하거나 형식을 지정해야 합니다.
예제 .slnx 파일
<Solution>
<Folder Name="/build/">
<File Path="Directory.Build.props" />
<File Path="Directory.Packages.props" />
<File Path="global.json" />
<File Path="NuGet.Config" />
<File Path="README.md" />
</Folder>
<Folder Name="/src/">
<Project Path="src/MyApp/MyApp.csproj" />
<Project Path="src/MyApp.Core/MyApp.Core.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/MyApp.Tests/MyApp.Tests.csproj" />
</Folder>
</Solution>
.sln에서 .slnx로 마이그레이션
dotnet sln migrate 명령을 사용하여 기존 솔루션을 변환합니다:
# 특정 솔루션 파일 마이그레이션
dotnet sln MySolution.sln migrate
# 또는 디렉토리에 .sln 파일이 하나만 있으면 다음 명령어 실행:
dotnet sln migrate
중요: 동일한 리포지토리에 .sln과 .slnx 파일을 함께 두지 마십시오. 이는 자동 솔루션 감지에 문제를 일으키고 동기화 문제를 초래할 수 있습니다. 마이그레이션 후에는 이전 .sln 파일을 삭제하세요.
Visual Studio에서도 마이그레이션할 수 있습니다:
- 솔루션 열기
- 솔루션 탐색기에서 솔루션 선택
- 파일 > 솔루션 다른 이름으로 저장... 으로 이동
- "파일 형식 저장"을 XML 솔루션 파일(*.slnx) 로 변경
새 .slnx 솔루션 만들기
# .NET 10+: 기본적으로 .slnx 생성
dotnet new sln --name MySolution
# .NET 9: 형식을 명시적으로 지정
dotnet new sln --name MySolution --format slnx
# 프로젝트 추가 (두 형식 모두 동일하게 작동)
dotnet sln add src/MyApp/MyApp.csproj
권장 사항
.NET 9.0.200 이상을 사용 중이라면 솔루션을 .slnx로 마이그레이션하세요. 장점은 상당합니다:
- 병합 충돌이 훨씬 적음 (무작위 GUID 변경 없음)
- 모든 텍스트 편집기에서 사람이 읽고 편집 가능
- 최신
.csproj형식과 일관성 - 풀 리퀘스트에서 더 나은 diff/리뷰 경험
Directory.Build.props
Directory.Build.props는 디렉터리 트리의 모든 프로젝트에 적용되는 중앙 집중식 빌드 구성을 제공합니다. 솔루션 루트에 배치하세요.
전체 예제
<Project>
<!-- 메타데이터 -->
<PropertyGroup>
<Authors>Your Team</Authors>
<Company>Your Company</Company>
<!-- 동적 저작권 연도 - 자동 업데이트 -->
<Copyright>Copyright © 2020-$([System.DateTime]::Now.Year) Your Company</Copyright>
<Product>Your Product</Product>
<PackageProjectUrl>https://github.com/yourorg/yourrepo</PackageProjectUrl>
<RepositoryUrl>https://github.com/yourorg/yourrepo</RepositoryUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageTags>your;tags;here</PackageTags>
</PropertyGroup>
<!-- C# 언어 설정 -->
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>$(NoWarn);CS1591</NoWarn> <!-- 누락된 XML 주석 -->
</PropertyGroup>
<!-- 버전 관리 -->
<PropertyGroup>
<VersionPrefix>1.0.0</VersionPrefix>
<PackageReleaseNotes>See RELEASE_NOTES.md</PackageReleaseNotes>
</PropertyGroup>
<!-- 대상 프레임워크 정의 (재사용 가능한 속성) -->
<PropertyGroup>
<NetStandardLibVersion>netstandard2.0</NetStandardLibVersion>
<NetLibVersion>net8.0</NetLibVersion>
<NetTestVersion>net9.0</NetTestVersion>
</PropertyGroup>
<!-- SourceLink 구성 -->
<PropertyGroup>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
</ItemGroup>
<!-- NuGet 패키지 자산 -->
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)logo.png" Pack="true" PackagePath="\" />
<None Include="$(MSBuildThisFileDirectory)README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<PropertyGroup>
<PackageIcon>logo.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<!-- 전역 Using 문 -->
<ItemGroup>
<Using Include="System.Collections.Immutable" />
</ItemGroup>
</Project>
주요 패턴
동적 저작권 연도
<Copyright>Copyright © 2020-$([System.DateTime]::Now.Year) Your Company</Copyright>
MSBuild 속성 함수를 사용하여 빌드 시 현재 연도를 삽입합니다. 수동 업데이트가 필요하지 않습니다.
재사용 가능한 대상 프레임워크 속성
대상 프레임워크를 한 번 정의하고 모든 곳에서 참조:
<!-- Directory.Build.props에서 -->
<PropertyGroup>
<NetLibVersion>net8.0</NetLibVersion>
<NetTestVersion>net9.0</NetTestVersion>
</PropertyGroup>
<!-- MyApp.csproj에서 -->
<PropertyGroup>
<TargetFramework>$(NetLibVersion)</TargetFramework>
</PropertyGroup>
<!-- MyApp.Tests.csproj에서 -->
<PropertyGroup>
<TargetFramework>$(NetTestVersion)</TargetFramework>
</PropertyGroup>
NuGet 패키지를 위한 SourceLink
SourceLink을 사용하면 NuGet 패키지의 단계별 디버깅이 가능합니다:
<PropertyGroup>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup>
<!-- 소스 제어에 맞는 공급자를 선택하세요 -->
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
<!-- 또는: Microsoft.SourceLink.AzureRepos.Git -->
<!-- 또는: Microsoft.SourceLink.GitLab -->
<!-- 또는: Microsoft.SourceLink.Bitbucket.Git -->
</ItemGroup>
Directory.Packages.props - 중앙 패키지 관리
중앙 패키지 관리(CPM)는 모든 NuGet 패키지 버전에 대한 단일 진실 공급원을 제공합니다.
설정
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<!-- 관련 패키지에 대한 버전 변수 정의 -->
<PropertyGroup>
<AkkaVersion>1.5.35</AkkaVersion>
<AspireVersion>9.1.0</AspireVersion>
</PropertyGroup>
<!-- 애플리케이션 종속성 -->
<ItemGroup Label="App Dependencies">
<PackageVersion Include="Akka" Version="$(AkkaVersion)" />
<PackageVersion Include="Akka.Cluster" Version="$(AkkaVersion)" />
<PackageVersion Include="Akka.Persistence" Version="$(AkkaVersion)" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
</ItemGroup>
<!-- 빌드/도구 종속성 -->
<ItemGroup Label="Build Dependencies">
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
</ItemGroup>
<!-- 테스트 종속성 -->
<ItemGroup Label="Test Dependencies">
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.1" />
<PackageVersion Include="FluentAssertions" Version="7.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.3" />
</ItemGroup>
</Project>
패키지 사용 (버전 불필요)
<!-- MyApp.csproj에서 -->
<ItemGroup>
<PackageReference Include="Akka" />
<PackageReference Include="Akka.Cluster" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>
<!-- MyApp.Tests.csproj에서 -->
<ItemGroup>
<PackageReference Include="xunit" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
</ItemGroup>
장점
- 단일 진실 공급원 - 모든 버전이 하나의 파일에 있음
- 버전 드리프트 없음 - 모든 프로젝트가 동일한 버전을 사용함
- 쉬운 업데이트 - 한 번 변경하면 모든 곳에 적용됨
- 패키지 그룹화 - 관련 패키지에 대한 버전 변수 (예: 모든 Akka 패키지)
global.json - SDK 버전 고정
모든 환경에서 일관된 빌드를 위해 .NET SDK 버전을 고정합니다.
{
"sdk": {
"version": "9.0.200",
"rollForward": "latestFeature"
}
}
롤 포워드 정책
| 정책 | 동작 |
|---|---|
disable | 정확한 버전 필요 |
patch | 동일한 주.부, 최신 패치 |
feature | 동일한 주 버전, 최신 부.패치 |
latestFeature | 동일한 주 버전, 최신 기능 밴드 |
minor | 동일한 주 버전, 최신 부 버전 |
latestMinor | 동일한 주 버전, 최신 부 버전 |
major | 최신 SDK (권장하지 않음) |
권장: latestFeature - 동일한 기능 밴드 내에서 패치 업데이트를 허용합니다.
RELEASE_NOTES.md를 사용한 버전 관리
릴리스 노트 형식
#### 1.2.0 2025년 1월 15일 ####
- 새로운 기능 X 추가
- Y의 버그 수정
- Z의 성능 개선
#### 1.1.0 2024년 12월 10일 ####
- 기능 A, B, C로 초기 릴리스
구문 분석 스크립트 (getReleaseNotes.ps1)
function Get-ReleaseNotes {
param (
[Parameter(Mandatory=$true)]
[string]$MarkdownFile
)
$content = Get-Content -Path $MarkdownFile -Raw
$sections = $content -split "####"
$result = [PSCustomObject]@{
Version = $null
Date = $null
ReleaseNotes = $null
}
if ($sections.Count -ge 3) {
$header = $sections[1].Trim()
$releaseNotes = $sections[2].Trim()
$headerParts = $header -split " ", 2
if ($headerParts.Count -eq 2) {
$result.Version = $headerParts[0]
$result.Date = $headerParts[1]
}
$result.ReleaseNotes = $releaseNotes
}
return $result
}
버전 범프 스크립트 (bumpVersion.ps1)
function UpdateVersionAndReleaseNotes {
param (
[Parameter(Mandatory=$true)]
[PSCustomObject]$ReleaseNotesResult,
[Parameter(Mandatory=$true)]
[string]$XmlFilePath
)
$xmlContent = New-Object XML
$xmlContent.Load($XmlFilePath)
# VersionPrefix 업데이트
$versionElement = $xmlContent.SelectSingleNode("//VersionPrefix")
$versionElement.InnerText = $ReleaseNotesResult.Version
# PackageReleaseNotes 업데이트
$notesElement = $xmlContent.SelectSingleNode("//PackageReleaseNotes")
$notesElement.InnerText = $ReleaseNotesResult.ReleaseNotes
$xmlContent.Save($XmlFilePath)
}
빌드 스크립트 (build.ps1)
# 도우미 스크립트 로드
. "$PSScriptRoot\scripts\getReleaseNotes.ps1"
. "$PSScriptRoot\scripts\bumpVersion.ps1"
# 릴리스 노트 구문 분석 및 Directory.Build.props 업데이트
$releaseNotes = Get-ReleaseNotes -MarkdownFile (Join-Path -Path $PSScriptRoot -ChildPath "RELEASE_NOTES.md")
UpdateVersionAndReleaseNotes -ReleaseNotesResult $releaseNotes -XmlFilePath (Join-Path -Path $PSScriptRoot -ChildPath "Directory.Build.props")
Write-Output "Updated to version $($releaseNotes.Version)"
CI/CD 통합
# GitHub Actions 예시
- name: 릴리스 노트에서 버전 업데이트
shell: pwsh
run: ./build.ps1
- name: 빌드
run: dotnet build -c Release
- name: 태그 버전으로 패키징
run: dotnet pack -c Release /p:PackageVersion=${{ github.ref_name }}
- name: NuGet에 푸시
run: dotnet nuget push **/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
NuGet.Config
NuGet 소스 및 동작 구성:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<solution>
<add key="disableSourceControlIntegration" value="true" />
</solution>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<!-- 필요한 경우 비공개 피드 추가 -->
<!-- <add key="MyCompany" value="https://pkgs.dev.azure.com/myorg/_packaging/myfeed/nuget/v3/index.json" /> -->
</packageSources>
</configuration>
주요 설정:
<clear />- 재현 가능한 빌드를 위해 상속/기본 소스 제거disableSourceControlIntegration- TFS/Git 통합 문제 방지
전체 프로젝트 구조
MySolution/
├── .config/
│ └── dotnet-tools.json # 로컬 .NET 도구
├── .github/
│ └── workflows/
│ ├── pr-validation.yml # PR 검사
│ └── release.yml # NuGet 게시
├── scripts/
│ ├── getReleaseNotes.ps1 # RELEASE_NOTES.md 구문 분석
│ └── bumpVersion.ps1 # Directory.Build.props 업데이트
├── src/
│ ├── MyApp/
│ │ └── MyApp.csproj
│ └── MyApp.Core/
│ └── MyApp.Core.csproj
├── tests/
│ └── MyApp.Tests/
│ └── MyApp.Tests.csproj
├── Directory.Build.props # 중앙 집중식 빌드 구성
├── Directory.Packages.props # 중앙 패키지 관리
├── MySolution.slnx # XML 솔루션 파일
├── global.json # SDK 버전 고정
├── NuGet.Config # NuGet 소스 구성
├── RELEASE_NOTES.md # 버전 관리
├── README.md # 프로젝트 문서
├── logo.png # NuGet 패키지 아이콘
└── LICENSE # 오픈 소스 라이선스
빠른 시작 템플릿
새 솔루션을 만들기 위한 전체 설정 명령어
# 솔루션 생성
dotnet new sln --name MySolution --format slnx
# 프로젝트 추가
dotnet new classlib -n MyApp.Core -o src/MyApp.Core --framework net8.0
dotnet new webapi -n MyApp -o src/MyApp --framework net9.0
dotnet new xunit -n MyApp.Tests -o tests/MyApp.Tests --framework net9.0
# 솔루션에 프로젝트 추가
dotnet sln add src/MyApp.Core/MyApp.Core.csproj
dotnet sln add src/MyApp/MyApp.csproj
dotnet sln add tests/MyApp.Tests/MyApp.Tests.csproj
# 필수 구성 파일 생성 (Directories.Build.props, Directory.Packages.props, global.json, NuGet.Config, RELEASE_NOTES.md)
# 위 예제의 내용을 사용하여 이 파일들을 만드세요.
문제 해결
| 문제 | 해결 방법 |
|---|---|
dotnet sln migrate 실패 | SDK 9.0.200+를 사용 중인지 확인하세요. |
| 솔루션에 중복 프로젝트 | .sln 파일 하나와 .slnx 파일 하나만 있는지 확인하세요. |
| CPM에서 패키지 버전 누락 | Directory.Packages.props에서 ManagePackageVersionsCentrally가 true로 설정되었는지 확인하세요. |
| SourceLink가 작동하지 않음 | 리포지토리 URL이 Directory.Build.props에서 올바르게 설정되었는지 확인하세요. |
| CI에서 SDK 버전 불일치 | global.json이 소스 제어에 커밋되었는지 확인하세요. |