dotnet newのカスタムテンプレート

Published on
Updated on

はじめに

競プロ用のプロジェクトテンプレートを整備したので、dotnet newのカスタムテンプレート作成の備忘録です。

dotnet new のカスタムテンプレートとは

公式の情報はこちら

.NETのプロジェクトを作成する際、dotnetコマンドを利用してプロジェクトを生成します。 例えば、コンソールアプリケーションを作成する場合、

dotnet new console -n Sample

のようなコマンドを実行することで、Sampleという名称のプロジェクトが作成されます。 これは、dotnet newコマンドで、consoleというデフォルトテンプレートを使ってプロジェクトを生成するという意味になります。

このdotnet newコマンドに、プロジェクトやスクリプトをカスタムテンプレートとして登録しておくことで、プロジェクトやファイルの作成を使いまわすことができます。

既定のテンプレートとして、dotnet newコマンドに-l|--listオプションをつけて実行すると、現在インストールされているdotnet newコマンドで生成できるテンプレートを確認することができます。

dotnet new -l

作ってみる

テンプレートの基本として、テンプレート化したいプロジェクトのディレクトリ下に、.template.configのディレクトリを作成し、さらにその下に、template.jsonを作成します。 そして、template.jsonにプロパティを設定し、dotnet newコマンドを使ってインストールすることで、テンプレートを使うことができるようになります。

dotnet new -i path-to-template

競技プロ用のプロジェクトテンプレートでは、次の3つをテンプレートとして準備します。

  • プロジェクト
  • 解答用のクラス
  • テスト用のクラス

プロジェクトのテンプレート

プロジェクトでは、解答用のクラスとテスト用クラスを配置するための骨組みとしてのプロジェクトを生成するようにします。

Template.Project/
    |
    |- Tasks/
    |    |
    |    |- Tasks.csproj
    |
    |- Tests/
    |    |
    |    |- Tester.cs
    |    |- Tests.csproj
    |
    |- Template.Project.sln

このプロジェクトをベースとして、Project/下に.template.config/ディレクトリを作成し、その下にtemplate.jsonを作成します。

Template.Project/
    |
    |- .template.config
    |    |
    |    |- template.json
    ...

template.jsonでは、次のメンバを記述します。

メンバ 説明
$schema template.jsonのスキーマ
author テンプレートの作成者
classfication テンプレートの種類
tags テンプレートのタグ
identity テンプレートの識別子
name テンプレートの名前
shortName dotnet new で指定する際の名前 (例: dotnet new cpproj)
sourceName テンプレート使用時に置き換える文字列 (dotnet newコマンドに、-n|--nameオプションで名前を指定することで、指定された文字列を全てその名前に置換することができます)
preferNameDirectory 出力先ディレクトリがない場合テンプレートのディレクトリを作成するか (既定値: false)

例えば、上記のプロジェクトでは次のようなjsonを記述します。

{
    "$schema": "http://json.schemastore.org/template",
    "author": "AconCavy",
    "classifications": [
        "C#",
        "Console"
    ],
    "tags": {
        "language": "C#",
        "type": "project"
    },
    "name": "Template Project",
    "identity": "AconCavy.Template.Project",
    "shortName": "cpproj",
    "sourceName": "Template.Project",
    "preferNameDirectory": true
}

sourceNameに設定した文字列は、テンプレート以下のすべての対象の文字列が置換されるため、dotnet new cpproj -n Sampleを実行した場合、Template.Project/ディレクトリ、Template.Project.slnSample/ディレクトリ、Sample.slnに置換されて生成されます。ファイル内の文字列も置換されるため注意が必要です。

この状態で、dotnet new -i path-to-templateコマンドでインストールし、dotnet new cpproj -n Sampleを実行することで、上記のプロジェクトテンプレートをもとに以下のようなプロジェクトが生成されます。

Sample/
    |
    |- Tasks/
    |    |
    |    |- Tasks.csproj
    |
    |- Tests/
    |    |
    |    |- Tester.cs
    |    |- Tests.csproj
    |
    |- Sample.sln

コマンドの追加オプション

また、Task.csprojTests.csprojのターゲットフレームワークをテンプレート生成時に指定できるようにするため、dotnet new cpprojコマンドにオプションを追加します。

まず、.template.config下にdotnetcli.host.jsonを追加します。 symbolInfoメンバに、longNameのオプションにframeworkを、shortNamefをもったFrameworkというメンバを追加します。 追加することで、dotnet new cpprojにオプションとして、-f|--frameworkのオプションを付与することができるようになります。

{
    "$schema": "http://json.schemastore.org/dotnetcli.host",
    "symbolInfo": {
        "Framework": {
            "longName": "framework",
            "shortName": "f"
        }
    }
}

次にtemplate.jsonsymbolsというメンバを追加し、ここに先ほど定義したFrameworkメンバを追加します。 ここではオプションの振る舞いを定義します。

今回はターゲットフレームワークを.NET 5.NET Core 3.1を選択肢として定義します。 datatypechoiceにして、choicesに選択肢を定義します。 csprojTargetFrameworkに指定する文字列として、.NET 5の場合はnet5.0.NET Core 3.1の場合はnetcoreapp3.1choiceに設定します。 replacesに置換する文字列を、defaultValueにオプションを指定しない場合の文字列を設定します。

