読者です 読者をやめる 読者になる 読者になる

clock-up-blog

go-mi-tech

Visual Studio の C# コンソールプロジェクトを CentOS 上の mono で実行する

C# Visual Studio CentOS NuGet

Visual Studio 形式の C# コンソールプロジェクトを CentOS 上で mono を用いてビルド・実行する方法について。

前提環境

# uname -a
Linux host1 3.10.0-327.36.3.el7.x86_64 #1 SMP Mon Oct 24 16:09:20 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

# cat /etc/redhat-release
CentOS Linux release 7.2.1511 (Core)

対象プロジェクト

Visual Studio 2015 による C# プロジェクトの例としてシンプルなコンソールプロジェクトを以下に置いた。

アプリケーション実行時の日時を表示してファイル保存するだけのシンプルな挙動。
今回はこのプロジェクトを CentOS 上で mono を用いて実行することを試みる。

何故 mono を選ぶのか

時代としては .NET Core の流れなんだろうけど .NET Core の dotnet コマンドだと Visual Studio 形式の .sln や .csproj ファイルが直接扱えない。mono だと xbuild コマンドにより .sln や .csproj が直接扱える。これはやっぱ楽。

そんなわけで今回は mono を用いる。(まぁ慣れの問題もあると思うので .NET Core についても追々ちゃんと使えるように検証は進めておこうと思う)

yum で入る mono について

# yum -y install mono-devel

# mono --version
Mono JIT compiler version 4.2.4 (tarball Mon Sep 19 02:09:55 UTC 2016)

# xbuild /version
XBuild Engine Version 12.0
Mono, Version 4.2.4.0

一応こんな感じで yum で mono 入れるのは楽なんだけど xbuild のバージョンが 12.0 だと Visual Studio 2013 (MSBuild 12.0) のプロジェクトまでしかビルドできない。

Visual Studio 2015 (MSBuild 14.0) のプロジェクトをこの xbuild でビルドしようとしても「Project has unknown ToolsVersion '14.0'. Using the default tools version '4.0' instead.」やら何やら言われてビルドは失敗する。

そんなわけなので今回はもっと新しい mono を入れる。

ソースコードから mono を導入する

ソースコードの入手

今回は現在(2017年1月16日)の最新 master を用いてみた。

# git clone https://github.com/mono/mono.git
# cd mono
# git branch
* master
# git log -1
commit 38983b6b1bfddaad132f9defea168e810f537e98

最新 master を用いるというのは少しばかり冒険になることが割とあるので本当は tag をチェックアウトしたほうが安全なんだけど今回は生の master でも成功してしまったのでそのまま進めた。

もう少し安全策を踏むなら以下のように適当な tag を見つけてチェックアウトすると良いと思う。(自分は試してないけど)

# git tag
....
mono-4.8.0.429
....
# git checkout mono-4.8.0.429

ソースコードのビルド・インストール

取得した mono ディレクトリ内で以下コマンドを実行することにより mono のビルドおよびインストールが行われる。

# ./autogen.sh
# make
# make install

この make はなかなか時間がかかる。1時間くらいは見積もっておいたほうが良いと思う。ちゃんと計測はしてないけど make の実行を開始してからご飯を作ってご飯を食べ終わっても make は終わってなかった。

インストールされた mono のバージョン確認

$ which mono
/usr/local/bin/mono

$ which xbuild
/usr/local/bin/xbuild

$ mono --version
Mono JIT compiler version 4.9.0 (master/38983b6 Mon Jan 16 20:59:18 JST 2017)
Copyright (C) 2002-2014 Novell, Inc, Xamarin Inc and Contributors. www.mono-project.com
        TLS:           __thread
        SIGSEGV:       altstack
        Notifications: epoll
        Architecture:  amd64
        Disabled:      none
        Misc:          softdebug
        LLVM:          supported, not enabled.
        GC:            sgen (concurrent by default)

$ xbuild /version
XBuild Engine Version 14.0
Mono, Version 4.9.0.0

tag としてはまだ存在しないバージョン 4.9 が入った。なかなか気持ちがいい。

mono による .sln のビルド (NuGet 依存無しの場合)

mono の xbuild コマンドにより .sln をビルドできる。実際には .sln を指定しても .csproj を指定しても同じようにビルドできるのだが今回は .sln を指定してビルドしてみる。

