ま、そんなところで。

ニッチな技術系メモとか、車輪を再発明してみたりとか.

debとrpmパッケージの命名規則

パッケージ命名規則

Linuxのパッケージを作ることになったので調査したメモです.

debパッケージの命名規則

${PACKAGE_NAME}_${VER_MAJ}.${VER_MIN}.${VER_BUILD}(-${VER_RELEASE}(${VER_RELEASE_SUFFIX}))_${ARCH_NAME}.deb

各要素は'_' (アンダーバー)を使用する.

  • PACKAGE_NAME --- パッケージ名
  • VER_MAJ --- メジャーバージョン
  • VER_MIN --- マイナーバージョン
  • VER_BUILD --- ビルドバージョン
  • VER_RELEASE --- リリース番号
  • VER_RELEASE_SUFFIX
    リビジョン修飾文字列.
    deb/rpm共通して文字列として使用できるのは、アルファベット小文字, 数字, '~'(チルダ) '+'(プラス).
    リビジョン番号の前後関係にも影響する.
  • ARCH_NAME --- アーキテクチャ (amd64, x86, etc..)

debパッケージのバージョン比較テスト

バージョン比較テストは dpkg --compare-versions を使用する.
演算子は 'lt'(<) 'gt'(>), 'le'(<=) 'ge'(>=)', 'eq'(==), 'ne'(!=)

# リリース番号ありを後発と判定
$ if $(dpkg --compare-versions "1.3.0" "lt" "1.3.0-1"); then echo true; else echo false; fi
true
# '~'はpreバージョンとして認識される. 
# '+'は数字順
$ if $(dpkg --compare-versions "1.3.0-2~1" "lt" "1.3.0-2"); then echo true; else echo false; fi
true
$ if $(dpkg --compare-versions "1.3.0-3+1" "lt" "1.3.0-3"); then echo true; else echo false; fi
false
# ひとつ前のリリースより後発リリースのpreバージョンのほうが新しい
$ if $(dpkg --compare-versions "1.3.0-3~rc1" "lt" "1.3.0-2"); then echo true; else echo false; fi
false

rpmパッケージの命名規則

${PACKAGE_NAME}-${VER_MAJ}.${VER_MIN}.${VER_BUILD}(-${VER_RELEASE}(${VER_RELEASE_SUFFIX})).${DIST_NAME}.${ARCH_NAME}.rpm

こちらは'-'(ハイフン)を使って要素を接続.

  • PACKAGE_NAME --- パッケージ名
  • VER_MAJ --- メジャーバージョン
  • VER_MIN --- マイナーバージョン
  • VER_BUILD --- ビルドバージョン
  • VER_RELEASE --- リリース番号
  • VER_RELEASE_SUFFIX
    リビジョン修飾文字列.
    deb/rpm共通して文字列として使用できるのは、アルファベット小文字, 数字, '~'(チルダ) '+'(プラス).
    リビジョン番号の前後関係にも影響する.
  • DIST_NAME
    パッケージの対象ディストリビューション.
    (ex. 'el7' -> RHEL7, 'el7_9' -> RHEL7〜9, 'centos' -> centos, etc..)
  • ARCH_NAME --- アーキテクチャ
    (x86, x86_64, noarch, etc..)

rpmパッケージのバージョン比較テスト

rpmdev-vercmp コマンドを使う.
rpmdevtools パッケージをインストールすると使える.

# インストール
$ sudo yum install -y rpmdevtools

バージョン比較テスト

$ rpmdev-vercmp mypkg-2.2.1-3 mypkg-2.2.1-4
mypkg-2.2.1-3 < mypkg-2.2.1-4
$ rpmdev-vercmp mypkg-1.2.0 mypkg-1.2.0-1
mypkg-1.2.0 < mypkg-1.2.0-1
$ rpmdev-vercmp mypkg-3.2.1-3 mypkg-3.2.0-4
mypkg-3.2.1-3 > mypkg-3.2.0-4
# (注意) yumは'~'をpreバージョンとしては扱わないようです.
$ rpmdev-vercmp mypkg-2.2.0-1~rc1 mypkg-2.2.0-1
mypkg-2.2.0-1~rc1 < mypkg-2.2.0-1

参考

Ubuntu18.04にpyenvでpython3.7.3をインストール時にBUILD FAILED

対処メモです.

pyenv install 3.7.3 でBUILD ERROR

環境は以下の通り.

  • Ubuntu 18.04.2 LTS (Bionic Beaver)
  • pyenv 1.2.11

pyenvはpythonのソースパッケージをDownloadしてきて、ビルドしてインストールしているんですね.
こんな感じでエラーが出ました.
install BUILD ERROR

エラーメッセージを見ると、いずれもpythonが参照しているネイティブのライブラリが見つからないことが原因のようです.

ならば、依存関係を満たしてやればうまく行きそうです.

行った対処

メッセージ中に見られたライブラリ名を手がかりに、以下のライブラリの開発パッケージをインストールしました.
(※ 当然ながら下記のリストは完全でないかもしれませんが... )

  • zlib (zlib1g-dev)
  • bzip2 (libbz2-dev)
  • openssl (libssl-dev)
  • libffi (libffi-dev)
  • readline (libreadline-dev)
  • sqlite3 (libsqlite3-dev)
$ sudo apt install -y libffi-dev libssl-dev libbz2-dev zlib1g-dev libreadline-dev libsqlite3-dev

パッケージインストール後に再度pyenvで3.7.3をインストールしたところ、無事インストール成功しました.


参考サイト

pyenvで3.7系のインストールに失敗したときのメモ - Qiita
pyenv でBUILD FAILED した時の対処法 - Qiita
3.7.3 fails to install · Issue #1319 · pyenv/pyenv · GitHub

(続)Unmanagedの C++ から Managedのクラスを使う

の続き.

ManagedクラスとUnmanagedクラスの生存期間差の問題

Unmanaged C++からManagedクラスを使用する場合、ただManagedクラスでUnmanaged C++クラスをWrapしてやるだけではダメなケースがあります.
Unmanaged クラスのポインタをVisitorやObserverパターンで使うケースです.

