MSBuild
ビルドツール
MSBuild
概要
MSBuildは、Microsoft社が開発した.NET生態系の標準ビルドプラットフォームです。2003年に.NET Framework 2.0と共に導入され、Visual Studioと.NETエコシステムの中核を成しています。XMLベースのプロジェクトファイル(.csproj、.vbproj、.fsproj等)を使用してビルドプロセスを定義し、コンパイル、テスト、パッケージング、デプロイメントまでの一連のプロセスを自動化します。2016年には.NET Coreと共にオープンソース化され、Windows、Linux、macOSでのクロスプラットフォーム開発をサポートしています。
詳細
主要機能
- .NET統合: C#、VB.NET、F#、C++等の.NET言語のネイティブサポート
- プロジェクトファイル: XMLベースの宣言的ビルド定義
- ターゲットシステム: 再利用可能なビルドタスクの定義と実行
- 条件分岐: プロパティとアイテムを使用した動的ビルド設定
- 増分ビルド: ファイルタイムスタンプベースの効率的なリビルド
- 並列ビルド: マルチプロセッサ対応の高速ビルド
- NuGetパッケージ統合: パッケージの自動復元と管理
アーキテクチャ
プロジェクトファイルを解析してビルドグラフを構築し、ターゲットとタスクを順次実行。プロパティとアイテムによる設定管理、条件評価エンジンによる動的ビルドプロセス。
エコシステム
Visual Studio、Visual Studio Code、NuGet、Azure DevOps、GitHub Actions等との深い統合。.NET CLI、Docker、Kubernetes等のモダン開発ツールとの連携も充実。
メリット・デメリット
メリット
- .NET生態系との完全統合: Visual Studioと.NETフレームワークのネイティブサポート
- 強力なIDE統合: Visual Studioでの優れた開発体験とデバッグ機能
- クロスプラットフォーム: Windows、Linux、macOSでの統一されたビルド体験
- 豊富な組み込み機能: NuGet、テスト、パッケージング等の統合機能
- 宣言的設定: XMLベースの分かりやすいプロジェクト設定
- 企業サポート: Microsoft社による長期的なサポートと更新
- 豊富なドキュメント: 包括的な公式ドキュメントとコミュニティリソース
デメリット
- .NET依存: .NET以外のプロジェクトでは利用価値が限定的
- XML設定の複雑さ: 大規模プロジェクトでは設定が複雑になる
- 学習コストの転換: 他のビルドツールからの移行時の概念の違い
- パフォーマンス: 非常に大規模なプロジェクトでは速度の制約
- エラーメッセージ: 設定ミス時の分かりにくいエラー表示
- Microsoft依存: エコシステムのMicrosoft依存度の高さ
参考ページ
- MSBuild 公式サイト
- MSBuild GitHub リポジトリ
- MSBuild リファレンス
- .NET プロジェクトファイルのSDKスタイル
- MSBuild ターゲット
- MSBuild プロパティ
書き方の例
インストールと基本セットアップ
# .NET SDK インストール (MSBuild含む)
# Windows
winget install Microsoft.DotNet.SDK.8
# macOS
brew install --cask dotnet
# Ubuntu/Debian
wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt-get update && sudo apt-get install -y dotnet-sdk-8.0
# CentOS/RHEL/Fedora
sudo dnf install dotnet-sdk-8.0
# Visual Studio (Windows)
# MSBuildは Visual Studio と共に自動インストール
# バージョン確認
dotnet --version
msbuild -version
# 新規プロジェクト作成
dotnet new console -n MyApp
cd MyApp
# ビルドと実行
dotnet build # MSBuildを使用したビルド
dotnet run # ビルドと実行
基本的な.csprojファイル(SDK-style)
<!-- MyApp.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- アプリケーション情報 -->
<AssemblyTitle>My Application</AssemblyTitle>
<AssemblyDescription>Sample console application</AssemblyDescription>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<Copyright>Copyright © 2024</Copyright>
</PropertyGroup>
<!-- NuGetパッケージ参照 -->
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
</ItemGroup>
<!-- プロジェクト参照 -->
<ItemGroup>
<ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
</ItemGroup>
</Project>
ライブラリプロジェクトとWebアプリケーション
<!-- MyLibrary.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<!-- NuGetパッケージ情報 -->
<PackageId>MyCompany.MyLibrary</PackageId>
<PackageVersion>1.2.3</PackageVersion>
<Authors>My Company</Authors>
<Description>Utility library for common operations</Description>
<PackageTags>utility;helper;library</PackageTags>
<PackageProjectUrl>https://github.com/mycompany/mylibrary</PackageProjectUrl>
<RepositoryUrl>https://github.com/mycompany/mylibrary</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.Json" Version="8.0.0" />
</ItemGroup>
</Project>
<!-- MyWebApp.csproj (ASP.NET Core) -->
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<UserSecretsId>12345678-1234-1234-1234-123456789012</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
</ItemGroup>
<!-- 開発時のみの依存関係 -->
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.0" />
</ItemGroup>
</Project>
高度なビルド設定とカスタムターゲット
<!-- Advanced.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
<Platform Condition="'$(Platform)' == ''">AnyCPU</Platform>
<!-- 条件付きコンパイル -->
<DefineConstants Condition="'$(Configuration)' == 'Debug'">DEBUG;TRACE</DefineConstants>
<DefineConstants Condition="'$(Configuration)' == 'Release'">TRACE</DefineConstants>
<!-- 最適化設定 -->
<Optimize Condition="'$(Configuration)' == 'Release'">true</Optimize>
<DebugSymbols Condition="'$(Configuration)' == 'Debug'">true</DebugSymbols>
<DebugType Condition="'$(Configuration)' == 'Debug'">portable</DebugType>
<!-- コード分析 -->
<AnalysisLevel>latest</AnalysisLevel>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsNotAsErrors>CS1591</WarningsNotAsErrors>
</PropertyGroup>
<!-- プラットフォーム固有設定 -->
<PropertyGroup Condition="'$(TargetFramework)' == 'net8.0'">
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
</PropertyGroup>
<!-- ファイルグループ定義 -->
<ItemGroup>
<Compile Include="**/*.cs" Exclude="bin/**;obj/**" />
<EmbeddedResource Include="Resources/*.resx" />
<Content Include="Content/**/*" CopyToOutputDirectory="Always" />
</ItemGroup>
<!-- カスタムターゲット定義 -->
<Target Name="PrintBuildInfo" BeforeTargets="Build">
<Message Text="Building $(MSBuildProjectName) in $(Configuration) mode" Importance="high" />
<Message Text="Target Framework: $(TargetFramework)" Importance="high" />
<Message Text="Output Path: $(OutputPath)" Importance="high" />
</Target>
<!-- ビルド後処理 -->
<Target Name="CopyAdditionalFiles" AfterTargets="Build">
<ItemGroup>
<AdditionalFiles Include="$(ProjectDir)docs/**/*" />
</ItemGroup>
<Copy SourceFiles="@(AdditionalFiles)"
DestinationFolder="$(OutputPath)docs" />
</Target>
<!-- 条件付きプロパティ -->
<PropertyGroup Condition="'$(BuildingInsideVisualStudio)' == 'true'">
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<!-- カスタムタスク実行 -->
<Target Name="RunCustomTask" BeforeTargets="BeforeBuild">
<Exec Command="echo Starting custom build process..." />
<Exec Command="npm ci" WorkingDirectory="$(ProjectDir)ClientApp" />
<Exec Command="npm run build" WorkingDirectory="$(ProjectDir)ClientApp" />
</Target>
</Project>
複数プロジェクトのソリューション管理
<!-- Directory.Build.props (ソリューションルート) -->
<Project>
<!-- 全プロジェクト共通プロパティ -->
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
<!-- バージョン管理 -->
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix Condition="'$(Configuration)' == 'Debug'">dev</VersionSuffix>
<!-- 共通パッケージバージョン -->
<MicrosoftExtensionsVersion>8.0.0</MicrosoftExtensionsVersion>
<NewtonsoftJsonVersion>13.0.3</NewtonsoftJsonVersion>
<SerilogVersion>3.1.1</SerilogVersion>
</PropertyGroup>
<!-- 共通パッケージ参照 -->
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsVersion)" />
<PackageReference Include="Serilog" Version="$(SerilogVersion)" />
</ItemGroup>
<!-- コード分析共通設定 -->
<PropertyGroup>
<AnalysisLevel>latest</AnalysisLevel>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>
<!-- Directory.Build.targets (共通ターゲット) -->
<Project>
<!-- 共通ビルド後処理 -->
<Target Name="CommonPostBuild" AfterTargets="PostBuildEvent">
<Message Text="Post-build processing for $(MSBuildProjectName)" Importance="high" />
<!-- テストプロジェクトでない場合のみ実行 -->
<CallTarget Targets="CopyLicenseFile" Condition="!$(MSBuildProjectName.Contains('Test'))" />
</Target>
<Target Name="CopyLicenseFile">
<Copy SourceFiles="$(MSBuildThisFileDirectory)LICENSE.txt"
DestinationFolder="$(OutputPath)"
Condition="Exists('$(MSBuildThisFileDirectory)LICENSE.txt')" />
</Target>
</Project>
テストプロジェクトと CI/CD 統合
<!-- MyApp.Tests.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6" />
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Moq" Version="4.20.69" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyApp\MyApp.csproj" />
<ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
</ItemGroup>
<!-- テストデータファイル -->
<ItemGroup>
<None Update="TestData/**/*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
ビルドスクリプトとコマンド実行
# 基本的なMSBuildコマンド
msbuild MyApp.sln # ソリューション全体のビルド
msbuild MyApp.csproj # 特定プロジェクトのビルド
msbuild MyApp.sln /p:Configuration=Release # リリースビルド
msbuild MyApp.sln /p:Platform=x64 # プラットフォーム指定
# .NET CLI使用(推奨)
dotnet build # ソリューションのビルド
dotnet build --configuration Release # リリースビルド
dotnet build --verbosity normal # 詳細度指定
dotnet build --no-restore # 復元をスキップ
# テスト実行
dotnet test # 全テスト実行
dotnet test --collect:"XPlat Code Coverage" # カバレッジ測定
dotnet test --logger trx # テスト結果をTRX形式で出力
# パッケージング
dotnet pack # NuGetパッケージ作成
dotnet pack --configuration Release # リリース用パッケージ
dotnet pack --output ./packages # 出力先指定
# 発行
dotnet publish --configuration Release --runtime win-x64 --self-contained
dotnet publish --configuration Release --runtime linux-x64
dotnet publish --configuration Release --framework net8.0
# クリーンアップ
dotnet clean # ビルド成果物削除
msbuild /t:Clean # MSBuild使用
# 復元
dotnet restore # NuGetパッケージ復元
msbuild /t:Restore # MSBuild使用
CI/CD設定例
# .github/workflows/dotnet.yml
name: .NET Build and Test
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.x'
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore --configuration Release
- name: Test
run: dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage"
- name: Pack
run: dotnet pack --no-build --configuration Release --output ./packages
- name: Upload packages
uses: actions/upload-artifact@v3
with:
name: nuget-packages
path: ./packages/*.nupkg
カスタムタスクとターゲット
<!-- Custom.targets -->
<Project>
<!-- カスタムタスク定義 -->
<UsingTask TaskName="GenerateVersionInfo"
AssemblyFile="$(MSBuildThisFileDirectory)BuildTasks.dll" />
<!-- 条件付きターゲット実行 -->
<Target Name="GenerateVersionFile"
BeforeTargets="CoreCompile"
Condition="'$(GenerateVersionInfo)' == 'true'">
<GenerateVersionInfo
OutputPath="$(IntermediateOutputPath)VersionInfo.cs"
AssemblyVersion="$(AssemblyVersion)"
GitCommit="$(GitCommitHash)" />
<ItemGroup>
<Compile Include="$(IntermediateOutputPath)VersionInfo.cs" />
</ItemGroup>
</Target>
<!-- プロパティ関数使用 -->
<PropertyGroup>
<BuildTimestamp>$([System.DateTime]::Now.ToString('yyyy-MM-dd HH:mm:ss'))</BuildTimestamp>
<GitCommitHash>$([System.IO.File]::ReadAllText('$(MSBuildProjectDirectory)\.git\refs\heads\main').Substring(0, 7))</GitCommitHash>
</PropertyGroup>
<!-- MSBuild拡張タスク -->
<Target Name="ZipOutput" AfterTargets="Publish">
<ItemGroup>
<FilesToZip Include="$(PublishDir)**/*" />
</ItemGroup>
<Zip SourceFiles="@(FilesToZip)"
DestinationFile="$(OutputPath)$(AssemblyName)-$(Configuration).zip"
OverwriteReadOnlyFiles="true" />
</Target>
</Project>