tech.guitarrapc.cóm

Technical updates

.NET Core Tools と DotNetCliToolReference

.NET Core 3.1 がリリースされました、LTS リリースなので netcoreapp3.0 にしていたものは悉く netcoreapp3.1 に移行ですね。

さて、C# 的には .NET Core 3.0 から変化がほぼないのですが、dotnet cli や ビルド的には.NET Core 2.2から破壊的変更が行われました。 .NET Core Local Tools です。

今回は、これまでもあった .NET Core Global Tools と、.NET Core 2.2 までの DotNetCliToolReference、.NET Core 3.1からの .NET Core Local Toolsについてです。

これまでも書こうと思ってたのですが、ちょっとながしてたやつ。

目次

TL;DR

  • ツールに対して一意のバージョンでCLIを使うなら .NET Core Global Tool でok
  • プロジェクトごとにバージョン使い分けたりプロジェクトに紐づけるなら、DotNetCliToolReference or .NET Core Local Tools を使う
  • DotNetCliToolReference は.NET Core 2.2 and below に限定されてオワコン
  • .NET Core 3.1 以上では、.NET Core Local Tools に移行必須
  • .NET Core Local Tools では dotnet tool restore を忘れないで
  • Manifest の二重管理の世界へようこそ

Summary

.NET Core 2.2 まで

  • マシンで一意に使う CLI : .NET Core Global Tools
  • プロジェクト固有に利用する CLI (csproj単位) : DotNetCliToolReference

.NET Core 3.0 以降

  • マシンで一意に使うCLI : .NET Core Global Tools
  • プロジェクト固有に利用する CLI (sln/csproj単位) : .NET Core Local Tools

.NET Core Global Tools (> .NETCore 2.1)

.NET Core で大きく変わった体験の一つが.NET Core Global Tool です。 コンセプトとして NPM global tools を意識しているだけあり、npm に相当するnuget にアップロードしたコンソールアプリケーションを dotnet CLI から取得して、パスを意識することなく利用できます。

Announcing .NET Core 2.1 Preview 1 | .NET Blog

# npm
$ npm install -g TOOL

# .NET Global Tools
$ dotnet install tool -g TOOL

これを使うことで、.NET Core製のCLIの配布が dotnet cli で行えるようになり、.NET Core SDK が入っている環境でのツール配布が一元化、簡略化され、利用者も.NET Core SDK が入っていればok になりました。 日常的な開発体験もそうですが、ちょっとしたツールの展開、CI/CDでのツール利用まで大きく簡略化されています。

何よりも、.NET Core をターゲットに作ることでマルチプラットフォームで利用できるツールをサクッと作って配布できるようになったのが .NET Core Tools の嬉しいことです。

.NET Core Global Tools以前のCLI配布

各人がGitHub Release や 直バイナリなどで配布、利用者はダウンロードしてパスを通して利用。

.NET Core Global Tools以降のCLI配布

NuGet にpackしてpush すれば配布はNuGet任せ。利用者はパスが通った箇所にツールがインストールされるのでCLIをインストール後は実行するだけ。

.NET Core Global Tools で満たされるケース

大概のケースではうまく機能するはずです。 唯一の前提は一つです。(.NET Core SDK は大前提なので含みません)

  • 通常のCLI利用として、ツールごとに一意のバージョンでよい

.NET Core Global Tools で困るケース

一方で、NPM global tools がそうであるように、.NET Core Global Toolsではツールごとに一意のバージョン参照になるという制約は当然あります。

.NET Core Global Tools を各自がインストールすればいいように見えますね。ダメなんでしょうか?

.NET Core Global Tools を使うと、複数のプロジェクトで個別のバージョンでツールを使いたいときに不自由します。 特にそういうケースは、マルチプラットフォームに csproj のビルド後イベントで何かを実行したいときに生じます。

こういうビルド後処理はシンプルなことが多くコマンド一つで行けたりするので、シェルを適当にたたきたいところですが、マルチプラットフォームが前提だと案外困ります。

  • Bash だとWindows で困る
  • BatはWindowsでない環境で困る
  • PowerShell は非Windowsでインストールされてないと困る

どれもそのプラットフォームに閉じればいいのですが、各人の開発環境やCI環境など実行個所を選ばない前提に立つと微妙に選択肢に困ります。