// 
// SomeListenerのManagedWrapper
//
ref class ListenerWrap {
public:
    // コンストラクタ
    ListenerWrap(SomeListener* cb)
    : cb_(cb)
    {

    }
    // デストラクタ
    ~ListenerWrap()
    {
        // ファイナライザへ委譲
        // C++CLIの場合, GC::SuppressFinalize(this)は無条件で行われるので呼び出しは不要.
        this->!ListenerWrap();
    }
    // ファイナライザ
    !ListenerWrap()
    {
        // ポインタを使った終了処理など.
        if (cb_) {
            cb_->close();  // Unmanaged オブジェクトの cb_ の後始末(★ ここが問題)
            cb_ = nullptr;   // ListenerWrapとSomeListenerポインタの関連付けを解除.
        }
    }

    virtual void Exec(int param);
protected:
    SomeListener* cb_;
};
// 問題になるコード・・・
void SomeClass::DoWithCallBack(int param, SomeListener* cb)
{
    // 未定義動作の危険!!
    // cbWrapはスコープアウトによってgc対象になるだけ. ファイナライザ実行まで行われるとは限らない.
    // GCによってcbWrapがFinalizeされるより前に cb が無効ポインタになってしまう可能性がある.

    ListenerWrap^ cbWrap = gcnew ListenerWrap(cb);
    someManagedObject_->Exec(param, cbWrap);
}

Unmanagedクラスのローカル変数は スコープアウトで削除 されますが、Managedクラスのハンドルは スコープアウトでGC対象になるだけ です.

分かりやすい例だと下記のようなコードです.

void DoSomethingClearSample()
{
    // localCbのほうが先に無効になる.
    // cbWrapのファイナライザはlocalCbが削除された後.
    SomeListener localCb;
    ListenerWrap^ cbWrap = gcnew ListenerWrap(&localCb);
             :
             :
}

この生存期間の僅かなギャップにより、Unmanagedクラスのポインタを保持するManagedクラスがFinalizerでUnmanagedクラスのポインタを使って何らかの終了処理を行うような場合に無効ポインタへのアクセスを引き起こすことになります.

C#のusingステートメントC++標準ライブラリのstd::unique_ptrのように、スコープアウトしたときに確実にDisposeしたりdeleteしたりする仕組みが欲しいなぁ、と思っていたところ、ちゃんと用意されてました.

こういうむず痒いところもさりげなーく手当てしてるのがすごい.
さすがMS.

msclr::auto_gcroot<T>テンプレートクラス

前の記事 の gcroot<T> は、UnmanagedクラスがManagedクラスのハンドルを保持するための補助的Unmanagedテンプレートクラスでした.

こちらはその機能を発展したものになります.
Unmanagedクラスのメンバに保持できるのは同じですが、こちらは デストラクタで保持しているManagedクラスのハンドルをdeleteしてくれます.

マネージドクラスのハンドルを delete するということは、IDisposable の Dispose メソッド呼び出しと同じですから、これを使うと Unmanagedのオブジェクトがdeleteされるタイミングで保持しているManagedのハンドルもDisposeさせる仕組みができます.

メンバメソッドも標準ライブラリのスマートポインタ std::unique_ptr<T> とほぼ同じで、C++の標準ライブラリと同じ感覚で使えるように設計されてました.
これは嬉しい.

// auto_gcrootヘッダをincludeします.
#include <msclr/auto_gcroot.h> 
             
SomeWrap^ someWrap = gcnew SomeWrap();
if (someWrap)
{
    // someWrapはスコープアウトでdispose.
    msclr::auto_gcroot<SomeWrap^> wrapped = someWrap;
    SomeWrap^ rawHandle = wrapped.get(); // rawハンドルの取得
               :
    wrapped->SomeFunc(); // -> でメソッド呼び出し
               :
    someWrap = nullptr;
    // someWrap.reset(other); // resetも同じ
    // someWrap.release(); // releaseもあります!
}

Managedクラスハンドルの生存期間をスコープで管理できるようになれば、生存期間のズレによる問題は容易に解決できますね.

void SomeClass::DoWithCallBack(int param, SomeListener* cb)
{
    // auto_gcrootで、スコープアウトのタイミングで確実にdelete(=Dispose)する.
    // このスコープ内ならcbが有効であることは確実なので、安全にcbの終了処理とnullptr化が行える.
    msclr::auto_gcroot<ListenerWrap^> cbWrap = gcnew ListenerWrap(cb);
    someManagedObject_->Exec(param, cbWrap);
}

これでcbポインタが無効になってからfinalizerが呼ばれることはなくなりました.

スコープ利用に特化した msclr::auto_handle<T> テンプレート

auto_gcroot<T> と似たようなクラスで auto_handle<T> ってのもあります.

これは、auto_gcroot<T>のManagedクラス版.

auto_handle<T> はManagedな参照(ref)型テンプレートクラスなので、Unmanagedクラスのメンバとして保持できないという違いがあるものの、機能としてはauto_gcroot<T> とほぼ同等です.

#include <msclr/auto_gcroot.h> 
#include <msclr/auto_handle.h> // auto_handle.hが別にあります.
             
{
    msclr::auto_gcroot<SomeWrap^>  wrapped1 = gcnew SomeWrap();
    // auto_handleにはハンドルのoperator=がないので、ハンドルのsetはコンストラクタかresetを利用する.
    msclr::auto_handle<SomeWrap> wrapped2( gcnew SomeWrap() ); // 割安
               :
               :
}

auto_gcrootがUnmanagedクラスのメンバとして保持することを想定したものであるのに対して、こちらはスコープ利用を想定したものといえそうです.
ちょうど、C++CLIC#のusingステートメント相当の処理を実現するためのもの 、といった位置づけでしょうか.

スコープ利用に限っては auto_gcroot<T> も auto_handle<T> と同じ作用をするのですが、内部処理としてGCHandle型のAllocとFreeが行われない分だけ auto_handle<T> のほうが処理コスト的に割安な感じです.

ということで、先の例は auto_handle のほうが最適な選択と言えますね.
書き換えると以下のような感じになりました.

