CMake
Make
autotools
meson

この記事はeeic Advent Calendar 2017 の17日目の記事として書かれたものです。昨日はsatewnさん担当でした。明日はasaiさんの単語ベクトルの話らしいです。

この記事では、ビルド補助ツールとしてのmakeと、configureとその先の話を軽くしたいなと考えています。去年の学科合宿LTでやった内容ですが、ゆるして……

make

eeicの人たちならたぶん2年生で講義で習(った|う)のではと思う1のですが、makeコマンドというものがありまして、大雑把に言うと、たくさんの実行するコマンドを必要なものだけまとめて実行してくれる、小さくない規模のC/C++開発には無いと困る (ことが多かった) 便利ツールです。別にC/C++に限った話でもないですが。

ここではとりあえず、

main.c
#include <stdio.h>

int main() {
  printf("生命、宇宙、そして万物についての究極の疑問の答え: %d\n", answer());
}
ans.h
int answer();
ans.c
int answer() {
  return 32; // ←打ち間違えてしまった……!
}

というanswer42プロジェクトがあったとします。

このプロジェクトに

Makefile
all: ans.o main.o
    $(CC) $^ -o main

Makefileを用意してあげれば、makeコマンドを打てば

$ make
cc    -c -o ans.o ans.c
cc    -c -o main.o main.c
cc ans.o main.o -o main

みたいなことやってくれます。

間違いに気がついて、ans.cを

ans.c
int answer() {
  return 42; // ←修正しました
}

に修正しても、makeコマンドは

$ make
cc    -c -o ans.o ans.c
cc ans.o main.o -o main

のように、変更していないmain.cから作成されるmain.oの必要のない再生成を省いてくれ2、必要のないコンパイルを行わないのでちょっとだけ開発→手動テストサイクルを早く回せるわけです。

メリット

  • 必要のないコマンドは省いてくれる
  • 実行する内容はただのシェルスクリプト
  • デフォルトで、Cコンパイル等のルールが定義されてある

デメリット

  • 一行ごとにシェルを呼び出すので、遅い
  • サブディレクトリはmakeを再帰的に呼び出す構造になっていることが多く、遅い

configure

C/C++で書かれたソフトウェアをソースコードからコンパイルしてローカルにインストールしたいとなったら、たいてい定型文句を唱えればよく、それが

$ ./configure
長い出力
$ make
やっぱり長い出力
$ sudo make install
比較的短い出力

でした3。今はこの呪文を唱えることも少なくなってしまったように感じます4がいまだにこの呪文が通じる状況は少なくありません。

configureは基本的にシェルスクリプトですが、結局シェルから実行できるようになっていれば5pythonでもなんでもいいです。基本的にはshが一番使われます。

で、このconfigureが何やっているのかといえば、ビルドに必要なコンパイラのチェック、ライブラリのチェック、機能のオンオフ、ビルドに必要なファイルの生成など片手で数えられないくらいのことはやっています。

でもチェックするコードを手で書くのはだるいので、autotoolsと呼ばれるフレームワークが使われることが多いです6

autotools

autotoolsは、configureを生成するautoconfと、Makefileを生成するautomakeと、その他もろもろする諸々コマンドを含むフレームワークです。

configure.ac (または非推奨configure.in) があればたいていautoconfが使われています。Makefile.amがあればautomakeも使われています。個人的なプロジェクトならよっぽどの理由がなければ、automakeも使ったほうが楽でしょう。

answer42の場合は、例えばこんなファイルが必要になるでしょう。

configure.ac
#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.60])
AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])
AC_CONFIG_SRCDIR([main.c])
AC_CONFIG_HEADERS([config.h])

# Checks for programs.
AC_PROG_CC

# Required by automake
AM_INIT_AUTOMAKE([foreign subdir-objects])

AC_CONFIG_FILES([Makefile])
AC_OUTPUT
Makefile.am
bin_PROGRAMS = main
main_SOURCES = ans.c main.c

ファイルを配置して、

$ autoreconf -iv  # autoconfやautomake等の必要なコマンドをいい感じの順番で呼び出してくれます
$ ./configure
$ make
$ ./main

してあげれば、動くことが確認できると思います。

特徴

  • m4というマクロ言語でconfigure.ac (やMakefile.am) を書く
  • 昔のデファクトスタンダード、今でも広く使われている

メリット

だいたいのパッケージにautotoolsが使われているので、参考にするコードは多い

たとえば三つ列挙すれば、git、vim 7、emacs……古くからあるプロジェクトはMakefile単体かautotoolsのどちらかが使われていることが多いです。

makeやshell scriptに詳しくなれる……かも

