この記事はeeic (東京大学工学部電気電子・電子情報工学科) Advent Calendar 2018の17日目の記事として書かれました。昨日はyuyabathさんの土屋言語への誘い、明日はhatiparallelさんのeeicタベホ同好会の歩みです。
去年の超クソ雑configureとMeson比較に引き続き、Mesonについて書きます。昨日までmesonタグ2記事しかなくて悲しいナァ
似たような記事
- GStreamer Advent Calendar 2016のHello Library World: The Meson Build System
- Tutorial (公式; 英語)
- Getting started with Meson build system and C++ (英語) どうしてCMakeではなくMesonを (書いた人が) 選んだのか、という理由もあります
メインの作者さんのCppCon 2018での発表動画も英語ですがYouTubeに上がっています。
そもそもMesonってなにさ
Meson is an open source build system meant to be both extremely fast, and, even more importantly, as user friendly as possible.
-- The Meson Build System https://mesonbuild.com/
(意訳) Mesonは、驚異的な速さと、もっと重要な要素である、よりユーザーフレンドリーであることを意図したオープンソースビルドシステムです。
ビルドシステムって何じゃいなという人もいるかと思いますが、ビルドに関連するツールのことです。どういうことかわからないですね。ビルドは、コンパイルして実行ファイルを作る一連の流れ周辺のことを指す言葉です。Mesonはその中でもコンパイルの前準備を扱います。C/C++の分野でいうと、Mesonと似たようなものには、CMake、autotools (+automake)、GYPなどがあります。
このへんは見たほうがはやいかなあと思うので、次に行きましょう。
Mesonのインストール
Mesonは前準備を扱うので、実際のコンパイルなどはNinja、Visual Studio、Xcodeなどを使います。コンパイル補助ツールとして有名 (?) なmakeは使えません。
MesonはPythonで書かれており、pipでインストールできます。大抵の人のPCはpythonとpipがインストールされているはずなので、
$ pip install meson ninja --user
でインストールできます。
--user
をつけたときのインストールされる場所は、python -c 'import site; print(site.USER_BASE);'
で確認できます。
あとは、python -c 'import site; print(site.USER_BASE);'
で出力されたディレクトリ直下のbinにPATHを通せば終わりです。
$ PATH="${PATH}:$(python -c 'import site; print(site.USER_BASE);')/bin" # PATHを通します (bash向け)
$ meson --version
0.50.1 # インストールされたバージョンが出力されます。0.45以上がおすすめです
普通の*nix環境ならシステムのパッケージマネージャー (brew、apt、yum1など) を使うことも出来ます。ただ、バージョンが古く使い物にならない場合があるので注意しましょう。
WindowsならGitHubのreleasesページから取得できるmsiインストーラーでもインストール出来ます。
使ってみる
というわけで、実際にmesonを使ってみましょう。
「プログラミングに関連することの最初は、Hello worldを出力することから始めるべきだ」のようなことは源氏物語にも書いてある2ので、「Hello worldを出力するプログラム」をビルドしてみましょう。
#include <stdio.h>
int main() {
printf("hello world\n");
}
# hello worldから始めろとは源氏物語には書いてありませんが、
# meson.buildはprojectから始めるというのは世界の理です。
project('helloworld', 'c')
# hello.cをコンパイルしてhelloという名の実行ファイルを作成します
executable('hello', 'hello.c')
meson.buildにはシンタックスハイライトが効かないのは僕のせいではないので許してください。GitHubならハイライトされます
上のファイルを作成したら、mesonを使ってビルドしてみましょう。
Visual Studio上でビルドしたい人は、Visual Studio 20XX Command Promptを開き3、ソースコードのフォルダーにcd
コマンドで移動した上で、
meson build --backend vs
とすればbuild
フォルダーにVisual Studio用のslnファイルが作成されるはずです。作成されたら、Visual Studioで開いていつものようにビルドしてください。以下同様なので、Windowsの説明は省きます。
Xcode?--backend xcode
でも渡せば動くんじゃね、知らんけど。
GCC/Clang+Ninjaを使う人は
$ meson build/
$ cd build/
$ ninja
でビルドまで走ります4。実行はbuild/
の中で
$ ./hello
hello world
です。なんか出力のしまりが悪いですね。Hello world!
を出力するようにソースコードを書き換えてみますか。
#include <stdio.h>
int main() {
printf("Hello world!\n");
}
再ビルドのときはmeson
コマンドを実行する必要はありません。
$ ninja
$ ./hello
Hello world!
(中級編) もうちょっと頑張る
あーなんか突然数値の足し算を計算するだけのライブラリと、それを使うプログラム書きたくなってきたなー!!書きたくて書きたくて腕がうずいちゃうなー!!!もうこれは書くしかないなー!!!!!!
というわけで書きましょう。ソースコードはcalcブランチにあります
#pragma once
int plus(int lhs, int rhs);
#include "libperfectcalc/plus.h"
int plus(int lhs, int rhs) {
return lhs + rhs;
}
#include <iostream>
#include "libperfectcalc/plus.h"
int main() {
int lhs, rhs;
std::cin >> lhs >> rhs;
std::cout << plus(lhs, rhs) << std::endl;
}
# c++のソースコードの場合はcppを2つ目の引数に渡します
project(
'calculator', 'cpp',
default_options: ['warning_level=3'])
# ↑名前付きの引数を渡すときは、名前も書く必要があります。
# どういう引数が渡せるかは https://mesonbuild.com/Reference-manual.html#project を見ましょう。
# ちなみに、default_optionsとして渡せるオプションは、ビルドディレクトリの中でmeson configureで一覧が見えます。
# includeディレクトリをincludeできるように、include_directoriesオブジェクトを作成します。(ただの呪文です)
includes = include_directories('include')
# subdir()で別のディレクトリのmeson.buildを読み込むことが出来ます。
subdir('src')
subdir('libperfectcalc')
program(
'calculator', ['main.cpp'], # 勘の良い方は気づくかと思いますが、ソースファイルは複数指定できます (['a.cpp', 'b.cpp'])
include_directories: includes, # インクルードディレクトリの指定
link_with: perfectcalc_lib) # リンクするライブラリの指定
# library() でライブラリを定義します。
# ただの library() の場合はデフォルトでは shared library (libperfectcalc.soなど) が作られます。
# meson build/ -Dbuildtype=static や meson configure -Dbuildtype=static で
# static library (libperfectcalc.a) が作られるようにも出来ます。
perfectcalc_lib = library(
'perfectcalc', ['plus.cpp'],
include_directories: includes)
meson.build
の分割はぶっちゃけ気分です。全部一つのファイルにまとめてもいいです。
このままbuild/
の中で再ビルドしてもいいですが、default_options
の効果が発揮されるのは最初のmeson
を呼び出すときなので、一度build/
を消しちゃいましょう……5
$ rm -rf build/
$ meson build/
$ ninja -v # ninjaに-vを渡すと、実行したコマンドをひとつずつ表示してくれます。
[1/5] ccache c++ -Isrc/libperfectcalc/src@libperfectcalc@@perfectcalc@sha -Isrc/libperfectcalc -I../src/libperfectcalc -I../include -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -Wnon-virtual-dtor -Wextra -Wpedantic -g -fPIC -MD -MQ 'src/libperfectcalc/src@libperfectcalc@@perfectcalc@sha/plus.cpp.o' -MF 'src/libperfectcalc/src@libperfectcalc@@perfectcalc@sha/plus.cpp.o.d' -o 'src/libperfectcalc/src@libperfectcalc@@perfectcalc@sha/plus.cpp.o' -c ../src/libperfectcalc/plus.cpp
[2/5] ccache c++ -Isrc/src@@calculator@exe -Isrc -I../src -I../include -fdiagnostics-color=always -pipe -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -Wnon-virtual-dtor -Wextra -Wpedantic -g -MD -MQ 'src/src@@calculator@exe/main.cpp.o' -MF 'src/src@@calculator@exe/main.cpp.o.d' -o 'src/src@@calculator@exe/main.cpp.o' -c ../src/main.cpp
[3/5] ccache c++ -o src/libperfectcalc/libperfectcalc.so 'src/libperfectcalc/src@libperfectcalc@@perfectcalc@sha/plus.cpp.o' -Wl,--no-undefined -Wl,--as-needed -shared -fPIC -Wl,--start-group -Wl,-soname,libperfectcalc.so -Wl,--end-group
[4/5] /usr/lib64/python-exec/python3.6/meson --internal symbolextractor src/libperfectcalc/libperfectcalc.so 'src/libperfectcalc/src@libperfectcalc@@perfectcalc@sha/libperfectcalc.so.symbols'
[5/5] ccache c++ -o src/calculator 'src/src@@calculator@exe/main.cpp.o' -Wl,--no-undefined -Wl,--as-needed -Wl,--start-group src/libperfectcalc/libperfectcalc.so -Wl,--end-group '-Wl,-rpath,$ORIGIN/libperfectcalc' -Wl,-rpath-link,/home/eclair/workspace/cpp/meson-handson/build/src/libperfectcalc
さっきdefault_options
にwarning_level=3
を指定したので、コンパイラーコマンドに警告を出力するオプション (-Wall等) が付いていることがわかります (コマンド行が長くてわかりにくいですが……)6。また、試した環境にはccacheがインストールされているので、ccacheが (勝手に) 使われていたこともわかります。
重要なのは、build/src/libperfectcalc
にlibperfectcalc.so
が出来ているのと、build/src
にcalculator
が出来ていることです7。実行してみましょう。
$ src/calculator
1 3
4
1 3
を入力して最後にEnterすると4が出力されるはずです。完璧8に足し算が行える計算機が完成しましたね。
ちゃんとlibperfectcalc.so
を使うようにもなっています。
$ ldd src/calculator
...
libperfectcalc.so => /home/eclair/workspace/cpp/meson-handson/build/src/libperfectcalc/libperfectcalc.so (0x00007f6c3f04b000)
...
(上級編) インストールされていたりされていなかったりするライブラリをつかう
最後に、Luaを使ってちょこっと遊んでみましょうか。ソースコードはlua_depブランチに置いてあります
#include <lua.hpp>
int main() {
lua_State* state;
#if LUA_VERSION_NUM >= 501
state = luaL_newstate();
#else
state = lua_open();
#endif
luaL_openlibs(state);
// まあつまり、Hello world!をLua経由で出力させるだけのプログラムです
luaL_dostring(state, "print \"Hello world!\"");
lua_close(state);
}
説明の都合上default_options
にwarning_level=3
を渡しません。
project('luatoasobu', 'cpp')
# luaのようにpkg-configなどが使えるライブラリは、dependency()で
# インクルードディレクトリやリンクするライブラリなどを適切に設定できるようになります。
lua_dep = dependency('lua')
# pkg-configが使えない場合は、find_library()やinclude_directories()などを使う必要があるかもしれません
subdir('src')
executable(
'main', ['main.cpp'],
dependencies: [lua_dep])
というわけでビルドして実行してみましょう!build/以下は削除してもしなくてもどっちでも良いですが、default_options
を書き換えたので削除しておくことにします。
$ rm -rf build/
$ meson build/
The Meson build system
Version: 0.45.1
Source dir: /home/illyasviel/tmp/meson-handson
Build dir: /home/illyasviel/tmp/meson-handson/build
Build type: native build
Project name: luatoasobu
Native C++ compiler: ccache c++ (gcc 7.3.0 "c++ (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0")
Build machine cpu family: x86_64
Build machine cpu: x86_64
Found pkg-config: /usr/bin/pkg-config (0.29.1)
meson.build:7:0: ERROR: Native dependency 'lua' not found
A full log can be found at /home/illyasviel/tmp/meson-handson/build/meson-logs/meson-log.txt
(さっきと違うPCなのでいろいろ違うのは許してください)
luaは見つかんないよ!と怒られてしまいました。それもそのはず。このPCはluaがインストールされていないのです!(luaがインストールされている場合は普通にビルドされます)
普通にluaをインストールしても良いのですが、ここではMesonのWrap dependency systemというものを使ってみましょう。
そうと決まれば、まずアクセスするべきはWrapDBです。長い一覧の中にluaがあることがわかりますね。
luaの行をクリックすると別のページに飛びます。どれでもいいのでDownloadリンクをクリックすると、なんかよくわからないファイルが表示されるかダウンロードされると思います。
[wrap-file]
directory = lua-5.3.0
source_url = http://www.lua.org/ftp/lua-5.3.0.tar.gz
source_filename = lua-5.3.0.tar.gz
source_hash = ae4a5eb2d660515eb191bfe3e061f2b8ffe94dce73d32cfd0de090ddcc0ddb01
patch_url = https://wrapdb.mesonbuild.com/v1/projects/lua/5.3.0/6/get_zip
patch_filename = lua-5.3.0-6-wrap.zip
patch_hash = 2b287e1f31466a7a3cc4ca319c436b812ce8c55b5bd6f7ff591f43052e914862
これをプロジェクトのsubprojects/lua.wrap
に保存しましょう。
あるいは、コマンドで叩くのがお好きな方は、
$ meson wrap search lua # このwrapはサブコマンドです。build/はサブコマンドではないので、ややこしいですね。
lua ← luaはさっきのWrapDBに登録されているよということです
$ mkdir subprojects # これはどうやら必須のようです
$ meson wrap install lua
Installed lua branch 5.3.0 revision 6
でも同じことが出来ます。
このwrapファイルは何者?という疑問は置いておいて、さっさとビルドが通るようにしちゃいましょう。例によってソースコードはlua_wrapブランチに置きました。
# 変更箇所だけ示します
# ↓fallback: を渡すようにした
lua_dep = dependency('lua', fallback: ['lua', 'lua_dep'])
ただ残念ながら、wrapファイルを置くことでLuaのインストールは不要になりますが、環境によってはreadlineの開発者用ファイルをインストールする必要があります3。
$ meson build/
The Meson build system
Version: 0.45.1
Source dir: /home/illyasviel/tmp/meson-handson
Build dir: /home/illyasviel/tmp/meson-handson/build
Build type: native build
Project name: luatoasobu
Native C++ compiler: ccache c++ (gcc 7.3.0 "c++ (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0")
Build machine cpu family: x86_64
Build machine cpu: x86_64
Found pkg-config: /usr/bin/pkg-config (0.29.1)
Downloading lua from http://www.lua.org/ftp/lua-5.3.0.tar.gz
Download size: 278045
Downloading: ..........
Downloading patch from https://wrapdb.mesonbuild.com/v1/projects/lua/5.3.0/6/get_zip
Download size: 1673
Downloading: ..........
Executing subproject lua.
Project name: lua
Native C compiler: ccache cc (gcc 7.3.0 "cc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0")
Library dl found: YES
Library m found: YES
Library readline found: YES
Build targets in project: 2
Subproject lua finished.
Dependency lua from subproject subprojects/lua found: YES
Build targets in project: 3
Found ninja-1.8.2 at /usr/bin/ninja
実行してみましょう。
$ ninja
$ src/main
Hello world!
うまくいきましたね。
このようにMesonのWrap dependency systemを使うと、システムにインストールされていない依存先を準備してビルドできるようにしてくれます。インストールされている場合はインストールされているものを使ってくれます。便利ですね。
もちろん、WrapDBに登録されていないライブラリでも、自分でwrapファイルを書けば同じようにやってくれます。Wrap dependency system manualを参考にどうぞ。もし自分でwrapファイルを書く場合は、subprojectの中で死んだときに理由が出力されない困るバグが修正されているので、Meson 0.45.0以上を使いましょう。
まとめ
Mesonは楽だし書きやすくていいぞ!
Q&A
どうして上級編ではwarning_level=3
にしなかったのですか
試してもらうとわかるのですが、luaのビルドでコンパイラが警告を吐くのが鬱陶しいからです。
lua側のビルド中はwarning_level=1にする方法とか、逆にroot projectのみwarning_level=3にする方法があれば良いんですけど、ぱっと見なさそうなんですよね……ここが残念。
ちなみに、-isystemオプションをサポートしているコンパイラーなら、ライブラリのヘッダファイル内の警告であれば抑制することが出来ます。
include_directories('path/to/include', is_system: true)
ただ、WrapDBに登録されているwrapは、include_directoriesにis_systemが渡されていないことが多いので(お察しください)
そういう意味で、自分でwrapファイルを書いたほうがなにかと都合が良いかも。
v0.52.0より、dependency()
にinclude_type: 'system'
を渡せるようになりました。あくまでヘッダinclude時警告の制御なので、wrapファイル内でコンパイルを行う場合はあまり意味がないですが……
-
epelレポジトリの追加が必要 ↩
-
嘘です ↩
-
build/
の部分は、お好きなディレクトリ名で構いません。 ↩ -
こうやってなにも気にせずにビルドディレクトリを消せるのは、out-of-treeと呼ばれる、ソースコードとは別のディレクトリをビルドディレクトリにする形 (をmesonが強制する) だからですね ↩
-
Windowsではコマンドの引数に渡せる長さがあまり長くないので、コマンド引数の大部分は別のファイルに保存されて代わりに
@file
引数が渡されてコンパイラーが呼ばれます。なので、どういうコマンド引数が渡されているのかが出力だけではわからなくなります。 ↩ -
デフォルトの
buildtype
はshared
なのでlibperfectcalc.a
ではありません。 ↩ -
足される数、結果ともにint型の範囲で収まる限り ↩