void SomeClass::DoWithCallBack(int param, SomeListener* cb)
{
    // auto_handleで、スコープアウトのタイミングで確実にdelete(=Dispose)する.
    msclr::auto_handle<ListenerWrap> cbWrap;
    cbWrap.reset( gcnew ListenerWrap(cb) );
    someManagedObject_->Exec(param, cbWrap);
}

auto_gcroot も auto_handle も地味に便利なんですが、あまり使用例は見かけないですね・・.
ヘッダのコードを見るとそんな難しいものでもなさそうなので、大体の人は同等のクラスを自作しちゃってるのかもしれません.


参考情報

C++ Support Library | Microsoft Docs
auto_gcroot Class | Microsoft Docs
auto_handle | Microsoft Docs
unmanagedクラスにmanagedなメンバを持たせる - schima.hatenablog.com

Unmanagedの C++ から Managedのクラスを使う

Pure C++コードから、.NET Frameworkベースのライブラリを使いたい・・・

純粋なC++コード(Unmanagedコード)から、C#など.NET Frameworkベースで開発されたライブラリ(Managedクラス)を利用しなければならなくなった・・
それほど頻繁に遭遇するケースではありませんが、こんな課題に出くわすことがあります.

基本的な方法としては、共通言語ランタイムをつかって(/clrオプション)C++CLIにすれば丸く解決・・・のはずですが、これが許されないケースがあります.

例えば、Native部分のコードに標準ライブラリのmutexとかatomicとか使ってる場合.
これは共通言語ランタイムと互換性がないので、C++CLIにしてコードを共存させることができません.
(共存させようとするとコンパイラに叱られますね.)

こうなると、C++CLIにできる部分と出来ない部分はモジュールを分割し、ManagedクラスのインスタンスをラップしたクラスをUnmanaged C++から使ってやるしかないわけですね.

さて、ここで一つ問題が起こります。

error C3265: マネージ 'hoge' をアンマネージ 'fuga' で宣言できません.

おっと、UnmanagedのクラスはManagedクラスのハンドルを持てないのでした・・・

UnmanagedクラスにManagedクラスのハンドル型を持たせられる?

うーんと悩みながらドキュメントを漁ると・・msclr名前空間にとても便利なクラスがありました.

msclr::gcroot<T> を使え!

今回キモとなるのは gcroot テンプレート.
msclr/gcroot.h をインクルードして使用します.

このクラスはC++のテンプレートになっていて、Managedの参照型(ref class)をUnmanagedクラスに保持させることができるように設計されています.
Unmanagedクラスのメソッドでは、gcrootでラッピングしたハンドルからメソッドを呼び出します.
親切なことに operator ->()とoperator T()が準備されてますので、普通のハンドルとそれほど変わらない使い方が可能です.

コレは便利!!

じゃあ、value class の場合はどうするの・・?

当然の疑問ですよね.

サンプルでは全然見かけないのですが、実は value class も同じように保持できます.

これは、Managedの値型に AutoBoxingが働いて value class は自動的にref classのハンドルでWrapする処理が入るため.
下記のコードではvalue classのSystem::DateTimeを保持しています.

// System::DateTime は value class .
// ハンドル型へはAutoBoxingされる
DateTime^ dth = DateTime.Now;
// DateTime^にAutoBoxingされるので, gcrootへも直に格納できる. 
msclr::gcroot<DateTime^> dtwrap = DateTime.Now;

// ハンドル型はそのまま取得( operetor T() )
DateTime^ dth = dtwrap;
// unboxは明示キャストが必要
DateTime dtv   = static_cast<DateTime>(dth);
DateTime dtv2 = static_cast<DateTime>(dtwrap);
 

unboxは上記のように明示的にキャストすればOKです.
ただし、Unbox処理はそこそこ負荷がかかりますので注意.
なるべくBoxされたハンドルのままで取り扱うのが良さそうです.

さて、これで準備は整いました.

さあ、gcrootを使ってWrapperを作ろう.

Wrapperの実装

見せたくないものを隠すのは モザイク Wrapperが定石ですね.

Pure C++からはManagedクラスが見えてはダメなので基本的に抽象化を使います.
class の内部メンバをNativeC++モジュールから完全に隠すため、ここでは pure virtual クラスを使います.
pure virtualな型のインスタンスをインタフェースオブジェクトとしてモジュールの外のコードに提供します.

ヒープ境界の問題があるので、モジュール外部(Native C++モジュール)のランタイム側のdelete/freeがこのインタフェースに対して使われないように少し細工が必要です.
ここでは デストラクタをprotectedにしてpure virtualなインスタンスをdelete出来ないようにし、deleteの役割は仮想関数の実装側のDestroyで行います.
こうするとDestroyメソッドの実装本体がC++CLI側モジュール内にあるので、必然的にCLIモジュール側のランタイムによるdeleteを強制することができます.

//
// interface.h
// 公開用ヘッダファイル
//

// モジュールの外から使うためのインタフェースクラス
class CliDateTimeIF {
protected:
  virtual ~CliDateTimeIF() {} // 直delete禁止
public:
  virtual void Destroy() = 0; // 代理デストラクタ. deleteを行う.
  virtual int GetYear() = 0;
  virtual int GetMonth() = 0;
  virtual int GetDay() = 0;
};

そして、インタフェースの実装クラスです.

#include <msclr/gcroot.h>

using namespace System;

// ------------------------------------------------------
// ManagedクラスをUnmanaged C++でラップする
class CliDateTimeImpl : public CliDateTimeIF {
protected:
  virtual ~CliDateTimeImpl()
  {
       // 後始末
  }

public:
  CliDateTimeImpl() noexcept { }

  virtual void Destroy() override {
      delete this; // self-delete
  }

  virtual int GetYear() {
      return dt_->Year;    
  }
  virtual int GetMonth() {
      return dt_->Month;   
  }
  virtual int GetDay() {
      return dt_->Day; 
  }

  void SetDateTime(DateTime^ dt) {
      dt_ = dt;
  }

  DateTime^ GetDateTime() {
      return dt_; // gcroot<T>::operator T()
  }

protected:
  msclr::gcroot<DateTime^> dt_; // ハンドル型
};

モジュールの外から使えるようにする

