MSBuild

ビルドツールMicrosoft.NETVisual StudioWindowsクロスプラットフォーム

ビルドツール

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依存度の高さ

参考ページ

書き方の例

インストールと基本セットアップ

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