2016年9月28日水曜日

C/C++中規模プロジェクト用のMakefileを自動生成する

C/C++中規模プロジェクトのための超シンプルなMakefile」という記事が話題になっていた。

僕はC/C++のプロジェクトを作る場合、いつも Autotools (Autoconf, Automake, etc. の総称) を使ってる。




Autotools は少ない労力で似たような目的に使えて非常に便利なんだ("out-of-sourceビルド" や、ヘッダ依存関係の自動生成、再帰 make 等々に対応、make install などの便利ターゲットも準備されている)。 ./configure && make && make install というコマンドは有名で、みんな一度は実行したことがあると思うけど、この3つのコマンドによるビルドが可能な configure スクリプトとMakefile.in は普通、 Autotools によって生成されてる。

例えば、 libcurl を使用した以下のようなプログラムを書いたとしよう。
このプログラムはURLを引数としてとり、そのURLのコンテンツを標準出力に表示する、というプログラムだ。

/*
 * fetchurl.c: fetch url and print the content to stdout
 */
#include <stdio.h>
#include <curl.h>

int main(int argc, char **argv)
{
        CURL *ch;
        CURLcode rc;
        char errbuff[CURL_ERROR_SIZE];

        if (argc < 2) {
         fprintf(stderr, "Usage: %s <url>\n", argv[0]);
         return 1;
        }

        curl_global_init(CURL_GLOBAL_ALL);
        ch = curl_easy_init();
        curl_easy_setopt(ch, CURLOPT_URL, argv[1]);
        curl_easy_setopt(ch, CURLOPT_ERRORBUFFER, errbuff);
        rc = curl_easy_perform(ch);
        if (rc != CURLE_OK) {
         fprintf(stderr, "%s: %s: failed to fetch: %s\n", argv[0], argv[1], errbuff);
         return 1;
        }

        return 0;
}

libcurl がどこにインストールされているかは環境依存なので、コンパイラに指定する -I は環境から取得したいし、リンカに指定する -lなんとか も Makefile には直接指定したくない。

じゃあ、このプログラムをビルドする Makefile を Autotools で生成してみよう。

まず、上のソースコード fetchurl.c を src/fetchurl.c に配置したとして、まず次のような Makefile.am と src/Makefile.am を用意する

Makefile.am は次の1行だ:

SUBDIRS = src
 
Autotools は Makefile.am に SUBDIRS を見つけると、そこで指定されたディレクトリに再帰的に降りていって make するようなルールを自動生成してくれる。
次に src/Makefile.am:

bin_PROGRAMS = fetchurl

fetchurl_SOURCES = fetchurl.c
fetchurl_CFLAGS = @CURL_CFLAGS@
fetchurl_LDADD = @CURL_LIBS@

bin_PROGRAMS には最終的な生成バイナリ名を、 <バイナリ名>_SOURCES にはバイナリのソース・ファイルを指定する。<バイナリ名>_CFLAGS(C++の場合は<バイナリ名>_CXXFLAGS)や <バイナリ名>_LDADD にはコンパイラやリンカに渡すオプションを指定する。 bin_PROGRAMS には複数のバイナリを書くことができ、 <バイナリ名>_SOURCES 等は bin_PROGRAMS に書いたバイナリごとに複数記述する。

@CURL_CFLAGS@ などは後ででてくる PKG_CHECK_MODULES で指定したライブラリ用のオプションで自動的に置き換えるためのマクロだ。ライブラリが pkg-config 対応なら、このようなマクロを利用可能だ。(広く使われているライブラリなら、だいたい対応していると思う)
Makefile.am を書き終えたら、 autoscan を実行する。

% autoscan

すると、カレントディレクトリに configure.scan というファイルが作成されるので、 configure.ac という名前に変更して、次のように編集する。

--- a/configure.scan
+++ b/configure.ac
@@ -2,14 +2,16 @@
 # Process this file with autoconf to produce a configure script.

 AC_PREREQ([2.69])
-AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])
+AC_INIT([fetchurl], [0.0.1], [someone@example.com])
 AC_CONFIG_SRCDIR([src/fetchurl.c])
 AC_CONFIG_HEADERS([config.h])
+AM_INIT_AUTOMAKE([foreign])

 # Checks for programs.
 AC_PROG_CC

 # Checks for libraries.
+PKG_CHECK_MODULES([CURL], libcurl)

 # Checks for header files.

人間が作成・編集しないといけないファイルは以上だ(実質10行にも満たない)。
次に autoreconf -if を実行して、configure スクリプトや Makefile.in, その他補助スクリプトなどを生成する。

% autoreconf -fi
configure.ac:11: installing './compile'
configure.ac:8: installing './install-sh'
configure.ac:8: installing './missing'
src/Makefile.am: installing './depcomp'

ここまでの手順で、おなじみの

% ./configure
% make
% make install

するだけでビルドとインストールができるソースツリーが完成した。このツリーはそのまま配布してもいいけど、 make distcheck すれば、配布用の tarball を生成し、生成した tarball で "out-of-source ビルド" して make install できるかどうかまでチェックしてくれる

% make distcheck
make  dist-gzip am__post_remove_distdir='@:'
make[1]: Entering directory '/home/kt/fetchurl'
if test -d "fetchurl-0.0.1"; then find "fetchurl-0.0.1" -type d ! -perm -200 -exec chmod u+w {} ';' && rm -rf "fetchurl-0.0.1" || { sleep 5 && rm -rf "fetchurl-0.0.1"; }; else :; fi
test -d "fetchurl-0.0.1" || mkdir "fetchurl-0.0.1"
 (cd src && make  top_distdir=../fetchurl-0.0.1 distdir=../fetchurl-0.0.1/src \
     am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)

...(中略)...

rm -f Makefile
make[1]: Leaving directory '/home/kt/fetchurl/fetchurl-0.0.1/_build/sub'
if test -d "fetchurl-0.0.1"; then find "fetchurl-0.0.1" -type d ! -perm -200 -exec chmod u+w {} ';' && rm -rf "fetchurl-0.0.1" || { sleep 5 && rm -rf "fetchurl-0.0.1"; }; else :; fi
================================================
fetchurl-0.0.1 archives ready for distribution:
fetchurl-0.0.1.tar.gz
================================================


配布用の tarball はそのまま ./configure && make && make install が可能なソースツリーになっており、配布先に Autotools がインストールされている必要はない。

もちろん利用ライブラリである libcurl がインストールされていなければビルドできないけど、たいていの OS ディストロにおいて依存関係の解決はパッケージマネージャ(yum(dnf)やapt)の領域だ。



この方法には、もちろん欠点もある。例えば最終的に生成されるバイナリがどのソースから作成されるのか、 Makefile.am にちゃんと書かないといけない(ソースツリーをスキャンして動的に決定することはできない)というところ。これはおそらく、明示的に指定された内容のみに依存したルールを生成すべき、というポリシーでこのような仕様になっていると思われる。ソースツリーをスキャンして動的に決定すると、思わぬルールになってしまうことがあるからだ。

ただ、あえてそうしたい場合も Makefile.am を生成するようなスクリプトを書けばいいだけだったりする。例えば僕は生成すべきバイナリが頻繁に増えるようなプロジェクトでは 、そのような Makefile.am 生成スクリプトを使っている。

Autotools は日本語の情報が少いうえ、歴史を重ねたプロジェクトのMakefile.amは非常に複雑な内容になっていることも多いので、小さいプロジェクトに応用しようとしたときに参考になる元ネタが少ない。でも、公式のチュートリアルなどをひととおりこなせば、最小限の構成をどうすればよいかなどはわかるようになっているので、読んでみるのをおすすめしたい。

また、Autotools 以外では、最近は CMake を使うプロジェクトが増えている気がするので、今から勉強するなら、そちらでも良いかもしれない。

0 件のコメント:

コメントを投稿