最後にモジュール外に公開するファクトリ関数です.
公開ヘッダファイルにファクトリ関数の定義を追加します.

//
// interface.h
// 公開用ヘッダファイル
//

// モジュールの外から使うためのインタフェースクラス
class CliDateTimeIF {
protected:
  virtual ~CliDateTimeIF() {} // 直delete禁止
public:
  virtual void Destroy() = 0; // 代理デストラクタ. deleteを行う.
  virtual int GetYear() = 0;
  virtual int GetMonth() = 0;
  virtual int GetDay() = 0;
};

extern "C" {

// -------------------------------------------------------------------
// 公開ファクトリ関数
__declspec(dllexport) 
CliDateTimeIF* __cdecl CreateCLIDateTime();

};

Load Time Link(libファイルを使ったリンク)でもDynamic Link(LoadLibraryを使うリンク)でも使いやすいように __cdecl 規約の C関数をExportします.
この関数の戻値に pure virtual のポインタを返します.

extern "C" {
// ---------------------------------------
// 公開ファクトリ実装
CliDateTimeIF* __cdecl CreateCLIDateTime()
{
    CliDateTimeIF* ptr = nullptr;
    try {
        ptr = new CliDateTimeImpl();
        ptr->SetDateTime(DateTime.Now);
    }
    catch (std::bad_alloc&) {
         // alloc error!!
    }
    return ptr;
}

};

呼び出し側のコードでは、ファクトリ関数を使ってインスタンスを生成して使うだけです.

CliDateTimeIF* pdt = CreateCLIDateTime();
       :
int year = pdt->GetYear();
       :
pdt->Destroy(); // delete
pdt = nullptr;

参考記事

msbuildでビルドするときに自動でNugetパッケージを復元させる

Msbuildは自動でNugetパッケージを復元してくれない

最近のWindowsではVisualStudioなしでプロジェクトファイルがビルドできるそうですね.
Windows(正確にはWindowsでデフォルトインストールされる .NET Framework)にはMsbuildっていうツールが標準で付属していて、別途必要なビルドツールセットを追加インストールすると、VisualStudioがなくてもslnファイルやxxprojファイルからプロジェクト一式をビルドできる環境が整います.

Visual Studio 2017向け
Build Tools for Visual Studio 2017 ってのをインストールします.

Visual Studio 2015向け
C++ .NET別々に分かれてます.
- Microsoft Build Tools 2015 - .NET向け
- Visual C++ Build Tools 2015 - C++向け. 直リンク
- Visual C++ Optimizer fixes for Visual Studio 2015 Update 3 - C++ コンパイラのバグパッチ

これでVisualStudioなしでも全然困らない環境ができるんだ、スゲェ・・と思いきや、Msbuildはプロジェクトのビルドに必要なNugetパッケージを自動では復元してくれませんでした.
仕方なく nuget をダウンロードしてきて・・・

nuget.exe restore -SolutionDirectory .\hoge\fuga...

なんてやって、最初にパッケージを手動で復元しないといけなかったりします.
これは手作業としてもちょっと不便ですよね.

プロジェクト一式を配布したときに手軽にビルドしてもらえるように何とかできないか・・と、思い立ったワケです.

プロジェクトファイルに細工してNuget実行されるようにしよう

VisualStudioやmsbuildによるビルドは、projectファイルで定義されたターゲットという処理を順番に実行するということにより成り立っています.
それなら、パッケージをレストアするためのカスタムターゲットを定義して、定義したカスタムターゲットがデフォルトのビルドシーケンスの前に実行されるようにprojectファイルの定義をカスタマイズすればよさそうです.

1. ソリューション一式のディレクトリ配置

まず、ソリューションファイルのあるディレクトリに、nugetパッケージ復元処理のターゲットを定義したファイルを配置するディレクトリを作ります.
ただ、nugetを手作業でダウンロードして配置してもらうのはさすがに 面倒くさい エレガントではないので、 nugetがなければ自動で最新版をDownloadしてきて使う仕組みとします.

ソリューションのファイル配置は以下のようにします.

ソリューションのファイル配置

+ -- Hoge.sln
+ -- project
|        + -- Fuga.csproj
|        + -- packages.config
|                    :    
+ -- nuget
         + -- nuget_restore.props

2. カスタムターゲットを定義したプロパティシートを作成.

ディレクトリの中にユーザ定義プロパティシート nuget_restore.props を作成し、独自定義ターゲット DowlloadNugetNugetRestorePackage を定義します.
同時に、ビルドシーケンスで実行されるように既定ターゲットリスト一覧である BuildDependsOn をオーバーライドして、 DowlloadNugetNugetRestorePackage ターゲットを挿入します.

出来上がった nuget_restore.props

