[Meson]Meson for C++の苦闘記

MesonでC++プロジェクトをクロスプラットフォームにビルドできるようにしたときのメモです。C++以外の事は分かりません・・・

基本

基本的なビルドスクリプトは以下のようになります。

# 必ずproject()から始める
project('test_project', 'cpp', default_options : ['warning_level=3', 'werror=true', 'cpp_std=c++17'], meson_version : '>=0.50.0')

# インクルードディレクトリ指定
include_dir = include_directories('include', 'oher/include')

# 実行可能ファイルを出力
executable('test_project', 'test.cpp', include_directories : include_dir)

公式のリファレンスとか

コンパイラを検出する

meson.get_compiler('cpp')コンパイラオブジェクト?を取得して、そこからget_id()コンパイラ文字列を取得します。
あとはifで分岐するだけです。

if cppcompiler == 'msvc'
# msvc用の処理
elif cppcompiler == 'gcc'
# gcc用の処理
elif cppcompiler == 'clang'
# clang用の処理
endif

ちなみに、コンパイルオプションを主要3コンパイラで分けたいだけならば、get_argument_syntax()を使うと便利です。これによって得られる文字列は、オプションの互換性があるコンパイラで同一になります。

project('test_project', 'cpp', default_options : ['warning_level=3', 'werror=true', 'cpp_std=c++17'], meson_version : '>=0.50.0')
cppcompiler = meson.get_compiler('cpp').get_argument_syntax()

if cppcompiler == 'msvc'
    # MSVC,clang-cl,icc(windows)用
    options = ['/std:c++latest']
elif cppcompiler == 'gcc'
    # gcc,clang,icc(linux)用
    options = ['-std=c++2a']
else
    # その他
    options = []
endif

include_dir = include_directories('include', 'oher/include')

executable('test_project', 'test.cpp', include_directories : include_dir, cpp_args : options)

例えばこうしておくと、それぞれのコンパイラで言語バージョンの指定ができます。
(ただし、デフォルトオプションとして指定している言語バージョンもそのままになってしまうので、MSVC等では警告が出ます・・・)

以下のページにこれらの関数で取得できるコンパイラ文字列の一覧があります。

VC++プロジェクトの癖

仕方ないことなのかもしれませんが、Mesonの出力するVC++プロジェクトは少し変わっています・・・

  • VS同梱の開発者コマンドプロンプトからmeson build --backend vsを実行しないといけない
  • 出力されたVC++メインのプロジェクトのプロパティはほぼ空(デフォルト)
    • 指定したコンパイルオプション等はビルド時には渡されているが、プロパティからは見えない・・・
      • このため、インテリセンスがC++14準拠になってしまう
  • プロジェクトプロパティの変更は、ビルド時にmeson.buildが変更されていてプロジェクト再出力が自動で行われた場合にリセットされる
    • 基本的にはこれ便利なんですけどもね・・・

VC++プロジェクトにヘッダを含める

出力されるVC++プロジェクトには指定したソースファイルは含まれていますが、インクルードディレクトリ内のヘッダは含まれていません。
例えばそれらのファイルを編集したくてVS上で開いたとしても、プロジェクト外のファイルに対してはインテリセンスがうまく働きません。
そのため、プロジェクトにそれらのヘッダを含めたいことがあるでしょう・・・

その場合は、ソースファイルと同じようにヘッダファイルを指定してやれば出力プロジェクトに含めることができます。

executable()extra_filesにプロジェクトに含めたいファイルを指定してやると含めておくことができます。

project('test_project', 'cpp', default_options : ['warning_level=3', 'werror=true', 'cpp_std=c++17'], meson_version : '>=0.50.0')
cppcompiler = meson.get_compiler('cpp').get_argument_syntax()

files = ['include/header1.hpp', 'include/header2.hpp']

include_dir = include_directories('include', 'oher/include')

executable('test_project', 'test.cpp', extra_files : files, include_directories : include_dir)

あるフォルダ内のファイルの列挙、ファイル名にワイルドカードを使う、等

できません。

ビルド高速化のために、この様な曖昧な書き方ができないようになっているようです。
残念ながら、プロジェクトに含めるファイルは1つづつ明示的に指定する必要があります。

依存ライブラリをダウンロードしてもらう

依存ライブラリの指定はsubproject()を使えば出来ます。これはインストール済みCMake(もしくはパッケージマネージャ)を検出して、そこから依存ライブラリ情報を取得してダウンロードして・・・と自動でやってくれる様子です。

でもWindowsだとそんなの入ってないし、githubから引っ張ってきたリポジトリとかでもよろしくやってほしいものです。
そのままだとこれは出来ない様子ですが、ラップファイルを用意してやることでやってもらえます。

meson.buildがあるフォルダにsubprojectsというフォルダを作り、その中にライブラリ名.wrapというファイルを用意しておきます。

例えば、doctestというライブラリを使いたいとしますと。

subprojects/doctest.wrapは以下のように書きます。

[wrap-git]
directory=doctest
url=https://github.com/onqtam/doctest.git
revision=2.3.4
clone-recursive=true

意味はなんとなくわかると思います。directory=の所を変えるとダウンロードされるディレクトリ名が変わるようです。revision=はダウンロードしてくるものの指定です。HEADとかコミットハッシュが使えるようです。

そして、meson.buildを以下のようにします。

project('test_project', 'cpp', default_options : ['warning_level=3', 'werror=true', 'cpp_std=c++17'], meson_version : '>=0.50.0')

#サブプロジェクトの指定
doctest_proj = subproject('doctest')
#依存オブジェクトの取得(名前が決まっている)
doctest_dep = doctest_proj.get_variable('doctest_dep')

files = ['test.cpp', 'include/header1.hpp', 'include/header2.hpp']

include_dir = include_directories('include', 'oher/include', 'subprojects/doctest')

executable('test_project', files, include_directories : include_dir, cpp_args : options, dependencies : doctest_dep)

subproject('プロジェクト名')で依存ライブラリを指定し(多分ここでダウンロード等がなされる)、その戻り値からget_variable('ライブラリ名_dep')で依存オブジェクト?を取得します。
この依存オブジェクトは、対象ライブラリの持つmeson.buildに書かれている名前を指定しなければなりません(慣例的にライブラリ名_depとなっているようです)。

最後に、executable()に依存オブジェクトを指定してあげます。もし静的ライブラリ等の出力がある場合はここで自動的に取り込まれるようです(対象ライブラリの持つmeson.buildが適切に書かれていれば)。

この方法、git submoduleで対象のライブラリを管理していても、なんだかよろしくやってくれます。

ちなみにこれらの時、ダウンロードしてきたプロジェクトのトップにmeson.buildが無いとたぶん上手くいきません・・・。 ただ、ヘッダーオンリーライブラリならインクルードパスの指定だけしてやればいい気がします(get_variable()してexecutable()で依存関係指定をしないで、subproject()だけしておく)

CI(Travis AppVeyar)

これはまだ試していないのでどうなるのかわかりませんが、公式サイトにTravisとAppVeyarに対するymlのサンプルがあります。この通りにやれば出来そうです。

参考文献

この記事のMarkdownソース