ちなみにプロジェクトが NuGet パッケージに依存しているかどうかでビルドの仕方が少し変わる。今回の実験リポジトリでは NuGet 依存の無いバージョンに no-nuget タグを打ってあるので、まずはそれのビルド・実行を試す。

$ git clone https://github.com/kobake/CsharpJikken.git
$ cd CsharpJikken
$ git checkout no-nuget
$ xbuild CsharpJikken.sln
XBuild Engine Version 14.0
Mono, Version 4.9.0.0
Copyright (C) 2005-2013 Various Mono authors

Build started 1/17/2017 12:14:01 AM.
....
Build succeeded.
         0 Warning(s)
         0 Error(s)

Time Elapsed 00:00:03.4969050

けっこう長いログが出るけど最終的に「0 Error」と出てればビルドは成功しているはず。

ビルドにより生成された .exe ファイルの実行

$ mono ./CsharpJikken/bin/Debug/CsharpJikken.exe
hello 1/17/2017 12:16:48 AM

$ cat hoge.txt
hello 1/17/2017 12:16:48 AM

ちゃんと実行できた。(ここまでは割と情報収集にも苦労しなかった)

次は NuGet パッケージに依存したプロジェクトのビルドを試してみる。

NuGet パッケージに依存したプロジェクトのビルド・実行

実験リポジトリ (CsharpJikken) の最新 master は Nuget パッケージであるところの Newtonsoft.Json (Json.NET) に依存するようにプロジェクトを構成してある。これのビルドを試みる。

証明書関連の同期

NuGet パッケージを取得するときに NuGet サーバと HTTPS 通信をすることになるのだが、ルート証明書を最新にしておかないとこれが失敗することがある。mozroots コマンドによりあらかじめルート証明書の同期を行っておく。

# mozroots --import --machine --sync
Mozilla Roots Importer - version 4.9.0.0
Download and import trusted root certificates from Mozilla's MXR.
Copyright 2002, 2003 Motus Technologies. Copyright 2004-2008 Novell. BSD licensed.

WARNING: mozroots is deprecated, please move to cert-sync instead.

Downloading from 'https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt'...
Importing certificates into machine store...
1 new root certificates were added to your trust store.
Import process completed.

実験プロジェクトの取得

今回は CsharpJikken の最新 master を用いる。この実験プロジェクトは Newtonsoft.Json (Json.NET) パッケージの機能を用いて JSON 出力を行うように実装してある。

$ git clone https://github.com/kobake/CsharpJikken.git
$ cd CsharpJikken
$ git checkout master

NuGet パッケージの依存解決

mono 自体は NuGet 機能を持っていないので dist.nuget.org から nuget.exe を取得し、これを用いて NuGet パッケージの依存解決を行う。

$ wget https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
$ mono nuget.exe restore CsharpJikken.sln
MSBuild auto-detection: using msbuild version '14.0' from '/usr/local/lib/mono/xbuild/14.0/bin'.
Restoring NuGet package Newtonsoft.Json.9.0.1.
  GET https://api.nuget.org/v3-flatcontainer/newtonsoft.json/9.0.1/newtonsoft.json.9.0.1.nupkg
  OK https://api.nuget.org/v3-flatcontainer/newtonsoft.json/9.0.1/newtonsoft.json.9.0.1.nupkg 638ms
Installing Newtonsoft.Json 9.0.1.
Adding package 'Newtonsoft.Json.9.0.1' to folder '/home/kobake/CsharpJikken/packages'
Added package 'Newtonsoft.Json.9.0.1' to folder '/home/kobake/CsharpJikken/packages'

NuGet Config files used:
    /home/kobake/.config/NuGet/NuGet.Config

Feeds used:
    /home/kobake/.nuget/packages/
    https://api.nuget.org/v3/index.json

Installed:
    1 package(s) to packages.config projects

$ ls -l
....
drwxrwxr-x 3 kobake kobake      34 Jan 17 00:56 packages
....

依存解決に成功するとソリューションディレクトリ配下に packages という名前のディレクトリができる。(この中に依存モジュールのファイル群が入る形になる。このディレクトリは git 管理外になるように .gitignore に記述しておくのが一般的。)

.sln をビルド

$ xbuild CsharpJikken.sln
XBuild Engine Version 14.0
Mono, Version 4.9.0.0
Copyright (C) 2005-2013 Various Mono authors