こういったときに.NET Core Global Tools は、.NET Core SDK が入っていればok なので .NET Core 開発環境、ビルド環境で利用するのに支障がないように見えます。 マルチプラットフォームに動作が保証されて導入も楽なのでいい感じに思えるのですが、前述の通りプロジェクトごとに個別のバージョンを利用したいときに困ります。 いちいちプロジェクトごとに、ツールのバージョンを変えるということはしたくないのです。

プロジェクト固有のCLI参照

.NET Core 2.2 までと、.NET Core 3.1 以降で方法が変わります。

DotNetCliToolReference (< .NETCore 2.2)

プロジェクトに固有で.NET CoreなCLIを利用したい、それを満たすために用意されていたのが DotNetCliToolReference です。

# Consuming per-project tools

Consuming these tools requires you to add a <DotNetCliToolReference> element to your project file for each tool you want to use. Inside the <DotNetCliToolReference> element, you reference the package in which the tool resides and specify the version you need. After running dotnet restore, the tool and its dependencies are restored.

.NET Core CLI extensibility model - .NET Core CLI | Microsoft Docs

DotNetCliToolReference を利用するには、SDK Styleの .csproj でNuGet に放流したパッケージを<DotNetCliToolReference> で参照すると (.NET Core Tools とは違う通常のNuGet パッケージ)、プロジェクトのビルド処理で利用できるものです。 dotnet restore でnuget パッケージと同様に復元されるので、.csprojに定義を追加してコミットすれば意識するなく利用できます。

PackageReference と同様に属性を定義ができるので、バージョンも固定もできるしプロジェクト固有のCLI処理だけ考えると概ね満たされます。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>

  <!-- The tools reference -->
  <ItemGroup>
    <DotNetCliToolReference Include="dotnet-api-search" Version="1.0.0" />
  </ItemGroup>
</Project>

.NET Core なプロジェクトでなくとも、SDK Style の csproj であれば .NET Framework 4.6 でも利用できるので案外使いどころはあります。

DotNetCliToolReference に対応した NuGetパッケージの作成、配布

難点として、配布側が少しだけ面倒です。 DotNetCliToolReference は、.NET Core Tools ではなく NuGet パッケージであるため、配布側は .NET Core Tools とは別に NuGetパッケージを作成する必要があります。CLIの処理的には何も変わらないので、同じコードのまま .csproj を変えて参照するだけです。

csproj としては2点だけ注意が要ります。

  • PackageType を DotNetCliTool にすること
  • PackageId と AssemblyName を合わせて、dotnet- の prefix で始めるように命名する

ほぼほぼ .NET Core Global Tools と違わないので、むむっという感じでしょう。

サンプルに実際にDotNetCliToolReference として配布している NuGetパッケージ dotnet-kustomizationconfigmapgenerator の .csproj を示します。

guitarrapc/KustomizeConfigMapGenerator: Kustomize ConfigMapGenerator Generator CLI

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

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.1</TargetFramework>
    <Version>0.1.0</Version>
    <AssemblyVersion>$(Version)</AssemblyVersion>
  </PropertyGroup>

  <!-- nuget -->
  <PropertyGroup>
    <PackageType>DotNetCliTool</PackageType>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    <PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
    <RootNamespace>KustomizeConfigMapGenerator.ProjectTool</RootNamespace>
    <AssemblyName>dotnet-kustomizationconfigmapgenerator</AssemblyName>
    <PackageId>dotnet-kustomizationconfigmapgenerator-project-tool</PackageId>
    <Description>
      <![CDATA[Project-installable Kustomize configMapGenerator commandline tool.
This package can be installed into a project using `DotNetCliToolReference`.
* To install as a dotnet global or local tool, use `dotnet-kustomizeconfigmapgenerator` instead.]]>
    </Description>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="MicroBatchFramework" Version="1.6.0" />
  </ItemGroup>

  <ItemGroup>
    <Compile Include="..\KustomizeConfigMapGenerator\**\*.cs" Exclude="..\KustomizeConfigMapGenerator\obj\**\*.cs"/>
  </ItemGroup>

  <ItemGroup>
    <None Include="..\..\LICENSE.md">
      <Pack>True</Pack>
      <PackagePath></PackagePath>
    </None>
  </ItemGroup>

</Project>

