mcpField
M
msbuild-modernization

msbuild-modernization

github.com/dotnet開発5.8k3.9k 回インストール1 日前に更新
リポジトリ

msbuild-modernization について

旧形式のプロジェクトを識別し、SDK-style MSBuildへ移行するための.NETスキルです。パッケージやビルド設定の移行に加え、よくある問題の確認方法も扱います。

主な機能

  1. 01旧形式とSDK-styleの識別
  2. 02プロジェクトファイルとTFMの移行
  3. 03packages.configからPackageReferenceへの変換
  4. 04中央パッケージと共有設定の集約

活用シーン

  1. 01旧形式の.NETプロジェクトのアップグレード
  2. 02重複するMSBuild設定の整理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dotnet/skills --skill msbuild-modernization

インストール後、Claude Code で/msbuild-modernizationを入力して読み込まれたか確認します。

詳細

MSBuild のモダナイゼーション:レガシー形式から SDK スタイルへの移行

レガシー形式と SDK スタイルのプロジェクトの見分け方

レガシー形式の目印:

  • <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  • ファイルを明示的に列挙(すべての .cs ファイルに対して <Compile Include="..." /> を指定)
  • <Project> 要素に ToolsVersion 属性がある
  • packages.config ファイルが存在する
  • アセンブリレベルの属性を含む Properties\AssemblyInfo.cs がある

SDK スタイルの目印:

  • ルート要素に <Project Sdk="Microsoft.NET.Sdk"> 属性がある
  • 内容が最小限 — シンプルなプロジェクトなら 10~15 行程度
  • ファイルを明示的にインクルードしない(暗黙的なグロブによる取り込み)
  • packages.config の代わりに <PackageReference> 項目を使用する

簡単な見分け方: シンプルなクラスライブラリやコンソールアプリの .csproj が 50 行を超える場合、レガシー形式である可能性が高いです。


<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <OutputType>Library</OutputType>
    <RootNamespace>MyLibrary</RootNamespace>
    <AssemblyName>MyLibrary</AssemblyName>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <Deterministic>true</Deterministic>
  </PropertyGroup>
  
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net472</TargetFramework>
  </PropertyGroup>
</Project>

移行チェックリスト:レガシー形式 → SDK スタイル

ステップ 1:プロジェクトのルート要素を置き換える

変更前:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"
          Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

変更後:

<Project Sdk="Microsoft.NET.Sdk">
  
</Project>

XML 宣言、ToolsVersion、xmlns、2 つの <Import> 行を削除します。Sdk 属性がこれらすべてを置き換えます。

ステップ 2:TargetFramework を設定する

変更前:

<PropertyGroup>
  <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
</PropertyGroup>

変更後:

<PropertyGroup>
  <TargetFramework>net472</TargetFramework>
</PropertyGroup>

TFM の対応表:

レガシー形式の TargetFrameworkVersionSDK スタイルの TargetFramework
v4.6.1net461
v4.7.2net472
v4.8net48
(.NET 6 への移行)net6.0
(.NET 8 への移行)net8.0
ステップ 3:ファイルの明示的なインクルードを削除する

変更前:

<ItemGroup>
  <Compile Include="Controllers\HomeController.cs" />
  <Compile Include="Models\User.cs" />
  <Compile Include="Models\Order.cs" />
  <Compile Include="Services\AuthService.cs" />
  <Compile Include="Services\OrderService.cs" />
  <Compile Include="Properties\AssemblyInfo.cs" />
  
</ItemGroup>
<ItemGroup>
  <Content Include="Views\Home\Index.cshtml" />
  <Content Include="Views\Shared\_Layout.cshtml" />
  
</ItemGroup>

変更後:

これらの <Compile> および <Content> 項目グループをすべて削除します。SDK スタイルのプロジェクトでは、暗黙的なグロブによって自動的に取り込まれます。

例外: 特別なメタデータが必要なファイルや、プロジェクトディレクトリの外にあるファイルについては、明示的なエントリを残します。

<ItemGroup>
  <Content Include="..\shared\config.json" Link="config.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
ステップ 4:AssemblyInfo.cs を削除する

変更前(Properties\AssemblyInfo.cs):

using System.Reflection;
using System.Runtime.InteropServices;

