.NET ビルド小技集 (4)

前回の .NET ビルド小技集 (3) では、PowerShell スクリプトでプロジェクトをビルドして ZIP ファイルを作成しました。
今回はさらに、バージョン番号をインクリメントする方法を追加します。

 

PowerShell でバージョンをインクリメントしてビルドする

前回までの方法では、Release ビルドの前に手動で AssemblyInfo.cs のバージョンを編集しなければなりませんでした。
今回は、バージョンのインクリメントも PowerShell で自動化します。

前回のファイルに IncrementVersion-cs.ps1 を追加しました。
このスクリプトで AssemblyInfo.cs 内の
AssemblyVersion 属性および AssemblyFileVersion 属性の値のビルド番号を 1 だけ増加させています。
例えば、1.0.2.0 が 1.0.3.0 に、1.0.2 が 1.0.3 に変わります。

Add-Type Cmdlet を利用しており実質的には C# のコードで、
文字列の検索・置換には正規表現を利用しています。

 

これで、リリース時にはスクリプトを PowerShell で実行するだけです。

image

実行結果:

image

 

前回: .NET ビルド小技集 (3)

作成したサンプル
BuildSample (GitHub)

参照
Add-Type
.NET Framework の正規表現
Build Version Increment Add-In Visual Studio
第4回 ミッション:ビルドを自動化セヨ!

たまに利用する .NET Tips 集
NuGet パッケージを作成して公開する

広告
カテゴリー: ALM, ツール. タグ: , . 3 Comments »

.NET ビルド小技集 (3)

前回の .NET ビルド小技集 (2) では、プロジェクトのビルド イベントで ZIP ファイルを作成しました。
今回は、ビルド自体を PowerShell で実行する方法についてです。

 

PowerShell でビルドする

前回までの方法では、ビルドのたびにコンテンツ ファイルがコピーされ、ZIP ファイルが作成されますが、
現実の運用では ZIP ファイルを作成するのは毎回である必要はなく、
アプリをリリースするときの Release ビルドだけでよいでしょう。

そこで今回は、Release ビルドと ZIP ファイルの作成を PowerShell で実行することにします。
ただし、xcopy によるファイルのコピーはデバッグ時にも必要だと考えられるため、プロジェクトのビルド イベントに残します。

image

PowerShell スクリプトで、MSBuild.exe および前回作成した CreateZipForAssembly.ps1 を呼び出します。

 

以上で準備は完了です。
リリース時には AssemblyInfo.cs でバージョンを設定して、この ps1 ファイルを PowerShell で実行します。

image

image

実行結果:

image

 

前回: .NET ビルド小技集 (2)
次回: .NET ビルド小技集 (4)

作成したサンプル
BuildSample (GitHub)

バージョン情報
Visual Studio 2013

参照
Build Version Increment Add-In Visual Studio
第4回 ミッション:ビルドを自動化セヨ!

たまに利用する .NET Tips 集
NuGet パッケージを作成して公開する

カテゴリー: ALM, ツール. タグ: . 2 Comments »

.NET ビルド小技集 (2)

前回の .NET ビルド小技集 (1) では、プロジェクトのビルド イベントを利用しました。
今回はビルド イベントで ZIP ファイルを作成する方法についてです。

 

ZIP ファイルを作成する

前回説明したファイルのコピーに加えて、ビルドにより出力された実行ファイル一式を圧縮して ZIP ファイルにします。
PowerShell では .NET のライブラリを利用することができるため、
PowerShell スクリプトで ZIP ファイルを作成することにします。
(PowerShell には圧縮のための Compress-Archive Cmdlet があったりもしますが。)

まず、ZipFile.CreateFromDirectory メソッドをラップしたスクリプトを用意します。
引数は、アセンブリのディレクトリと ZIP ファイルのパスです。

そして、前回のプロジェクトのビルド イベントでこのスクリプトを呼び出します。

cd $(ProjectDir)
xcopy Data $(OutDir)Data /D/E/C/I/H/Y
powershell -ExecutionPolicy Unrestricted ..\Tools\CreateZip.ps1 $(OutDir) ..\Downloads\$(TargetName).zip

これで、ビルドのたびに ZIP ファイルが作成されます。

image

 

ちなみに、PowerShell では C# のコードを埋め込むことができます。
次のようにしても同様の結果が得られます。

 

ZIP ファイルの名前にバージョンを含める

さらに、ZIP ファイルの名前を、App1-1.0.0.zip のようにバージョン番号が含まれるようにしてみましょう。
バージョン番号として、AssemblyInfo.cs の AssemblyFileVersion 属性に指定された値を取得します。

先ほどの CreateZip.ps1 を拡張して、
対象のアセンブリから AssemblyFileVersion 属性を取得して、
ZIP ファイルの名前が App1-1.0.0.zip の形式になるように組み立てます。
このスクリプトの引数は、アセンブリのパスと出力先のディレクトリとしています。