<?xml version="1.0" encoding="utf-8"?>
<!-- nuget_restore.props -->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <PropertyGroup>
        <!-- ビルドシーケンス実行前にカスタムターゲットを実行するようオーバーライド -->
        <BuildDependsOn>
            DownloadNuGet;
            NugetRestorePackages;
            $(BuildDependsOn)
        </BuildDependsOn>
        <!-- nugetの配置パス. nugetが見つからなければここにダウンロードして配置する -->
        <NugetPath>$(SolutionDir)nuget\nuget.exe</NugetPath>
        <!-- 最新版 nugetが取得できる直リンクURL -->
        <LatestNugetURL>https://dist.nuget.org/win-x86-commandline/latest/nuget.exe</LatestNugetURL>
    </PropertyGroup>

    <!-- DownloadFileByHttp Taskの定義.  MsBuild 15.8の DownloadFile タスクのWorkaround -->
    <!-- DownloadFile タスクは MSBuild 15.8(VS2017)以降でしか使えないため、自前で定義する.-->
    <!-- これでVS2015でも動作させることができる. -->
    <UsingTask TaskName="DownloadFileByHttp"
               TaskFactory="CodeTaskFactory"
               AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
        <ParameterGroup>
            <DownloadUrl ParameterType="System.String" Required="true"/>
            <SavePath ParameterType="System.String" Required="true" />
        </ParameterGroup>
        <Task>
            <Reference Include="System" />
            <!-- 最新版の nuget.exe をダウンロードするcsコード -->
            <!-- 昨今ではSSL3ではerrorになるのでプロトコルをTLSに限定する -->
            <Code Type="Fragment" Language="cs">
                <![CDATA[
                      System.Net.ServicePointManager.SecurityProtocol
                      = System.Net.SecurityProtocolType.Tls
                      | System.Net.SecurityProtocolType.Tls11
                      | System.Net.SecurityProtocolType.Tls12;
                      (new System.Net.WebClient()).DownloadFile(DownloadUrl, SavePath);
                ]]>
            </Code>
        </Task>
    </UsingTask>

    <!-- 最新版DLを試みるターゲット DownloadNuGet の定義 -->
    <!-- nugetが NugetPath にない場合のみ実行される -->
    <Target Name="DownloadNuGet"
            Condition="!Exists($(NugetPath))">
        <Message Text="Downloading Nuget : '$(LatestNugetURL)' -> '$(NugetPath)'"/>
        <DownloadFileByHttp DownloadUrl="$(LatestNugetURL)"
                            SavePath="$(NugetPath)" />
    </Target>

    <!-- nugetによるパッケージのレストア NugetRestorePackages ターゲットの定義 -->
    <!-- &quot;はパスに空白が含まれる場合の対策. -->
    <Target Name="NugetRestorePackages">
        <!-- nuget.exe restore -SolutionDirectory (ソリューションディレクトリ)  -->
        <Exec Command="&quot;$(NugetPath)&quot; restore -SolutionDirectory &quot;$(SolutionDir).&quot;"/>
    </Target>
</Project>

3. propsをプロジェクトファイルへImportする

あとは、自動でパッケージ復元を行わせたいプロジェクトファイルの末尾にこっそりとpropsファイルをImportする以下の2行を追記しておきます.

プロジェクトファイルへの挿入

<Project>
          :
          :
  <!-- ターゲット定義をImportしてプロジェクトへ組み込む. -->
  <Import Project="$(SolutionDir)nuget\nuget_restore.props" />
</Project>

これでmsbuildを使ってプロジェクトのビルドを実行すると、nugetのダウンロード/デプロイとパッケージ復元を試みるターゲットがプロジェクトのビルドに先立って実行されるようになります.

VisualStudioでこのカスタマイズしたプロジェクトをビルドするとNugetの実行が重複しますが、Nugetは復元済みパッケージは無視するので、運用する上で目立った副作用は無さそうです.

cmakeのExternalProjectコマンドなどmsbuildを使ったビルド時はもちろん、ライセンスなどの関係でProfessional以上のVisualStudioを準備できない人向けにプロジェクトを配布したりするときなどに便利です.

(追記) プロキシ環境下でのNugetは?

こればかりは以下のかずき氏のブログにあるような手動での設定をお願いするしかないです.
NuGet v3.0で認証プロキシを突破する方法 - かずきのBlog@hatena

なんかいい方法ないかなぁ。。。

(追記) 通信プロトコルを制限する

昨今ではSSL3を使用する通信がエラーになることがあります.
プロトコルTLSに限定するようにする設定を追加しました.


参考記事

MacOS MojaveでCmakeがOpenMPを見つけてくれない件

CmakeをupdateしたらOpenMPが見つからないぞ!?

MacOpenMPを使ったクロスプラットフォームC++コードを書くことがありまして、 HomebrewでlibompをインストールしてCMakeLists.txtをコツコツとかきあげて、 なんとかビルド実行できる環境になったわけです.

ところが、Cmakeが古いのが気になってHomebrewで 3.12にupgradeしたところ、 OpenMPを見つけてくれなくなりました。。

テストでfind_packageしてみるけれども見つからない・・・

HomebrewのFomuraでlibompをインストールした後に表示されるご指示の通り、必要なマクロ変数とオプションを定義してfind_packageしてやってるわけです。

# libompのインストール先パスをbrewコマンドを実行して取得する.
execute_process(COMMAND brew --prefix libomp
                OUTPUT_VARIABLE OpenMP_HOME
                OUTPUT_STRIP_TRAILING_WHITESPACE)  
message(STATUS "OpenMP Root : ${OpenMP_HOME}")

# OpenMPのfindに必要な変数設定
set(OpenMP_C_LIB_NAMES "omp")
set(OpenMP_CXX_LIB_NAMES "omp")
set(OpenMP_omp_LIBRARY "${OpenMP_HOME}/lib/")
set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp -Wno-unused-command-line-argument -I${OpenMP_HOME}/include -lomp -L${OpenMP_omp_LIBRARY}") 
set(OpenMP_C_FLAGS "-fopenmp -Wno-unused-command-line-argument -I${OpenMP_HOME}/include -lomp -L${OpenMP_omp_LIBRARY}")

# libompをlookup
find_package(OpenMP)

message(STATUS "OpenMP_C_Found   : ${OpenMP_C_FOUND}")
message(STATUS "OpenMP_CXX_Found : ${OpenMP_CXX_FOUND}")

if (OpenMP_FOUND)
message(STATUS "YES!! OpenMP found.")
else (OpenMP_FOUND)
message(STATUS "No!!   OpenMP did not find.")
endif (OpenMP_FOUND)

ところが!

ちゃんとsetを行ってるのに、なぜか OpenMP_CXX_FLAGSOpenMP_C_FLAGS が設定されてないんですね。。
初回実行すると「OpenMP did not find.」なんですが 、なぜか2回目以降は値が設定されてて「OpenMP found.」。。。

何故に初回だけがダメなのか・・・!?

それ変数やない、キャッシュ変数や・・

なんでやねん!と首を傾げながら何気なくCMakeCache.txtを流し読みしてて気づきました。。
なんてこった・・CMakeCache.txt内に OpenMP_CXX_FLAGSOpenMP_C_FLAGS があるじゃあないですか・・・orz

OpenMP_CXX_FLAGSOpenMP_C_FLAGS はキャッシュ変数になっていたんですね。
キャッシュ変数はフツーにsetするのではダメで、書き込むときには FORCE オプションをつけて 強制的に書き込んでやる必要があった、というオチ。

以下の通り変更するとちゃんと見つかるようになりました。