{
    ...
    "symbols": {
        "Framework": {
            "type": "parameter",
            "description": "The target framework for the project.",
            "datatype": "choice",
            "choices": [
                {
                    "choice": "net5.0",
                    "description": "Target net5.0"
                },
                {
                    "choice": "netcoreapp3.1",
                    "description": "Target netcoreapp3.1"
                }
            ],
            "replaces": "netcoreapp3.1",
            "defaultValue": "netcoreapp3.1"
        }
    },
    ...
}

そして、Tasks.csprojTests.csprojTargetFrameworkreplacesで設定した文字列を設定します。

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

  <PropertyGroup>
    ...
    <TargetFramework>netcoreapp3.1</TargetFramework>
    ...
  </PropertyGroup>
  ...

</Project>

この状態で、dotnet new cpproj -n Sample -f net5.0を実行することで、TargetFrameworknet5.0が設定されたプロジェクトを生成することができます。

解答用のクラスとテスト用のクラスのテンプレート

単一のファイルのみ生成するように、テンプレートを構築します。

Template.Solver/
    |
    |- .template.config/
    |    |
    |    |- template.json
    |
    |- Template.Solver.cs

Template.Tests/
    |
    |- .template.config/
    |    |
    |    |- template.json
    |
    |- Template.TestsTests.cs

プロジェクトのテンプレートの作り方と同様に、template.jsonを記述しますが、単一ファイルのみ生成させるため、preferNameDirectoryを削除、またはfalseにします。

解答用のsourceNameTemplate.Solverに、テスト用のsourceNameTemplate.Testsにすることで、dotnet newコマンドの-n|--nameオプションにSampleを指定すると、それぞれSample.csSampleTests.csが生成されます。

プロジェクトのパッケージ化

テンプレートが3つ用意できましたが、テンプレートをインストールする際にはそれぞれ個別にインストールが必要となります。 そのため、3つのテンプレートまとめて、1つのnugetパッケージを生成します。 3つのディレクトリを一つのディレクトリにまとめ、そのディレクトリと同じ階層にcsprojファイルを生成します。

CPTemplate/
    |
    |- content/
    |    |
    |    |- Template.Project/
    |    |- Template.Solver/
    |    |- Template.Tests/
    |
    |- CPTemplate.csproj

ディレクトリを整理したら、CPTemplate.csprojを編集し、ビルド情報を定義します。

メンバ 説明
PackageType nugetパッケージタイプ
PackageVersion パッケージのバージョン
PackageId パッケージの識別子
Title パッケージの名称
Authors パッケージの作成者
Description パッケージの説明
PackageTags パッケージのタグ
TargetFramework パッケージをビルドするためのターゲットフレームワーク
PackageProjectUrl プロジェクトURL
IncludeBuildOutput ビルド時に生成されるファイルをパッケージに含めるか
ContentTargetFolders パッケージ化するプロジェクトのルートがcontentcontentFiles以外の場合は設定する
Content パッケージに含めるファイルや除くファイルを設定する
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <PackageType>Template</PackageType>
    <PackageVersion>1.0</PackageVersion>
    <PackageId>AconCavy.Templates</PackageId>
    <Title>Templates</Title>
    <Authors>AconCavy</Authors>
    <Description>sample template.</Description>
    <PackageTags>dotnet-new;templates;competitive-programming</PackageTags>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <PackageProjectUrl>https://github.com/AconCavy/CompetitiveProgrammingTemplateCSharp</PackageProjectUrl>

    <IncludeBuildOutput>false</IncludeBuildOutput>
    <ContentTargetFolders>content</ContentTargetFolders>
  </PropertyGroup>

  <ItemGroup>
    <Content Include="content/**/*" Exclude="content/**/bin/**;content/**/obj/**" />
    <Compile Remove="**/*" />
  </ItemGroup>

</Project>

また、それぞれのテンプレートのtemplate.jsongroupIdentityを追加します。

// Project
"groupIdentity": "AconCavy.Templates.Project"

// Solver
"groupIdentity": "AconCavy.Templates.Solver"

// Tests
"groupIdentity": "AconCavy.Templates.Tests"

dotnet packコマンドを実行することでnugetパッケージを生成することができます。

dotnet pack

実行後、bin/Debug/下に{PackageId}.{PackageVersion}.nupkgが生成されます。

CPTemplate/
    |
    |- bin/
    |    |
    |    |- Debug/
    |    |    |
    |    |    |- AconCavy.Templates.1.0.0.nupkg
    ...

このnupkgdotnet newコマンドでインストールすることで、3つのテンプレートを1回でインストールすることができます。

dotnet new -i ./bin/Debug/AconCavy.Templates.1.0.0.nupkg

まとめ

dotnet newのカスタムテンプレートの作り方と、テンプレートのパッケージ化の手順をまとめました。

  • テンプレートのルートに.template.configディレクトリを作成し、内にtemplate.jsonを作成する。
  • テンプレートが複数ある場合は1つのディレクトリにまとめ、dotnet packコマンドでパッケージ化する。