やらなイカ?

たぶん、iOS/Androidアプリの開発・テスト関係。

カスタムRoslyn AnalyzerをUPMパッケージとして配布する

Unity 2020.2でRoslynアナライザによる静的解析を行えるようになりましたが*1、NuPkgやDLLで配布してUnityプロジェクトに導入するのはやや面倒です。

そこで、アナライザをUnity Package Manager (UPM) パッケージとして配布する方法を紹介します。アナライザ単体での配布はもちろん、自作ライブラリに関連するアナライザをライブラリのUPMに同梱して配布できます。

前提は、Unity 2020.3.4以降です。UnityバージョンごとのRoslynアナライザまわりの機能制限については次の記事にまとめています(Unity 2021.2の差分まで反映済)。

www.nowsprinting.com

アナライザ入りUPMパッケージの作成

アナライザをNuGetから取得もしくはビルドしたDLLは、一旦Assetsフォルダ下に置いて*2 Inspectorウィンドウを開き、次のようにmeta情報を設定します。

  • Select platforms for pluginをすべてoff
  • Asset Labelsに RoslynAnalyzer を設定

f:id:nowsprinting:20211101080608p:plain:w300

"Apply"をクリックして適用したら、アナライザDLLを配布するUPMパッケージ下に移動します。 このとき必ず、Assembly Definition File (.asmdef) の影響下に配置してください*3

例えば次のようなディレクトリ構成にします。 ライブラリに同梱するときは、パスの途中に他の.asmdefを置いてしまうと別アセンブリ扱いになりますので注意してください。

Packages/your.library
├── Editor
│   └── YourLibrary.Editor.asmdef
├── Runtime
│   ├── Analyzers
│   │   ├── your.library.analyzers.dll
│   │   └── your.library.analyzers.dll.meta
│   ├── YourLibrary.Runtime.asmdef
│   └── YourLibraryCode.cs
├── Tests
│   └── YourLibrary.Tests.asmdef
├── package.json
└── package.json.meta

これで、このUPMパッケージを使用するプロジェクトでは、アナライザを含んだアセンブリ(上例では YourLibrary.Runtime )を"Assembly Definition References"に追加することで(そのアセンブリ内でのみ)アナライザが有効になります。

※ただし、Unity Editor上では。

なお、ライブラリに同梱でなくアナライザだけを配布したい場合でも、アセンブリが作られる必要があります。そのため、asmdef下にはDLLだけでなく、何かしらダミーのC#スクリプトファイルも置く必要がある点に注意してください。[2/6追記]

IDEでの振る舞い[1/4追記]

Unityに対応しているIDE(JetBrains Rider, Visual Studio, Visual Studio Code)には、Unityエディターのメニューから "Open C# Project" を実行したときに.slnおよび.csprojファイルを生成してくれるプラグインパッケージが提供されています。

プラグインパッケージが生成する.csprojファイルが上記Unityエディターと同じ振る舞いになる*4のは、次のバージョン以降です。

  • Unity 2020.3.6以降、もしくは Unity 2021.1.2以降

かつ

IDEプラグインパッケージ向けのAnalyzer Importerスクリプト

本記事執筆時点で、各IDE(JetBrains Rider, Visual Studio Code, Visual Studio)のプラグイン*5はRoslynアナライザに正しく対応していません。

例えばJetBrains Rider Editor package v3.0.7では、Assets下および、Packages直下に実体のある組み込みパッケージ (Embedded package) のみが、.asmdefの依存関係に関係なく適用されます。

IDE側の対応がなされるまでは 上記バージョンの組み合わせを満たせないプロジェクトでは、次のスクリプトをUPMパッケージに同梱して配布するとよいでしょう。 パス設定など不要で、そのままアナライザと同じ.asmdef下に放り込めば動くようになっています。

gist.github.com

ただし、以下の制限事項があります。

  • .asmdefのファイル名(拡張子は除く)と、アセンブリ名(Inspectorウィンドウの"Name")を一致させてください
  • .asmdefの参照の連鎖には対応していません。アセンブリA(ここにアナライザ) ← アセンブリB ← アセンブリC と参照した場合、アナライザが有効になるのはアセンブリBまでです

UPMパッケージに組み込んだもの(テスト付き)はこちら。デフォルトブランチでないので注意。

github.com

アナライザの有効範囲についての補足

アナライザをインポートしたプロジェクト側では、Unity Editorでは.rulesetファイル、Riderでは.editorconfigファイルによって診断の重大度 (Severty) を制御できます。 従って、Severtyをnoneにすることで実質的にアナライザを無効化することはできます。

しかし、.rulesetは影響範囲をアセンブリつまり.asmdef単位で指定する必要があり、アナライザおよびアセンブリが多数あるプロジェクトでは管理が難しくなってきます。

.editorconfigではパスで影響範囲を指定できるため.rulesetよりは楽ですが、やはり.asmdefの依存関係による有効範囲制御が好ましいと思います。

UPMパッケージの対応Unityバージョンについて[11/1追記]

UPMパッケージのマニフェストファイル (package.json) には、パッケージが動作する最低Unityバージョンを指定できます。

アナライザのみを配布するパッケージであれば、次のようにUnity 2020.3.6f1*6と各IDEsプラグインパッケージのバージョンを指定することで「インストールしたけれど動作しない」といった混乱を回避できます。 unityRelease 属性はUnity 2020.3で追加されたものですが、それ以前のバージョンは unity 属性で除外できるので問題ないでしょう。

{
  "name": "your.analyzer.name",
  "version" "1.0",
  "unity": "2020.3",
  "unityRelease": "6f1",
  "dependencies": {
    "com.unity.ide.rider": "3.0.9",
    "com.unity.ide.visualstudio": "2.0.11",
    "com.unity.ide.vscode": "1.2.4"
  }
}

実際にNUnit.AnalyzersをUPMパッケージ化したものを作ってみましたので参考にしてください。 ただし、Unity Editor上でこのアナライザが動作するのはUnity 2021.2以降*7のため、unity属性は2021.2にしてあります。

github.com

ただ、この指定では「Unity Editorでは動作しなくていいがIDEで使いたい」というニーズをカバーできないのが悩みどころです。

また、ライブラリにアナライザを同梱する場合には、特に最低Unityバージョンを変更する必要はないでしょう。 Unity 2020.3.3以前であっても、アナライザが動作しないだけで悪影響はありません。

参考

docs.unity3d.com

docs.unity3d.com

qiita.com

github.com

github.com

*1:Roslynアナライザの作成・導入方法はこちらにまとまっています https://swet.dena.com/entry/2021/05/25/100000

*2:Packages下に置くとInspectorウィンドウでAsset Labelsが設定できません

*3:もし、UPMパッケージをインポートしたプロジェクトの全コードに対して無条件に適用してよいアナライザであるなら、アナライザを.asmdef下に置かないで配布することもできます。また、.asmdefのAuto Referencedをonにすると、Assembly-CSharpアセンブリに含まれるコードに対して自動で適用されます。Assembly-CSharp-Editorには適用されません

*4:具体的には、該当csproj中に<Analyzer>ノードが挿入される

*5:Unityのメニューから"Open C# Project"を実行したとき、.slnおよび.csprojファイルを生成する役割

*6:ただしこの設定では2021.1.0f1〜2021.1.2f1未満が漏れるため、安全に倒したければ2021.1.2f1以降としてください

*7:原因(理由)は未調査