あとは、プロジェクトのビルド イベントでこのスクリプトを呼び出します。

cd $(ProjectDir)
xcopy Data $(OutDir)Data /D/E/C/I/H/Y
powershell -ExecutionPolicy Unrestricted ..\Tools\CreateZipForAssembly.ps1 $(TargetPath) ..\Downloads

AssemblyFileVersion 属性には、「1.0.1-Alpha」のように任意の文字列を指定できるようです。
(ただし、AssemblyVersion 属性は数値または * でなければビルド エラーになります。)

image

実行結果:

image

 

次回は、ビルド自体を PowerShell で実行する方法についてです。

前回: .NET ビルド小技集 (1)
次回: .NET ビルド小技集 (3)

作成したサンプル
BuildSample (GitHub)

バージョン情報
Visual Studio 2013

参照
ZipFile.CreateFromDirectory メソッド

Add-Type
PowerShell の Add-Type と [Reflection.Assembly]
PowerShellスクリプト内でC#コードを書いて使う

Compress-Archive
PowerShell v5 の新機能紹介 – Zipファイルの操作が可能に

カテゴリー: ALM, ツール. タグ: , . 2 Comments »

.NET ビルド小技集 (1)

.NET アプリケーション開発のビルドに関する小技を集めました。
以下の内容の実現方法について取り扱います。

  • ビルド時にファイルをコピーする
  • ZIP ファイルを作成する
  • ZIP ファイルの名前にバージョンを含める
  • PowerShell でビルドする
  • PowerShell でバージョンをインクリメントしてビルドする

 

ビルド時にファイルをコピーする

.NET アプリケーションをビルドすると、
プロジェクト フォルダーの下の bin\Debug や bin\Release に実行ファイルが出力されます。
このとき同時に、画像、動画、データなどのコンテンツ ファイルをコピーする方法を紹介します。
(サイズの小さい画像ファイルであればアセンブリ内のリソースとして埋め込んで使いますが。)

よくある方法としては、コンテンツ ファイルをプロジェクトに含めて、プロパティで [出力ディレクトリにコピー] の項目を設定します。
ただしこれだと、ファイルごとに設定しなければならないという欠点があります。

imageimage

 

この欠点を回避するには、プロジェクトのビルド イベントを利用するとよいでしょう。
プロジェクトのプロパティを開き、[ビルド イベント] を選択します。
この画面で、ビルド時に実行するコマンド ラインを指定できます。 

ファイルをコピーするには copy、フォルダーをコピーするには xcopy を利用します。

xcopy $(ProjectDir)Data $(TargetDir)Data /D/E/C/I/H/Y

image

$(ProjectDir) などの形式の文字列はマクロと呼ばれるもので、
ソリューションやプロジェクトにとっての特別な文字列を扱うことができます。
[ビルド後の編集] をクリックするとエディターが開きます。
ここではマクロの実際の値を確認しながら自動で挿入できます。

image

 

上記で使用した xcopy の各オプションの説明も挙げておきます。

  • /D コピー元の日付がコピー先の日付より新しいファイルだけをコピーします。
  • /E ディレクトリまたはサブディレクトリが空であってもコピーします。"/S /E" と同じ意味です。
  • /C エラーが発生してもコピーを続けます。
  • /I 指定されたコピー先が存在しない場合、コピー先をディレクトリとしてコピーします。
  • /H 隠しファイルやシステム ファイルもコピーします。
  • /Y 既存のファイルを上書きする前に確認のメッセージを表示しません。

 

このように設定してビルドを実行すれば、出力ディレクトリにコンテンツ ファイルがコピーされます。

image

 

なお、ビルド イベントでコマンド ラインが実行されるときの作業ディレクトリは、
$(TargetDir)、つまり bin\Debug や bin\Release などの出力ディレクトリです。
複数のコマンドを実行する場合など、相対パスにしたほうがわかりやすくなるときは、

cd $(ProjectDir)
xcopy Data $(OutDir)Data /D/E/C/I/H/Y

のように作業ディレクトリを設定するとよいでしょう。

次回は、ビルド時に ZIP ファイルを作成する方法についてです。

次回: .NET ビルド小技集 (2)

作成したサンプル
BuildSample (GitHub)

バージョン情報
Visual Studio 2013

参照
Copy
Xcopy

カテゴリー: ALM, ツール. タグ: . 1 Comment »

Azure Table の検索条件を LINQ で指定する

Azure Storage の SDK (Windows Azure Storage) を利用して .NET のクライアントから Table のデータを取得する際に、
検索条件を指定しようとすると、通常の実装では次のようなコードになり少し複雑です。
この例では、フィルター条件を 2 つ指定しています。


var query = new TableQuery<Person>()
    .Where(TableQuery.CombineFilters(
        TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "2015"),
        TableOperators.And,
        TableQuery.GenerateFilterConditionForInt("Age", QueryComparisons.LessThan, 20)));