# libompのインストール先パスをbrewコマンドを実行して取得する.
execute_process(COMMAND brew --prefix libomp
                OUTPUT_VARIABLE OpenMP_HOME
                OUTPUT_STRIP_TRAILING_WHITESPACE)  
message(STATUS "OpenMP Root : ${OpenMP_HOME}")
# OpenMPのインストールチェック
set(OpenMP_C_LIB_NAMES "omp")
set(OpenMP_CXX_LIB_NAMES "omp")
set(OpenMP_omp_LIBRARY "${OpenMP_HOME}/lib/")
# キャッシュ変数なのでFORCEオプションをつける
set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp -Wno-unused-command-line-argument -I${OpenMP_HOME}/include -lomp -L${OpenMP_omp_LIBRARY}" CACHE STRING "" FORCE) 
# 同じくキャッシュ変数なのでFORCEオプションをつける
set(OpenMP_C_FLAGS "-fopenmp -Wno-unused-command-line-argument -I${OpenMP_HOME}/include -lomp -L${OpenMP_omp_LIBRARY}" CACHE STRING "" FORCE)

# libompがインストールされているかどうか.
find_package(OpenMP)
# 見つかったか?
message(STATUS "OpenMP_C_Found   : ${OpenMP_C_FOUND}")
message(STATUS "OpenMP_CXX_Found : ${OpenMP_CXX_FOUND}")

if (OpenMP_FOUND)
message(STATUS "YES!! OpenMP found.")
else (OpenMP_FOUND)
message(STATUS "No!!   OpenMP did not find.")
endif (OpenMP_FOUND)

これ、説明がないとちょっとわかんないですよねぇ。。。


参考記事

C++でMessagePackを使ってみる(6)〜 Polymorphism(多態性)とシリアライズ 〜

多態性キープしてシリアライズしたいけど・・

MessagePackを使うとかなり簡単にC++でデータのシリアライズ/シリアライズが実現できるのですが、いろいろ使い込んでいくと一つ困った状況に気づきます.
ポインタで表現されるオブジェクト( 生ポインタではなく、std::shared_ptr )をシリアライズ/デシリアライズする時、ポインタの型でシリアライズ/デシリアライズしようとしてしまい、ポインタが指しているオブジェクトの実体の型でシリアライズ/デシリアライズが出来ないという問題です.

class Monster {
    int life_point_;
    int magic_point_;
};

class Goblin : public Monster {
    int race_class_;
    int drop_item_ids_; 
};

というクラスがあったとして、このような場合です.

class CaughtMonster {
    CaughtMonster(std::shared_ptr<Monster> dep)
    : base_(dep) { }

    // Goblinでシリアライズ/デシリアライズされてほしいけれども....
    MSGPACK_DEFINE(base_);
private:
    std::shared_ptr<Monster> base_;
};

std::shared_ptr<Goblin> pder = std::make_shared<Goblin>();
CaughtMonster obj(pder);


// base_は、GoblinではなくMonsterとしてシリアライズされる.
// この結果、race_class_, drop_item_ids_の情報がシリアライズ時に失われてしまう.
std::stringstream strm;
msgpack::pack(strm, obj);

ポリモーフィックなポインタは実体でシリアライズ/デシリアライズしてほしいよなぁ・・ってことで、ちょっと工夫をしてみました.

ポリモーフィックシリアライズ対応

MessagePackにはライブラリ側で定義されたAdapterがいろいろあって、何もしなくてもshared_ptrシリアライズ可能になっています.
そこで、「ある特定クラスの派生クラスだけに限定して使われる shared_ptr アダプタ」を定義して、派生クラスの情報を含めたシリアライズ/デシリアライズが行えるようにする実装をしてみます.

Step.1 ポリモーフィックシリアライズ対応を示すクラスを定義する

まず、ポリモーフィック型でのシリアライズ/デシリアライズに必要なメソッドを定義する抽象クラスを定義します.
このクラスの派生クラスであることをポリモーフィックシリアライズの条件にします.

//
// poly_pack_base.h
//
#include <msgpack.hpp>

// 多態シリアライズ対象クラスの基底クラス
class poly_pack_base {
protected:
    poly_pack_base() noexcept = default;
public:
    virtual ~poly_pack_base() = default;

    // 実体クラスを示すIDを取得する仮想関数
    virtual int get_type_id() const noexcept = 0;
    // 読み出し仮想関数
    virtual void read_object_v(msgpack::object const& o) = 0;
    // with_zoneオブジェクトへ書き込む仮想関数
    virtual void write_object_v(msgpack::object::with_zone& oz) const = 0;
};

Step.2 ポリモーフィックシリアライズ対応の基底クラスを準備する

次に、多態シリアライズ対象となる基底クラスの実装です.

poly_pack_baseとして要求される純粋仮想関数をオーバーライドして、このクラスとこのクラスから派生するクラスのオブジェクトを復元するためのファクトリクラス型を公開します.
このファクトリクラスはこのクラスとこのクラスの派生クラスから共通して参照され、各々の型が持つTYPE_IDで復元すべき型を指定してインスタンスを生成します.
ファクトリ関数がオブジェクトを生成するときは、特別な情報を必要とせずにオブジェクトを生成できるようにしておく必要があるので、忘れずに引数のないデフォルトコンストラクタを定義しておきます.

//
// Monster.h
//

// オブジェクトを復元するファクトリクラスのヘッダ
#include "restore_factory.h"
#include "poly_pack_base.h"
#include <msgpack.hpp>

// Monsterクラス
class Monster : public poly_pack_base {
public:
    // デフォルトコンストラクタ. 
    // ファクトリがインスタンスを作るために必要.
    Monster() noexcept;
         :
    // カスタムアダプタから参照するためのファクトリクラスのtype
    using factory_race_class_t = restore_factory;

    // クラス型を示すID
    constexpr int TYPE_ID() const noexcept
    {
        return static_cast<int>(PackClassIDs::Mid_Monster);
    }
    // ------------------------------------------------
    // 実体クラスを示すIDを取得する仮想関数
    virtual int get_type_id() const noexcept override
    {
        return TYPEID();
    }