m4がいくらマクロ言語だとしても、書くのはMakefileとシェルスクリプトなので……

クロスコンパイルがしやすい

configureに--host=x86_64-w64-mingw32を渡してあげるだけでクロスコンパイルしてくれます。嘘です。してくれる場合もあります。別途設定が必要な場合もあります。

それでも後に述べる二つより簡単だと思っています。PC環境向けならば。残念なことにマイコン向けとかのクロスコンパイルをautotoolsで行ったことがないので、それ以上のことはわかりません。

デメリット

とにかく書くのが辛い

あるフラグが対応しているかどうかの判定すら、標準そのままだと辛い 8 ので、ライブラリをincludeして使うなり自分で書くことが多くなると思います。たとえば、GNUのコンパイラが特定のフラグに対応しているかを判定するライブラリ

また、m4のマクロに引数を渡す時、角括弧を付けて渡すことが推奨されています。たとえば下のように。

AX_CHECK_COMPILE_FLAG([-std=c++14],
       [AX_APPEND_FLAG([-std=c++14], [extra_cxxflags])], [
       AC_MSG_WARN([$CXX is not supported -std=c++14. check your compiler version])
       AX_CHECK_COMPILE_FLAG([-std=c++1y],
               [AX_APPEND_FLAG([-std=c++1y], [extra_cxxflags])], [
               AC_MSG_WARN([$CXX is not supported -std=c++1y. may not be supported C++14 features!!])])])

まだこれは楽な方なので大したことはないのですが、

The most common problem with existing macros is an improper quotation. This section, which users of Autoconf can skip, but which macro writers must read, (後略)
-- https://www.gnu.org/software/autoconf/manual/autoconf.html#M4-Quotation

と公式で書かれるくらいには、この角括弧の付け方もろもろはトリッキーです。

必要なファイルが多い

中間ファイルがたくさんある図

出力ファイルが大きい

例えば、Subversionのトップディレクトリのconfigureは、858KB 28706行です。多いか少ないかは個人の判断によりますが、経験的に言えばconfigureだけで2MB 9 行くこともままある気がします。まあ画像よりは遥かに小さいんですけど。

Windowsのようにfork-execが遅い環境だと辛い

辛いです。mingw/msysを使ってる人はmingw-w64/msys2を使うと若干早くなります。それでも辛いです。

cmake

autotoolsを使ったconfigureが辛いものだったので、置き換えを目指したのか目指さなかったのか、CMakeとかいうものが世の中にはあります。

CMakeLists.txt
# 雰囲気だけ読み取ってください。
cmake_minimum_required (VERSION 3.2)

ENABLE_LANGUAGE(CXX)
project(proj)

set(CMAKE_CXX_STANDARD 14)
set(CXX_STANDARD_REQUIRED ON)
if (NOT MSVC)
    set(DX_GCC_COMPILE ON)
endif ()
add_definitions(-DHAVE_CONFIG_H)
if (CMAKE_BUILD_TYPE STREQUAL Debug)
    add_definitions(-D_DEBUG)
else()
    add_definitions(-DNDEBUG)
endif()

include_directories("${proj_BINARY_DIR}/src" "${proj_SOURCE_DIR}/src")