var result = PeopleTable.ExecuteQuery(query).ToArray();


ここで、Person  は TableEntity を継承したクラスで、PeopleTable は CloudTable 型のオブジェクトです。

結局、上記の Where メソッドに渡されるのは、

(PartitionKey eq ‘2015’) and (Age lt 20)

という文字列になります。
これなら string.Format メソッドでもよいのではないのかという気もしますが、
プログラミングのミスを防ぐためには、フィルターや射影などの検索条件は LINQ で指定したいところです。

検索条件を LINQ で指定する方法として TableServiceContext クラスを使う方法もあるようですが、
現在は Obsolete 属性が指定されており、非推奨となっています。

とはいえ、自力で IQueryable<T> を実装するのも骨が折れるので、
ここでは簡易的に、TableQuery<T> の拡張メソッドとして Select および Where メソッドを実装していきます。
ラムダ式で指定された検索条件を式ツリーとして受け取って解析し、動的にクエリを生成します。

このような TableHelper クラスを実装することで、Azure Table の検索条件を LINQ で指定できるようになります。
ただし、文字列の不等式については、String クラスの演算子として不等号が定義されていないため、

p.LastName >= "W"

と書くことができず、

p.LastName.CompareTo("W") >= 0

のようにせざるを得ませんでした。

 

作成したサンプル
ExpressionsConsole (GitHub)

バージョン情報
Windows Azure Storage 6.0.0

参照
Windows Azure Storage
Expression<TDelegate> クラス
TableQuery<TElement> Class

Windows Azure Storage Extensions
テーブル サービスに対する LINQ クエリの作成 (古い形式)
TableServiceContext Class (古い形式)

エンティティを匿名型で手軽に実装する (2)

前回のエンティティを匿名型で手軽に実装する (1) で作成した EntityType<TEntity> では、
インスタンスを初期化するために毎回 ConstructorInfo を経由していましたが、
少し重い処理のため、回数が多いと処理時間に影響します。

そこで、あらかじめコンストラクターをラムダ式の式ツリーからコンパイルしておくという方法があります。
先にコードを示します。

取得した ConstructorInfo をもとに、

p => new TEntity((int)p[0], (string)p[1])

のような処理に相当する式ツリーを構築してコンパイルすることで、object[] から TEntity を初期化する関数が得られます。

CreateEntity メソッドの呼び出しを 100 万回実行して計測してみると、手元の環境だと前回のコードでは

  • EntityType の初期化: 0.0006 秒
  • CreateEntity メソッド 100 万回: 0.6 秒

だったのが、今回のコードでは

  • EntityType の初期化: 0.006 秒
  • CreateEntity メソッド 100 万回: 0.06 秒

になりました。

 

前回: エンティティを匿名型で手軽に実装する (1)

作成したサンプル
ExpressionsConsole (GitHub)

参照
Expression<TDelegate> クラス

エンティティを匿名型で手軽に実装する (1)

ここでは、次のようなものをエンティティ型と呼ぶことにします。

  • プロパティが定義されている
  • 各プロパティの値を指定してインスタンスを初期化する方法 (コンストラクターなど) が用意されている

通常、これを C# で実装すると次の図のようになり、少し手間がかかると思うことがあります。
Class1 はコンストラクターの実装が必要であり、Class2 はインスタンス生成時にプロパティ名を指定する必要があります。

エンティティ型の宣言

 

そこで今回は、匿名型を活用してこのようなエンティティ型を定義する方法を考えてみたいと思います。

実は匿名型は、プロパティは読取り専用で定義され、プロパティの数だけ引数を持つコンストラクターが 1 つだけ定義されます。
つまり、ちょうど上記の Class1 のような構造をしています。

このコンストラクターにアクセスできれば、任意のタイミングで匿名型のインスタンスを生成できるというわけです。
そこで、次のように EntityType クラスを実装してみました。

匿名型によりプロパティを宣言し、CreateEntity メソッドによりインスタンスを初期化できます。
CreateEntity メソッドの引数は params object[] のため IntelliSense は利きませんが、簡単に書けている気がします。
ただし匿名型のため、ローカル スコープを抜けられないという制限があります。

 

次に、これを CSV ファイルの読み込みに応用します。
CsvFile クラスで、読み込んだデータを指定された型に変換します。

(なお、ConvertHelper.cs は省略されています。完全なソースコードは ExpressionsConsole – GitHub にあります。)

このようにすると、CsvFile クラスを呼び出す側では IntelliSense が利き、LINQ to Objects でアクセスできます。

CsvFile (IntelliSense)

 

さて、インスタンスを生成するときに毎回 ConstructorInfo にアクセスするのは少しコストがかかるので、
次回は式ツリーを利用してパフォーマンスを改善したいと思います。

次回: エンティティを匿名型で手軽に実装する (2)

作成したサンプル
ExpressionsConsole (GitHub)

参照
匿名型 (C# プログラミング ガイド)