.NETはglobal.jsonファイルを使って、利用する.NET SDKのバージョンの制限を表現できます。 あまりglobal.jsonを有効に機能させられるシーンはないと考えていたのですが、ワークアラウンドを見てなるほど思ったので.NET SDKのバージョンを固定することが有効なシーンを考えてみます。
global.jsonがどう動作するか
global.jsonは現在のディレクトリ、または親ディレクトリのいずれかで見つかった場合に利用されます。1
例えば次のようなフォルダ構成を用意します。ホストマシンには、9.0.200
と8.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つ考えます。
- ローカルとCIの.NET SDKバージョンを揃える
- CIで利用する.NET SDKバージョンを制約する
- 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アクションで直指定とあまり変わらないので微妙なんですよね。