file(GLOB_RECURSE libproj_files RELATIVE ${proj_SOURCE_DIR} src/*.cpp src/*.cxx src/*.h)
list(REMOVE_ITEM libproj_files src/WinMain.cpp)
list(APPEND libproj_files "${proj_BINARY_DIR}/src/config.h")
add_library(projcore ${libproj_files})

add_dependencies(projcore dxlib)
target_link_libraries(projcore
    DxLibW DxUseCLibW DxDrawFunc jpeg png zlib tiff theora_static
    vorbis_static vorbisfile_static ogg_static bulletdynamics bulletcollision bulletmath
    liblua g3logger)

特徴

必要なファイルはCMakeLists.txtのみ

autotoolsに比べて、ファイルのスリム化を計ったみたいですね。

autotoolsみたいに標準で出来ないことが多い、なんてことはない

autotoolsならGNUのライブラリを使わないと出来なかった、C++14の利用 (-std=c++14) が、CMakeなら一行で出来るよ!

メリット

最近広がっている

autotoolsの代替として、それなりの勢いで広まっていると思います。例えば、LLVMやOpenCV、MySQLはCMakeです。

実行が軽い

シェルスクリプトじゃないので、比較的configure相当のプロセスが速いです。

WindowsのVisual Studio向け、macOSのXcode向けのプロジェクトが作れる

cmakeの引数にそれ用のオプションを渡してあげれば、mingw-w64とかを入れなくてもWindowsでビルドできるようになるのは結構良いと思います。

あと、Makeが遅くて嫌になった人向けにNinjaというビルドツール 10 でもビルドできるようにしてくれたりもします。

指定してあげればファイルのダウンロード、展開、ビルドもやってくれる

らしいです (ExternalProject_Add)。使ったことがそこまでないのであまり触れられません。

デメリット

まあ大体はGStreamer and Meson: A New Hopeのコメントあたりに書いてありますが。

記法が新しい()

if (NOT MSVC)
    set(DX_GCC_COMPILE ON)
endif () # ←endにも括弧をつけるのか……
check_include_files("windows.h;versionhelpers.h" HAVE_VERSIONHELPERS_H)

set(list_var a b c) # ←リストが作られる
message("${list_var}") # a;b;c
message(${list_var}) # abc (三つの引数として渡される = message(a b c)と一緒 (らしい)、慣れないと混乱する
# (え、シェルスクリプトもそうだって?くぁwせdrftg……

CMake (中略) trades whitespace-safety for semicolon-safety: ok, it's not common to put semicolons in filenames, but if you do you'll have a nightmake in CMake
-- http://blog.nirbheek.in/2016/05/gstreamer-and-meson-new-hope.html?showComment=1464020031868#c3890433598738944056

引数で挙動が変わる

string(FIND <string> <substring> <output variable> [REVERSE])
string(REPLACE <match_string> <replace_string> <output variable> <input...>)
string(REGEX MATCH <regular_expression> <output variable> <input…>)
string(APPEND <string variable> [<input>...])
string(CONCAT <output variable> [<input>...])

上は公式のドキュメントより。string_findじゃだめだったんですか。

一つのことをやるのに複数のやり方を提供する

コンパイルフラグを指定するのに複数のやり方があるので、覚えきれなかった 印象 があります。ごめん最近CMake触ってないからもう忘れた……

上のブログだとtarget_link_librariesが例に出されています。ちょっとよくわからなかったから割愛。

変数/プロパティありすぎ問題

変数一覧プロパティ一覧

CMakeのまとめ

書いててだいぶCMakeの記憶が抜けてたことに気がつきました。間違ってたこと書いてたらごめんなさい。

まあなんというか、結局のところこの言葉に落ち着くと思います。

CMake works great for many people, but it doesn't work for everyone.
-- http://blog.nirbheek.in/2016/05/gstreamer-and-meson-new-hope.html?showComment=1464011145671#c8053239224284015105

個人的にはautotoolsと同じくらいクソという感じでした。CMake使っていたときはLinuxでWindows向けにクロスコンパイルしているから余計なのかもしれませんが……

CMakeの公式ドキュメントの通りクロスコンパイル設定ファイルを作ったら、windresがない (x86_64-w64-windresとしてインストールされていたので) エラーが出てひっかかったとか、cotireというCMakeのライブラリ (?) を使わないとプリコンパイル済みヘッダをよしなに出来ないとか、そのくせしてコンパイラのいわゆる-flto向けのオプションがあるとか、そうじゃないだろ感があって…………

Meson

というわけで、このままクソばっかり言っててもアレなので、もう一つくらい紹介したいと思います。

ロゴが可愛い

まだそこまで広く使われているわけではないのですが、特にGNOME界隈で人気です

meson.build
# こいつも雰囲気だけで。
project('hogefuga', 'cpp',
    default_options: [
        'cpp_std=c++14',
        'warning_level=3',
        'default_library=static',
    ],
    version: '4.1',
    license: 'MIT')

cpp_compiler = meson.get_compiler('cpp')

project_warnings = []
if get_option('warning_level') == '3'
    extra_warnings = [
        '-Wctor-dtor-privacy',
        '-Wdelete-non-virtual-dtor',
        '-Wdisabled-optimization',
        '-Wfloat-equal',
    ]
    foreach warn : extra_warnings
        if cpp_compiler.has_argument(warn)
            add_project_arguments(warn, language: 'cpp')
        endif
    endforeach
endif

if get_option('buildtype').contains('debug')
    if cpp_compiler.has_argument('-rdynamic')
        add_project_link_arguments('-rdynamic', language: 'cpp')
    endif
endif

boost_dep = dependency('boost', modules: ['exception'])
g3logger_dep = dependency('g3logger',
    fallback: ['g3log', 'g3logger_dep'],
    default_options: ['dynamic_logging=true']
)

libavcodec_dep = dependency('libavcodec')
libavutil_dep = dependency('libavutil')
libswscale_dep = dependency('libswscale')
ffmpeg_dep = declare_dependency(
    dependencies: [libavcodec_dep, libavutil_dep, libswscale_dep])

どれくらい使われていないかというと、このようにソースコードに色付けしてくれないくらいには広く使われていないのですが、それはそれとして。

なんか見た目からもうわかりやすくて心躍りませんか?躍らないですかそうですか……

特徴

makeサポートを完全に撤廃している

make?そんな古いものは知らんですよ

makeは古くから使われているので、私が持っているmakeとあなたが持っているmakeが違うということが稀によくあります 11。あと、上にも書いたけど遅い。

mesonではNinjaというヤングでナウいツールを使ってビルドします。モチロンVisual StudioやXcodeにも対応してますよ?

in-treeビルドに対応していない

mesonではout-of-treeビルドを強制されます。必ず新しいディレクトリを作って、その中でビルドする必要があるということです。

in-treeビルド、つまりソースtarを落として展開してその中にcdしてmake!は出来ないということです。説明が分かりにくいな

メリット

ビルドが速い (ことになっている)

CMake+Ninjaに比べて、Meson+Ninjaのほうが速いということになっているらしいです

Ninja自体が一般的な条件下ではMakeより速いので、CMake+Makeよりはずっと速いですが、CMake+NinjaとMeson+Ninjaを比べてもそこまで大きくは変わらないと思います。どうせ実行するコマンドは大体一緒なので

記法が読みやすい

個人の感想ですが、autotoolsやCMakeに比べて圧倒的に読みやすいです。

指定してあげればファイルのダウンロード、展開、ビルドもやってくれる (CMakeとカブり)

……のですが、Mesonの場合はmeson.buildがビルドするパッケージの中にある必要があります (wrap file)。zip/tar.gzだったら、別途meson.build等の必要なファイルを用意してあげて、新規に必要なファイルだけをzipで固めてあげればいいです。参考例。gitだとフォークしてあげる必要がありそう。

………………ところで、このwrap fileなんですが、ハッシュエラーとかmeson.buildの構文エラーとかがあると死ぬほどわかりにくいエラーメッセージで死にやがるんで、ちょっとそこだけはありえないと思います。

デメリット

あまり広く使われていない

まあ前述のとおりです。それでもナウヤングに大人気 (?) のsystemdはautotoolsからmesonに完全移行しました。

勝手にccacheが使われる

デメリットなのかどうかはしらないですが、ccacheがPATH内にあると勝手に使ってくれます。

シンプルな分、凝ったことで出来ることが少ない

たとえば、src/以下のファイルを全てビルド対象に追加、なんてことはできず、基本的にはspecificである必要があります。公式によればre-configure時にあるファイルなら出来ますが。

あと、Meson言語はチューリング完全ではないので、functionが定義できません (!!!!!)。foreachとかで頑張りませう。

日本語のドキュメントが少ない

英語の公式ドキュメントは比較的読みやすいので、そこまで苦にはならないと思っていますが、読みたくないものは読みたくない…………

hello world的なものならば去年のGStreamer Advent CalendarHello Library World: The Meson Build Systemが参考になると思います。あとは公式のWrapDBに登録されているプロジェクトのwrap fileの、patch_urlを落として見てみるのもいいかもしれません。

全体のまとめ

新しいプロジェクトを立てるならば、Meson使えばいいんじゃないかな?(予定調和)(これがいいたかっただけ)(ありがとうございました)


  1. そもそも記憶違いかもしれないし、教官とカリキュラムが変わってるから習わないかもしれないです。 

  2. 本来ならばmain.oはmain.cとans.hが更新されたら再生成するべきですが、今回のサンプルコードではそうなっていません。 

  3. ./configure --prefix=${HOME}/installのようにしてroot権限が必要のないところへのインストールを指示すればmake installにsudoは必要ないですし、理由がなければそうするべきだと思っていますが、本質ではないので省いています。 

  4. パッケージマネージャーから入れることが出来るのでソースからコンパイルする必要がなくなったり、後述のconfigureではないビルド設定コマンドを使うソフトが増えてきたのが理由です。 

  5. つまり、基本的には、shebangが書いてあって実行権限があるファイルのことです。 

  6. 逆に、当然ではありますが、autotoolsであることは必須ではないので、ffmpegのように./configureがシェルスクリプトでありながら手でメンテナンスするプロジェクトは存在します。 

  7. configure.acがあるのに拡張子inなしのMakefileがあったりしてちょっと変則的? 

  8. フラグを付けて、ソースコードを書いてコンパイルして、コンパイルに失敗したらフラグをもとに戻して、という段階を踏む必要があります (間違ってるかも) 

  9. 例えば、gcc-7.2.0のlibstdc++-v3/configureの2.2M 84842行 

  10. 速くて軽い、ナウいヤング向けのツール。https://ninja-build.org/ 

  11. いわゆるGNU MakeとBSD Make