Build started 1/16/2017 9:47:39 PM.
....
Build succeeded.
         0 Warning(s)
         0 Error(s)

Time Elapsed 00:00:03.4875230

「0 Error」となっていれば成功。

できあがった .exe を実行

$ mono ./CsharpJikken/bin/Debug/CsharpJikken.exe
{
  "message": "hello",
  "time": "2017-01-17T01:01:07.123896+09:00"
}

$ cat hoge.txt
{
  "Q": "hello",
  "time": "2017-01-17T01:01:07.123896+09:00"
}

ちゃんと JSON 機能が働いている(依存している Newtonsoft.Json パッケージが機能している)ことが確認できた。

トラブルシューティング

参考までにダメな例も載せておく。(割とハマる人は多いと思う)

依存解決(mono nuget.exe restore)忘れの例

NuGet パッケージに依存しているプロジェクトにおいては mono nuget.exe restore を行わずに xbuild だけ実行すると以下のようなエラーが出る。

$ xbuild CsharpJikken.sln
XBuild Engine Version 14.0
Mono, Version 4.9.0.0
Copyright (C) 2005-2013 Various Mono authors

Build started 1/17/2017 12:34:25 AM.
....
    Target ResolveAssemblyReferences:
/usr/local/lib/mono/xbuild/14.0/bin/Microsoft.Common.targets:  warning : Reference 'Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL' not resolved
        For searchpath {CandidateAssemblyFiles}
        Warning: {CandidateAssemblyFiles} not supported currently
        For searchpath {HintPathFromItem}
        Considered ../packages/Newtonsoft.Json.9.0.1/lib/net45/Newtonsoft.Json.dll, but it does not exist.

....

Build FAILED.

....

Errors:

/home/kobake/CsharpJikken/CsharpJikken.sln (default targets) ->
(Build target) ->
/home/kobake/CsharpJikken/CsharpJikken/CsharpJikken.csproj (default targets) ->
/usr/local/lib/mono/xbuild/14.0/bin/Microsoft.CSharp.targets (CoreCompile target) ->

    Program.cs(1,7): error CS0246: The type or namespace name 'Newtonsoft' could not be found (are you missing a using directive or an assembly reference?)

     1 Warning(s)
     1 Error(s)

Newtonsoft.Json 依存の解決に失敗している。xbuild 前にちゃんと mono nuget.exe restore しましょう。

mono nuget.exe restore が失敗する例

ルート証明書を最新にしておかないと以下のように nuget の通信でエラーが起こることがある。

$ mono nuget.exe restore CsharpJikken.sln
....
  /home/kobake/.nuget/packages/: Package 'Newtonsoft.Json.9.0.1' is not found on source '/home/kobake/.nuget/packages/'.
  https://api.nuget.org/v3/index.json: Unable to load the service index for source https://api.nuget.org/v3/index.json.
  Error: TrustFailure (The authentication or decryption has failed.)
  The authentication or decryption has failed.
  Invalid certificate received from server. Error code: 0xffffffff800b0109
....

mono のバージョンによっては微妙にメッセージに出方が違ったりするが言わんとすることに変わりはない。

$ mono nuget.exe restore CsharpJikken.sln
....
  /home/kobake/.nuget/packages/: Package 'Newtonsoft.Json.9.0.1' is not found on source '/home/kobake/.nuget/packages/'.
  https://api.nuget.org/v3/index.json: Unable to load the service index for source https://api.nuget.org/v3/index.json.
  An error occurred while sending the request
  Error: TrustFailure (Ssl error:1000007d:SSL routines:OPENSSL_internal:CERTIFICATE_VERIFY_FAILED)
  Ssl error:1000007d:SSL routines:OPENSSL_internal:CERTIFICATE_VERIFY_FAILED
....

上記のようなエラーが出る場合は mozroots (または cert-sync) によるルート証明書の更新をちゃんと行っておきましょう。

おわり

Windows 側での C# プロジェクト開発時に Linux での実行時の都合をそれほど考えていなくても、Linux 側でうまく mono と nuget.exe を取り回せばそのまんまの構成でビルド・実行できることが確認できました。これはなかなか快適です。

ちなみにここまで記事を書き終えてから気づいたんだけど「WARNING: mozroots is deprecated, please move to cert-sync instead.」って言われてましたね。今は mozroots よりも cert-sync を使うほうが推奨されてるっぽいです。(追々このへんは検証しなおします)

});