C#プロジェクトにNullable要素を追加するツールを作った
いちいち手作業で書くの面倒だったので。
成果物
概要
.csprojファイルに<Nullable>enable</Nullable>
を挿入する。ただし、<Nullable>
要素が既存なら何もしない。
想定ユースケース
dotnet3.0.100にてdotnet new ...
コマンドを使いプロジェクトを作成した。しかし、<Nullable>
が設定されていない。dotnet3のC#8.0以降は、常にnull安全なコードを書くようにしたい。そこで、C#8.0プロジェクトファイルに<Nullable>enable</Nullable>
を挿入したい。
想定外
dotnet3のC#8.0以降における.csprojプロジェクトファイルを想定している。ただし、それ以外のファイルにも書けてしまえる場合がある。拡張子が.csproj
でXML形式なら対象となる。バージョンが古いものなども含む。
いずれにせよ、ファイルが破壊されてしまっても一切責任を持たない。
コマンド
csnull # カレントディレクトリにある.csprojが対象
将来、以下の機能を追加する可能性はある。だが、使う場面が少ないと判断したら実装しないと思う。
csnull -r # 再帰的。サブディレクトリにある.csprojも対象 # 以下 e, d, a, w は併用不可(相互に排他的) csnull -e # <Nullable>enable</Nullable>(デフォルト。全省略時と同等) csnull -d # <Nullable>disable</Nullable> csnull -a # <Nullable>annotations</Nullable> csnull -w # <Nullable>warnings</Nullable> csnull -h # ヘルプ csnull -v # バージョン
以下のような機能は追記しない。
追加しない機能 | 理由 |
---|---|
対象csprojファイルを検索するディレクトリを指定する | カレントディレクトリで代用できる。無駄に複雑化する |
<LangVersion>8.0</LangVersion> 追記 |
何のために使うか謎。バージョン管理は本件と別問題。 |
<LangVersion> があり8.0 以上か確認 |
デフォルトでは<LangVersion> が存在しないため無意味そう。 |
#nullable ディレクティブ追記 |
今回の要件である新規プロジェクトが対象なら<Nullable> で一括設定したほうがよい。 |
処理
- カレントディレクトリから
*.csproj
ファイルを探す - 1から
<Nullable>
要素を探す - もし2が存在しなければ
<Nullable>enable</Nullable>
を挿入する
ゴール
デフォルトのcsprojは以下のような内容である。
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp3.0</TargetFramework> </PropertyGroup> </Project>
上記に<PropertyGroup>
要素の子として<Nullable>enable</Nullable>
を挿入したい。
つまり以下のように変更したい。
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp3.0</TargetFramework> <Nullable>enable</Nullable> </PropertyGroup> </Project>
コード
Program.cs
using System; using System.IO; using System.Collections; using System.Collections.Generic; using System.Xml.Linq; using System.Xml; // XmlWriter using System.Text; // StringBuilder namespace CSharpProjectNullabler { class Program { static void Main(string[] args) { foreach (string path in GetProjectFiles()) { var xml = CreateNullableXDocument(path); using (var writer = CreateOmitXmlDeclarationWriter(path)) { xml.Save(writer); } } } static IEnumerable<string> GetProjectFiles() { //return Directory.GetFiles(System.Environment.CurrentDirectory, "*.csproj"); return Directory.EnumerateFiles(System.Environment.CurrentDirectory, "*.csproj"); // return Directory.EnumerateFiles(System.Environment.CurrentDirectory, "*.csproj", SearchOption.AllDirectories); } static XmlWriter CreateOmitXmlDeclarationWriter(string path) { XmlWriterSettings xws = new XmlWriterSettings(); xws.OmitXmlDeclaration = true; // XML宣言を出力しない xws.Indent = true; return XmlWriter.Create(path, xws); } static XDocument CreateNullableXDocument(string path) { XDocument xml = XDocument.Load(path); Console.WriteLine($"xml: {xml}"); Console.WriteLine($"<Project>: {xml.Element("Project")}"); XElement project = xml.Element("Project"); XElement propertyGroup = project.Element("PropertyGroup"); XElement? nullable = propertyGroup.Element("Nullable"); if (null == nullable) { propertyGroup.Add(new XElement("Nullable", "enable")); } Console.WriteLine($"propertyGroup: {propertyGroup}"); // XML宣言が追記されてしまう! <?xml version="1.0" encoding="utf-8"?> // xml.Declaration = null; // 効果なし // xml.Save(path); return xml; } } }
Releaseモードでビルドする
- dotnet build オプション
-c Release
$ dotnet build --configuration Release .NET Core 向け Microsoft (R) Build Engine バージョン 16.3.0+0f4c62fea Copyright (C) Microsoft Corporation.All rights reserved. /tmp/work/CSharpProjectNullabler/CSharpProjectNullabler.csproj の復元が 131.63 ms で完了しました。 CSharpProjectNullabler -> /tmp/work/CSharpProjectNullabler/bin/Release/netcoreapp3.0/CSharpProjectNullabler.dll ビルドに成功しました。 0 個の警告 0 エラー 経過時間 00:00:54.89
Debug
モードとほとんど変わらなかった。一部のファイルサイズがほんの少し小さくなったくらい。
解決した問題
.csprojに以下コードが追加されてしまう。
<?xml version="1.0" encoding="utf-8"?>
- XDocument.Declarationに
null
を設定すればいいのか?
XDocument xml = XDocument.Load(path);
xml.Declaration = null;
null
にしても出力されてしまう。一体どうすれば出力されないようにできるの?
なにこれ超面倒なんですけど。以下も参考にしつつコードを書いて解決できた。
- XmlWriter
- XmlWriter.Create(String, XmlWriterSettings)
- 指定したファイルパスに対するライタを生成する
- XmlWriter.Create(String, XmlWriterSettings)
XML宣言が出力されてしまう版
もしXML宣言が勝手に追記されちゃってもいいなら、以下で済む。
static void Main(string[] args) { foreach (string path in GetProjectFiles()) { SetNullable(path); } } static IEnumerable<string> GetProjectFiles() { return Directory.EnumerateFiles( System.Environment.CurrentDirectory, "*.csproj"); } static void SetNullable(string path) { XDocument xml = XDocument.Load(path); XElement project = xml.Element("Project"); XElement propertyGroup = project.Element("PropertyGroup"); XElement? nullable = propertyGroup.Element("Nullable"); if (null == nullable) { propertyGroup.Add(new XElement("Nullable", "enable")); } xml.Save(path); }
XML宣言出力のON/OFFくらいもっと簡単に設定したいのに。
所感
ビルドしてできた実際のコマンド名は、プロジェクト名と同じCSharpProjectNullabler
になってしまっている。csnull
とか短い名前にしたい。出力ファイルのリネーム設定とかできないのかな?
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch 9.0 2018-11-13 ※
- bash 4.4.12(1)-release ※
- SQLite 3.29.0 ※
- C# dotnet 3.0.100
$ uname -a Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux