tech.guitarrapc.cóm

Technical updates

.NET SDKのバージョンをglobal.jsonで固定するのが有効なシーンを考える

.NETはglobal.jsonファイルを使って、利用する.NET SDKのバージョンの制限を表現できます。 あまりglobal.jsonを有効に機能させられるシーンはないと考えていたのですが、ワークアラウンドを見てなるほど思ったので.NET SDKのバージョンを固定することが有効なシーンを考えてみます。

global.jsonがどう動作するか

global.jsonは現在のディレクトリ、または親ディレクトリのいずれかで見つかった場合に利用されます。1

例えば次のようなフォルダ構成を用意します。ホストマシンには、9.0.2008.0.406がインストールされているとしましょう。

$ tree
.
├── ConsoleApp13
│   ├── ConsoleApp13.csproj
│   └── Program.cs
├── ConsoleApp13.sln
└── global.json

global.jsonを次のような内容にします。

{
  "sdk": {
    "version": "9.0.200"
  }
}

この時dotnet --versionを実行すると、global.jsonで指定したバージョンが利用されます。

$ dotnet --version
9.0.200

global.jsonを9.0.103にしてみましょう。ホストマシンには9.0.103がインストールされていないのでエラーとインストールするように促されます。

$ dotnet --version
The command could not be loaded, possibly because:
  * You intended to execute a .NET application:
      The application '--version' does not exist.
  * You intended to execute a .NET SDK command:
      A compatible .NET SDK was not found.

Requested SDK version: 9.0.103
global.json file: C:\Path\To\repos\ConsoleApp13\global.json

Installed SDKs:
8.0.406 [C:\Program Files\dotnet\sdk]
9.0.200 [C:\Program Files\dotnet\sdk]

Install the [9.0.103] .NET SDK or update [C:\Path\To\repos\ConsoleApp13\global.json] to match an installed SDK.

Learn about SDK resolution:
https://aka.ms/dotnet/sdk-not-found

また、global.jsonのsdk.rollForwardでSDKのバージョン解決方法を示すこともできます。例えば、latestFeature(=機能バンド)を指定してみましょう。

{
  "sdk": {
    "version": "9.0.103",
    "rollForward": "latestFeature"
  }
}

バージョンで9.0.103を指定していますが、機能バンドによって9.0.xxxに解決されるのでインストールされている9.0.200が返ってきました。rollForwardに指定した値がどのように解決するかはドキュメントをみてください。

$ dotnet --version
9.0.200

以上のようにglobal.jsonを使うことで、プロジェクトで利用する.NET SDKに制限をかけることができます。.csprojのTargetFrameworkではnet9.0のようにメジャーバージョンは指定できても利用するSDKのバージョンを指定できず、ターゲットフレームワークに沿ったバージョンかつインストールされている最新のSDKを利用します。ここにglobal.jsonを使ってバージョンを制約する利用シーンを見出すことができそうです。

global.jsonを使うシーン

個人開発というよりチーム開発やCI/CDで利用シーンが考えられそうです。利用シーンとして使いやすそうなものから3つ考えます。

  1. ローカルとCIの.NET SDKバージョンを揃える
  2. CIで利用する.NET SDKバージョンを制約する
  3. dotnet-installスクリプトを使ったインストールを展開する

ローカルとCIの.NET SDKバージョンを揃える

ローカル開発とCI両方でglobal.jsonでバージョンを指定することで、CIでのバージョン管理を明確にできます。例えばGitHub Actionsで.NET SDKを展開する場合、setup-dotnetアクションを使ってインストールできます。通常は以下のようにバージョンを指定します。

- name: Setup .NET
  uses: actions/setup-dotnet@v4
  with:
    dotnet-version: '9.0.x'

setup-dotnetはglobal.jsonを使ってバージョンを指定できるのを使って組んでみましょう。

この場合ローカル開発でもglobal.jsonを参照するので、slnと同階層がよさそうです。2

$ tree
.
├── .github
│   └── workflows
│       └── build.yaml
├── ConsoleApp13
│   ├── ConsoleApp13.csproj
│   └── Program.cs
├── ConsoleApp13.sln
└── global.json

最新の.NET 9バージョンを使ってもいいとするならlatestFeatureを指定するといいでしょう。

{
  "sdk": {
    "version": "9.0.103",
    "rollForward": "latestFeature"
  }
}

あとはGitHub Actionsのsetup-dotnetアクションでglobal.jsonを参照するように設定すればOKです。

- uses: actions/checkout@v4
- name: Setup .NET
  uses: actions/setup-dotnet@v4
  with:
    global-json-file: 'ci/global.json'

これで、先ほどの9.0.xと同様に最新の.NET 9バージョンを使ってCIでビルドできるようになります。ローカルでも最新の.NET 9バージョンが使われるのでCIでのバージョン指定が明確になります。

CIで利用する.NET SDKバージョンを制約する

ローカルでは最新のSDKを使いつつ、CIで利用するSDKバージョンを制約するのにも利用できそうです。9.0.103を使いたいとして以下のように書いてもよいですが、global.jsonでも同じことができます。

- name: Setup .NET
  uses: actions/setup-dotnet@v4
  with:
    dotnet-version: '9.0.103'

この場合ローカル開発で指定したバージョンのSDK利用を強制するのは開発体験が悪すぎなのでglobal.jsonを参照させたくありません。global.jsonはCI用のディレクトリにおいて、slnやプロジェクトとは隔離するとよさそうです。 例えば以下の例ではciフォルダにglobal.jsonを配置しています。[^2]

$ tree
.
├── .github
│   └── workflows
│       └── build.yaml
├── ci
│   └── global.json
├── ConsoleApp13
│   ├── ConsoleApp13.csproj
│   └── Program.cs
└── ConsoleApp13.sln

global.jsonで1つ前の9.0.103バージョンを固定するなら次のように書けばいいです。

{
  "sdk": {
    "version": "9.0.103"
  }
}

あとはGitHub Actionsのsetup-dotnetアクションでglobal.jsonを参照するように設定すればOKです。

- uses: actions/checkout@v4
- name: Setup .NET
  uses: actions/setup-dotnet@v4
  with:
    global-json-file: 'ci/global.json'

直接指定とどっちがいいかは好みが出そうですが、多くのワークフローでsetup-dotnetアクションを使っていて、Composite Actionを自分で書きたくないなら有効ですね。

dotnet-installスクリプトを使ったインストールを展開する

dotnet-installスクリプトを使ってインストールする場合、global.jsonを使ってバージョンを指定できます。Windowsではコマンドでインストールを展開することは少ないようですが、Jenkinsやセルフホストランナーなど独自CIのプロビジョニングやツール構築ジョブで指定した.NET SDKをインストールしたい時は使えそうです。

# bash
dotnet-install.sh --jsonfile global.json
# PowerShell
dotnet-install.ps1 -JSonFile global.json

まとめ

global.jsonは正直使いどころないなぁ、使ってるのを見ても意味あるのかなぁと考えていました。が、改めて考えるとCIでのバージョン管理やローカルとCIでのバージョンを揃えるのに使えそうです。特にCIでのバージョン管理は、GitHub Actionsのsetup-dotnetアクションを使っている場合に有効です。とはいえ、ローカル開発環境はglobal.jsonがなければ最新のSDKが使われる + setup-dotnetは9.0.xのように最新バージョンを指定する。という暗黙挙動と差異がないので、積極的にglobal.jsonを使う必然性はなさそうです。

ただ、9.0.200でdotnet-formatが壊れているワークアラウンドとして、global.jsonでCIのバージョンを固定するという方法はありな気もしました。ローカル開発が巻き添えになるのが微妙だけど、ファイルをCI用に用意するのはsetup-dotnetアクションで直指定とあまり変わらないので微妙なんですよね。

参考


  1. プロジェクトやソリューションと同階層に配置する制約がないことを意味しますが、わかりやすさのためにslnと同じ階層に配置することが多いと認識しています。
  2. 個人的には.githubディレクトリでもよいと考えますが、簡単のためciディレクトリとします。