DotNetCliToolReference で困るケース

利用する分には概ねありません。

プロジェクトごとの参照になるため、ソリューション単位で必要な場合には各プロジェクトで埋め込みが必要になるでしょう。

何より DotNetCliToolReference は .NET Core 2.2 までのサポートです。.NET Core 3.1 以降では、次に述べる .NET Core Lobal Tools を使う必要があります。

Limit DotNetCliToolReference Tools to .NET Core 2.2 and Below · Issue #107 · dotnet/announcements

.NET Core Tools (> .NETCore 2.1)

.NET Core Global Tools では、 dotnet tool install -g Tool-g を付けていました。-g を外して、パスを指定することで、グローバルではなく指定したパスにツールをインストールして利用できます。

# インストール
$ dotnet tool install --tool-path .tools Cake.Tool

# 利用
$ ./.tools/dotnet-cake --help

これなら、プロジェクトごとにツールを利用できそうです。

パスを指定した .NET Core Tools のインストールで困ること

何もツールとしてはサポートがないので、あると便利な機能がありません。

  • インストールパスが決まっていないので、何をインストールしたのか一覧をとるコマンドサポートがなく利用する際に困る
  • 相対パスでインストールする前提だと、ほかのプロジェクトでも同じツールを使いたい場合でも、コピーを作ることになる。この例なら ./tools
  • マルチプラットフォームで利用を想定するとツールの起動パスは ./ で開始するでしょう。すると pwsh や bash (そのほか) に利用が制限されます。cmd ではこのパスしてはエラーなのはわかっていても、使う側からするとちょっと悩ましい。

どれも些細ですが、微妙に使い勝手も困ります。

もう少し、ツールをバージョンごとに指定して利用できる仕組みがないでしょうか。

.NET Core Local Tools (> .NETCore 3.1)

.NET Core 3.1 で、Global Tools のようですが所定のパスにツールをインストールし解決する仕組みが追加されました。 これを .NET Core Local Tools といいます。

https://docs.microsoft.com/en-us/dotnet/core/whats-new/dotnet-core-3-0#local-tools

使うプロジェクトやソリューションで、dotnet new tool-manifest を実行します。

dotnet new tool-manifest

すると、.config/dotnet-tools.json が生成されます。

ls .config

.NET Core Tools で配布している中から、使いたいツールを追加します。

dotnet tool install KustomizeConfigMapGenerator

ツールが追加されたことが json でわかります。

{
  "version": 1,
  "isRoot": true,
  "tools": {
    "kustomizeconfigmapgenerator": {
      "version": "0.2.1",
      "commands": [
        "dotnet-kustomizeconfigmapgenerator"
      ]
    }
  }
}

コマンドでも取得できます。

$ dotnet tool list

dotnet tool list
パッケージ ID                         バージョン      コマンド                                    マニフェスト          
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
kustomizeconfigmapgenerator      0.2.1      dotnet-kustomizeconfigmapgenerator      C:\git\ConsoleApplication100\.config\dotnet-tools.json

ビルドイベントに仕掛けてみましょう。

dotnet tool restore
dotnet tool run dotnet-kustomizeconfigmapgenerator

実行前に dotnet tool restore でリストアを忘れないようにします。リストア時に、実行できるコマンドが表示されるので親切です。

$ dotnet tool restore

Tool 'kustomizeconfigmapgenerator' (version '0.2.1') was restored. Available commands: dotnet-kustomizeconfigmapgenerator

CLI から実行するときも、dotnet tool run dotnet-kustomizeconfigmapgenerator で実行できます。

dotnet-tools.json マニフェストの解決順は次の通りです。

  1. 現在のパスの .config フォルダー ./.config/dotnet-tools.json
  2. 現在のフォルダー./dotnet-tools.json
  3. 親フォルダー ../dotnet-tools.json
  4. ルートにたどり着くまでの親フォルダを順次

REF

What's new in .NET Core 3.0 | Microsoft Docs

.NET Core ツールの使用に関する問題のトラブルシューティング - .NET Core CLI | Microsoft Docs

Local Tools Early Preview Documentation · Issue #10288 · dotnet/cli

Limit DotNetCliToolReference Tools to .NET Core 2.2 and Below · Issue #3115 · dotnet/sdk

.NET ローカル ツールの使い方 - Qiita