    // ------------------------------------------------
    // msgpack::objectからオブジェクトを読み出す.
    virtual void read_object_v(msgpack::object const& o) override
    {
        using self_t = std::remove_reference_t<decltype(*this)>;
        msgpack::operator>>(o, static_cast<self_t&>(*this));
    }
    // -----------------------------------------------
    // with_zoneオブジェクトにこのクラスのシリアライズデータを書き込む.
    virtual void write_object_v(msgpack::object::with_zone& oz) const override
    {
        using self_t = std::remove_reference_t<decltype(*this)>;
        msgpack::operator<<(oz, static_cast<self_t&>(*this));
    }

    // 通常のシリアライズ定義.
    MSGPACK_DEFINE(life_point_, magic_point_);
private:
    int life_point_;
    int magic_point_;
};

Step.3 ポリモーフィックシリアライズ対応の派生クラスを準備する

基底クラスから派生するクラスです.
実装はほぼ同様ですが、ファクトリクラスの公開は不要です.

//
// Goblin.h
//
#include "Monster.h"
#include <msgpack.hpp>

// Goblinクラス
class Goblin : public Monster {
public:
    // デフォルトコンストラクタ.  復元時に必要になる.
    Goblin() noexcept;
           :
           :
    // ### 実体クラスを示すID定数
    constexpr int TYPE_ID() const noexcept
    {
        return static_cast<int>(PackClassIDs::Mid_Goblin);
    }
    // ------------------------------------------------
    // 実体クラスを示すIDを取得する仮想関数
    virtual int get_type_id() const noexcept override
    {
        return TYPE_ID();
    }

    // ------------------------------------------------
    // msgpack::objectからオブジェクトを読み出す.
    virtual void read_object_v(msgpack::object const& o) override
    {
        using self_t = std::remove_reference_t<decltype(*this)>;
        msgpack::operator>>(o, static_cast<self_t&>(*this));
    }
    // -----------------------------------------------
    // with_zoneオブジェクトにこのクラスのシリアライズデータを書き込む.
    virtual void write_object_v(msgpack::object::with_zone& oz) const override
    {
        using self_t = std::remove_reference_t<decltype(*this)>;
        msgpack::operator<<(oz, static_cast<self_t&>(*this));
    }

    MSGPACK_DEFINE(MSGPACK_BASE(Monster), race_class_, drop_item_ids_);
private:
    int race_class_;
    int drop_item_ids_;
};

Step.4 IDに対応するファクトリクラスを実装する

ファクトリクラスは、クラスのもとの型を示すTYPE_IDからもとのオブジェクトを生成するクラスです.
idに応じたオブジェクトを生成して返します.
headerファイルとsourceファイルは以下のような感じです.

//
// restore_factory.h
//

// Fwd Decl
class Monster;

// --------------------------------------------------------------
// Factoryが対象とするクラスのTYPE_ID定義用enum
enum class PackClassIDs : int {
    Mid_Monster, // Monsterクラス
    Mid_Goblin,  // Goblinクラス
};

// --------------------------------------------------------------
// ファクトリクラス
class restore_factory {
public:
    restore_factory() = delte;
    ~RestorationFactory() = delete;
    // pack_id から 元の型のインスタンスを生成
    static std::shared_ptr<Monster> CreateObject(int pack_id);
};
//
// restore_factory.cpp
//
#include "restore_factory.h"
#include "Monster.h"
#include "Goblin.h"

// --------------------------------------------------------------
// ファクトリ関数
std::shared_ptr<Monster> restore_factory::CreateObject(int pack_id)
{
    // 基底クラスのshared_ptr型で返す
    std::shared_ptr<Monster> ptr;
    switch (pack_id) {
    case Monster::TYPE_ID():
        ptr = std::make_shared<Monster>();
        break;
    case Goblin::TYPE_ID():
        ptr = std::make_shared<Goblin>();
        break;
    default:
        break;
    }
    return ptr;
}

Step.5 ポリモーフィック対応したアダプタを実装する

最後に convert, pack, object_with_zone カスタムアダプタを定義します.
ただし、poly_pack_base の派生クラスであることを条件にしてアダプタが適用されるように、 各アダプタの第2テンプレートパラメータでenable_ifを使って対象を制限します.

//
// poly_serializeable_adapter.h
//
#include "poly_pack_base.h"
#include <msgpack.hpp>
#include <msgpack/versioning.hpp>
#include <msgpack/adaptor/adaptor_base.hpp>
#include <msgpack/adaptor/check_container_size.hpp>