[assembly: AssemblyTitle("MyLibrary")]
[assembly: AssemblyDescription("A useful library")]
[assembly: AssemblyCompany("Contoso")]
[assembly: AssemblyProduct("MyLibrary")]
[assembly: AssemblyCopyright("Copyright © Contoso 2024")]
[assembly: ComVisible(false)]
[assembly: Guid("...")]
[assembly: AssemblyVersion("1.2.0.0")]
[assembly: AssemblyFileVersion("1.2.0.0")]

変更後(.csproj 内):

<PropertyGroup>
  <AssemblyTitle>MyLibrary</AssemblyTitle>
  <Description>A useful library</Description>
  <Company>Contoso</Company>
  <Product>MyLibrary</Product>
  <Copyright>Copyright © Contoso 2024</Copyright>
  <Version>1.2.0</Version>
</PropertyGroup>

Properties\AssemblyInfo.cs を削除します。SDK はこれらのプロパティからアセンブリ属性を自動生成します。

代替方法: AssemblyInfo.cs を残す場合は、自動生成を無効にします。

<PropertyGroup>
  <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
ステップ 5:packages.config を PackageReference に移行する

変更前(packages.config):

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Newtonsoft.Json" version="13.0.3" targetFramework="net472" />
  <package id="Serilog" version="3.1.1" targetFramework="net472" />
  <package id="Microsoft.Extensions.DependencyInjection" version="8.0.0" targetFramework="net472" />
</packages>

変更後(.csproj 内):

<ItemGroup>
  <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
  <PackageReference Include="Serilog" Version="3.1.1" />
  <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup>

移行後に packages.config を削除します。

移行方法:

  • Visual Studio: packages.config を右クリック → packages.config を PackageReference に移行
  • CLI: dotnet migrate-packages-config または手動で変換
  • バインディング リダイレクト: SDK スタイルのプロジェクトではバインディング リダイレクトが自動生成されます。app.config に <runtime> セクションがある場合は削除します
ステップ 6:不要な定型記述を削除する

SDK には適切な既定値が用意されているため、以下をすべて削除します。


<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" ... />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />


<PropertyGroup>
  <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
  <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
  <ProjectGuid>{...}</ProjectGuid>
  <OutputType>Library</OutputType>  
  <AppDesignerFolder>Properties</AppDesignerFolder>
  <FileAlignment>512</FileAlignment>
  <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
  <Deterministic>true</Deterministic>
</PropertyGroup>


<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
  <DebugSymbols>true</DebugSymbols>
  <DebugType>full</DebugType>
  <Optimize>false</Optimize>
  <OutputPath>bin\Debug\</OutputPath>
  <DefineConstants>DEBUG;TRACE</DefineConstants>
  <ErrorReport>prompt</ErrorReport>
  <WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
  <DebugType>pdbonly</DebugType>
  <Optimize>true</Optimize>
  <OutputPath>bin\Release\</OutputPath>
  <DefineConstants>TRACE</DefineConstants>
  <ErrorReport>prompt</ErrorReport>
  <WarningLevel>4</WarningLevel>
</PropertyGroup>


<ItemGroup>
  <Reference Include="System" />
  <Reference Include="System.Core" />
  <Reference Include="System.Data" />
  <Reference Include="System.Xml" />
  <Reference Include="System.Xml.Linq" />
  <Reference Include="Microsoft.CSharp" />
</ItemGroup>


<None Include="packages.config" />


<Service Include="{508349B6-6B84-11D3-8410-00C04F8EF8E0}" />

SDK の既定値と異なるプロパティ(例:<OutputType>Exe</OutputType>、アセンブリ名と異なる場合の <RootNamespace>、カスタムの <DefineConstants>)だけを残します。

ステップ 7:最新機能を有効にする

移行後、最新の C# 機能を有効にすることを検討してください。

<PropertyGroup>
  <TargetFramework>net8.0</TargetFramework>
  <Nullable>enable</Nullable>
  <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
  • <Nullable>enable</Nullable> — null 許容参照型の解析を有効にします
  • <ImplicitUsings>enable</ImplicitUsings> — よく使われる名前空間を自動でインポートします(.NET 6 以降)
  • <LangVersion>latest は避けてください — 実際に使われる言語バージョンは TFM だけでなく、SDK/コンパイラの既定値によって決まるため、インストールされている SDK が異なるマシン間で、ビルドの差異が気付かないうちに生じる可能性があります。特定のバージョンに固定する必要がない限り、<LangVersion> は省略してください。再現可能なビルドを実現するには、リポジトリ全体の SDK バージョンを global.json で固定する(間接的に言語バージョンの既定値も固定されます)か、プロジェクトごとに数値で明示した <LangVersion>(例:<LangVersion>12</LangVersion>)を設定して、言語バージョンを直接制御します。

