.NET Core Global ToolsをMcMaster.Extensions.CommandLineUtilsで手軽に作ってみる

このエントリーをはてなブックマークに追加

.NET Core 2.1から.NET Core Global Toolsというものが追加されました。
それによってNuGetを使ってコマンドラインツールを提供できるようになるみたいです。今回はコマンドラインの引数を簡単にパースしてくれるMcMaster.Extensions.CommandLineUtilsを使いたくなったので、簡単なコマンドラインツールを作ってみます。

GitHub - natemcmaster/CommandLineUtils: Command line parsing and utilities for .NET

Command line parsing and utilities for .NET. Contribute to natemcmaster/CommandLineUtils development by creating an account on GitHub.

github.com

GitHub - natemcmaster/CommandLineUtils: Command line parsing and utilities for .NET
Go to GitHub - natemcmaster/CommandLineUtils: Command line parsing and utilities for .NET

また、今回作ったものはいつも通りGithubに公開しています。

GitHub - MeilCli/PenguinCommand: Sample of .NET Core Global Tools

Sample of .NET Core Global Tools. Contribute to MeilCli/PenguinCommand development by creating an account on GitHub.

github.com

GitHub - MeilCli/PenguinCommand: Sample of .NET Core Global Tools
Go to GitHub - MeilCli/PenguinCommand: Sample of .NET Core Global Tools

プロジェクト構成

.NET Core Global Toolsということもあって、コンソールアプリケーションプロジェクトは.NET Coreである必要があります。また、今回使いたかったMcMaster.Extensions.CommandLineUtilsは.NET Standardにも対応している便利設計なため、コード本体は.NET Standardプロジェクトにすることにしました。

具体的には以下の構成です:

  • PenguinCommand.NETCore
    • PenguinCommand.NETStandardを参照
  • PenguinCommand.NETStandard

あと、こないだTwitterで話題になったソリューション内のプロジェクトすべてのC# LangVersionを一括で変更できるDirectory.build.propsをソリューション直下に配置しています。
もちろん中身はこれです:

<Project>
 <PropertyGroup>
   <LangVersion>latest</LangVersion>
 </PropertyGroup>
</Project>

C#erなら無条件で最新バージョン使うよなぁ?という感じにlatest指定。ちなみにVisual Studio 2019 previewでこの指定をしてもC# 8.0 previewにはならないみたいです。8.0と明示しないといけないみたいですが、PCの容量不足でVisual Studio 2019 previewをインストールできてないので確かめてないです……

あと、このファイルをVisual Studio上から追加した場合はプロジェクト設定に反映させるためにソリューションを開きなおすなどして再読み込みしないといけませんので注意!

コマンドの用意

今回は実際にコマンドラインツール作成時に使いそうなMcMaster.Extensions.CommandLineUtilsのAttribute APIを使います。サンプルもそれなりに用意してくれてる親切ライブラリのようで、このサンプルに似た作りにします。

Attribute APIでは[HelpOption("--help|-h")]のような属性を付けて実際のコマンドと結びつけます。全てのコマンドにHelpOptionを対応させたいので基底クラスを用意してあげます

    [HelpOption("--help|-h")]
    public abstract class BaseCommand
    {
        protected virtual int OnExecute(CommandLineApplication application)
        {
            return 0;
        }
    }

基底クラスを用意したら、コマンドの引数がなかった時に呼ばれる?ルートコマンドを用意し、そこにサブコマンドを結び付けます

    [Command]
    [Subcommand("show", typeof(ShowCommand))]
    [Subcommand("list", typeof(ListCommand))]
    public class Command : BaseCommand
    {
        protected override int OnExecute(CommandLineApplication application)
        {
            application.ShowHelp();
            return base.OnExecute(application);
        }
    }

サブコマンドはこんな感じでいいでしょう:

    [Command(Description = "Show the penguin.")]
    public class ShowCommand : BaseCommand
    {
        [Option("-l|--logo")]
        public bool IsLogo { get; }

        protected override int OnExecute(CommandLineApplication application)
        {
            string text = IsLogo ? Constant.PenguinLogo : Constant.Penguin;
            Console.WriteLine(text);
            return base.OnExecute(application);
        }
    }

詳しいAPIは公式ドキュメントを見ればだいたいわかるはずです:

https://natemcmaster.github.io/CommandLineUtils/index.html

コマンドを用意できたら.NET CoreプロジェクトでCommandLineApplicationを実行するだけです。

    internal class Program
    {
        private static void Main(string[] args) => CommandLineApplication.Execute<Command>(args);
    }

デバッグ

コンソールアプリケーションを作りながらコマンドのデバッグをしたいときはVisual Studio上からはめんどくさいので、dotnetコマンドを叩けるCLI上でデバッグしましょう。

Windows環境ならエクスプローラーで該当のプロジェクト(今回はPenguinCommand.NETCore)のディレクトリを開き、左上のファイルのとこからPowerShellを開けば該当プロジェクトがCurrentDirectoryなPowerShellが開けます。

引数有りのコマンドを動かすには、コンソールアプリケーションに引数を与えて実行すればいいのでdotnet run -- {コマンド 引数}といった感じにコマンドを実行しましょう。

先ほど作ったshowコマンドならdotnet run -- show -lみたいな感じになります。

.NET Core Global Toolsの作成

コンソールアプリケーション自体を作れたらそれを.NET Core Global Toolsに合わせた形式にします。公式ドキュメントはこちらです↓

チュートリアル: .NET ツールを作成する - .NET CLI

.NET ツールを作成する方法について説明します。 ツールは、.NET CLI を使用してインストールするコンソール アプリケーションです。

docs.microsoft.com

チュートリアル: .NET ツールを作成する - .NET CLI
Go to チュートリアル: .NET ツールを作成する - .NET CLI

公式ドキュメントの解説が丁寧すぎて言うことはあまりありませんが、コンソールアプリケーションのcsprojに<PackAsTool><ToolCommandName>を追加します

こんな感じ:

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>netcoreapp2.1</TargetFramework>

        <PackAsTool>true</PackAsTool>
        <ToolCommandName>penguin</ToolCommandName>
    </PropertyGroup>

公式ドキュメントでは<PackageOutputPath>も記載されてますが、これは生成されるnupkgファイルの場所を変えるもので、今回はデフォルトのままにします。

NuGet packageの作成はコマンド上ではdotnet packとするだけで生成されます。デフォルトでは/bin/Debug/に配置されるかと思います。

それをdotnet tool install --globalでインストールすることになるのですが、今回はNuGet公開しないローカルなものとしてインストールするので--add-sourceオプションでNuGet packageが作成されたディレクトリを指定することにします。

作成したサンプルではこのようなコマンドです: dotnet tool install --global --add-source bin\Debug PenguinCommand.NETCore

無事インストールされたら以下のようなメッセージが出ます:

次のコマンドを使用してツールを呼び出せます。penguin
ツール 'penguincommand.netcore' (バージョン '1.0.0') が正常にインストールされました。

今回はNuGet packageのバージョンを指定しなかったので、バージョン1.0.0となりましたが、実際に配布するものの場合はちゃんとバージョン指定しないといけませんね。

インストールできたら<ToolCommandName>で指定したコマンド名を叩けば作成したコンソールアプリケーションが実行されます。

penguin showとかpenguin --helpとかそんな感じで