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で大きく変わった体験の1つが.NET Core Global Toolです。 コンセプトとしてNPM global toolsを意識しているだけあり、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 で満たされるケース

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

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

.NET Core Global Tools で困るケース

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

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

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

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

  • 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