移行前後の完全な例

移行前(レガシー形式 — 65 行):

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"
          Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProjectGuid>{12345678-1234-1234-1234-123456789ABC}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>MyLibrary</RootNamespace>
    <AssemblyName>MyLibrary</AssemblyName>
    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
    <Deterministic>true</Deterministic>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Xml.Linq" />
    <Reference Include="Microsoft.CSharp" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Models\User.cs" />
    <Compile Include="Models\Order.cs" />
    <Compile Include="Services\UserService.cs" />
    <Compile Include="Services\OrderService.cs" />
    <Compile Include="Helpers\StringExtensions.cs" />
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <ItemGroup>
    <None Include="packages.config" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

移行後(SDK 形式 — 11 行):

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net472</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
    <PackageReference Include="Serilog" Version="3.1.1" />
  </ItemGroup>
</Project>

よくある移行時の問題

埋め込みリソース: 標準の場所にないファイルには、明示的なインクルードが必要になる場合があります。

<ItemGroup>
  <EmbeddedResource Include="..\shared\Schemas\*.xsd" LinkBase="Schemas" />
</ItemGroup>

CopyToOutputDirectory を使用するコンテンツファイル: これらは引き続き明示的なエントリが必要です。

<ItemGroup>
  <Content Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
  <None Include="scripts\*.sql" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

マルチターゲティング: 要素名を単数形から複数形に変更します。


<TargetFramework>net8.0</TargetFramework>


<TargetFrameworks>net472;net8.0</TargetFrameworks>

WPF/WinForms プロジェクト: 適切な SDK またはプロパティを使用します。


<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">


<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <UseWPF>true</UseWPF>
    
    <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>
</Project>

テストプロジェクト: テストフレームワークのパッケージとともに標準の SDK を使用します。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <IsPackable>false</IsPackable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
    <PackageReference Include="xunit" Version="2.7.0" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.5.7" />
  </ItemGroup>
</Project>

パッケージの一元管理への移行

複数プロジェクトのソリューション全体で NuGet のバージョン管理を一元化します。詳細については、https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management を参照してください。

手順 1: リポジトリのルートに Directory.Packages.props を作成し、<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> と、すべてのパッケージの <PackageVersion> 項目を追加します。

手順 2: 各プロジェクトの PackageReference から Version を削除します。


<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />


<PackageReference Include="Newtonsoft.Json" />

Directory.Build の集約

複数の .csproj ファイルで繰り返し使用されているプロパティを特定し、共有ファイルに移動します。

Directory.Build.props(プロパティ用 — リポジトリまたは src のルートに配置):

<Project>
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <Company>Contoso</Company>
    <Copyright>Copyright © Contoso 2024</Copyright>
  </PropertyGroup>
</Project>

Directory.Build.targets(ターゲット/タスク用 — リポジトリまたは src のルートに配置):

<Project>
  <Target Name="PrintBuildInfo" AfterTargets="Build">
    <Message Importance="High" Text="Built $(AssemblyName) → $(TargetPath)" />
  </Target>
</Project>

個別の .csproj ファイルには、プロジェクト固有のものだけを残します。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <AssemblyName>MyApp</AssemblyName>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Serilog" />
    <ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
  </ItemGroup>
</Project>

ツールと自動化

ツール用途
dotnet try-convertレガシー形式から SDK 形式への自動変換。インストール:dotnet tool install -g try-convert
.NET Upgrade AssistantAPI の変更を含む包括的な移行。インストール:dotnet tool install -g upgrade-assistant
Visual Studiopackages.config を右クリック → packages.config を PackageReference に移行
手動での移行シンプルなプロジェクトでは最もすっきりすることがよくあります — 上記のチェックリストに従ってください

推奨する方法:

  1. 最初の段階として try-convert を実行します
  2. 出力を確認し、手動で整理します
  3. ビルドして問題を修正します
  4. 最新の機能(nullable、implicit usings)を有効にします
  5. 共有設定を Directory.Build.props に集約します

コメント