// message packの名前空間
namespace msgpack {
// version 名前空間
MSGPACK_API_VERSION_NAMESPACE(MSGPACK_DEFAULT_API_NS) {
// message packのアダプタ用名前空間
namespace adaptor {
// -------------------------------------------------------------------------
// pack アダプタ
template <typename T>
struct pack<std::shared_ptr<T>,
            std::enable_if_t<std::is_base_of<poly_pack_base, T>::value>>
{
    // pack
    template <typename Stream>
    msgpack::packer<Stream>& operator()(msgpack::packer<Stream>& o,
                                        const std::shared_ptr<T>& v) const
    {
        // 有効なポインタ
        if (v) {
            // 2要素のarrayとしてpackする
            // array[0] <- TYPE_ID
            // array[1] <- 本体のobject
            o.pack_array(2);
            // 実際の型のTYPE_ID取得
            int pack_id = v->get_type_id();
            // TYPE_IDを書き込み
            o.pack(pack_id);
            // zoneとwith_zone生成
            msgpack::zone z;
            msgpack::object::with_zone oz(z);
            // 仮想関数でポインタのデータを中間形式with_zoneへ書き込む
            v->write_object_v(oz);
            // ozを書き込み.
            o.pack(static_cast<msgpack::object&>(oz));
        }
        else {
            // nullptrの場合, nullとしてpack
            o.pack_nil();
        }
        return o;
    }
};

// -------------------------------------------------------------------------
// object_with_zone アダプタ シリアライズ用
template <typename T>
struct object_with_zone<std::shared_ptr<T>,
                        std::enable_if_t<std::is_base_of<poly_pack_base, T>::value>>
{
    // oへvを書き込む
    void operator()(msgpack::object::with_zone& oz,
                    const std::shared_ptr<T>& v) const
    {
        if (v) {
            // zoneの参照を取り出す.
            msgpack::zone& zone = oz.zone;
            // oのタイプをサイズ2のArrayに設定
            oz.type = msgpack::type::ARRAY;
            oz.via.array.size = 2;
            // object2つ分の領域をzone上に確保
            void* pmem = zone.allocate_align(sizeof(msgpack::object) * oz.via.array.size,
                                             MSGPACK_ZONE_ALIGNOF(msgpack::object));
            oz.via.array.ptr = static_cast<msgpack::object*>(pmem);

            // pack_idを取得して書き込む
            int pack_id = v->get_type_id();
            oz.via.array.ptr[0] = msgpack::object(pack_id);

            // ポインタ実体のオブジェクト書き込み
            // 実体オブジェクト用のwith_zone(msgpack::objectの派生クラス)を準備して
            // vの仮想関数でv自身をwith_zoneへ書き込ませる.
            msgpack::object::with_zone oz_mine(zone);
            v->write_object_v(oz_mine);
            oz.via.array.ptr[1] = oz_mine;
        }
        else {
            // v == nullptr
            // oのタイプをnilに設定.
            oz.type = msgpack::type::NIL;
        }
    }
};

// -------------------------------------------------------------------------
// デシリアライズ用の convertアダプタ
template <typename T>
struct convert<std::shared_ptr<T>,
               std::enable_if_t<std::is_base_of<poly_pack_base, T>::value>>
{
    // oから読み出してvへオブジェクトを復元
    msgpack::object const& operator()(msgpack::object const& o,
                                      std::shared_ptr<T>& v) const
    {
        switch (o.type) {
        case msgpack::type::NIL:
            // nullptrなので何もせず.
            break;
        case msgpack::type::ARRAY:
            // サイズが2であることを確認
            if (o.via.array.size == 2) {
                // Tもしくは派生クラスであればfactory_race_class_tが参照できるはず.
                using factory_t = typename T::factory_race_class_t;
                // 元の型のTYPE_ID読み出し
                // factoryを使って元の型のオブジェクトを生成
                // もとのオブジェクトの仮想関数を使ってobjからデータを読み出す
                int pack_id = o.via.array.ptr[0].as<int>();
                v = std::static_pointer_cast<T>(factory_t::CreateObject(pack_id));
                v->read_object_v(o.via.array.ptr[1]);
            }
            else {
                // サイズのミスマッチ
                // ミスマッチの例外を投げる
                throw type_err();
            }
            break;
        default:
            // 型のミスマッチ
            // ミスマッチの例外を投げる
            throw type_err();
        }
        return o;
    }
};

}; // adaptor
}; // v3
}; // msgpack

Step.6 マクロ化しちゃえばすっきり

よーくみると基底クラスと派生クラスはほとんと同じ実装なので、マクロ化するとスッキリしますね.
マクロはこんな感じでしょうか.

//
// poly_pack_base.h
//
#include <msgpack.hpp>

// Polymorphicシリアライズ対象クラスの基底クラス
class poly_pack_base {
protected:
    poly_pack_base() noexcept = default;
public:
    virtual ~poly_pack_base() = default;

    // 実体クラスを示すIDを取得する仮想関数
    virtual int get_type_id() const noexcept = 0;
    // with_zoneオブジェクトからオブジェクトを読み出す.
    virtual void read_object_v(msgpack::object const& o) = 0;
    // with_zoneオブジェクトにこのクラスのシリアライズデータを書き込む.
    virtual void write_object_v(msgpack::object::with_zone& oz) = 0;
};

// レストア用ファクトリ宣言
#define MSGPACK_EXTRA_DECL_FACTORY_CLASS( factory_name ) \
    using factory_race_class_t = factory_name;

// pack_id定義
#define MSGPACK_EXTRA_DECL_TYPE_ID( pack_id ) \
    constexpr int TYPE_ID() const noexcept \
    { return static_cast<int>(pack_id); } \
    virtual int get_type_id() const noexcept override \
    { return TYPE_ID(); }

// 仮想関数実装
#define MSGPACK_EXTRA_POLYPACK_METHOD_IMPL() \
    virtual void read_object_v(msgpack::object const& mpkobj) override { \
        using self_t = std::remove_reference_t<decltype(*this)>; \
        msgpack::operator>>(mpkobj, static_cast<self_t&>(*this)); \
    } \
    virtual void write_object_v(msgpack::object::with_zone& mpkobjz) const override { \
        using self_t = std::remove_reference_t<decltype(*this)>; \
        msgpack::operator<<(mpkobjz, static_cast<self_t&>(*this)); \
    }

基底クラスの定義.
ずいぶんとスッキリしました.

//
// Monster.h
//

// オブジェクトを復元するファクトリクラスのヘッダ
#include "restore_factory.h"
#include "poly_pack_base.h"
#include "poly_serializeable_adapter.h"

// Monsterクラス
class Monster : public poly_pack_base {
public:
    Monster() noexcept;
         :
    MSGPACK_EXTRA_DECL_FACTORY_CLASS(restore_factory);
    MSGPACK_EXTRA_DECL_TYPE_ID(MosterClassTypeIds::Mid_Monster);
    MSGPACK_EXTRA_POLYPACK_METHOD_IMPL();

    MSGPACK_DEFINE(life_point_, magic_point_);
private:
    int life_point_;
    int magic_point_;
};

こちらは派生クラスの定義です.

//
// Goblin.h
//
#include "Monster.h"

// Goblinクラス
class Goblin : public Monster {
public:
    Goblin() noexcept;
        :
    MSGPACK_EXTRA_DECL_TYPE_ID(PackClassIDs::Mid_Goblin);
    MSGPACK_EXTRA_POLYPACK_METHOD_IMPL();

    MSGPACK_DEFINE(MSGPACK_BASE(Monster), race_class_, race_class_);
private:
    int race_class_;
    int drop_item_ids_;
};

多態性を維持したままのシリアライズができれば、だいたいのシーンには対応できそうですね.


参考記事