autoconf and m4

選挙

衆議院の選挙が有った。前回は出遅れて一番乗りを逃してしまったので、今回は頑張ってみた。早朝とはいえ、気温6度で、散歩には丁度良い塩梅。15分前に到着。余裕がありすぎたか。

会場になってる所は、幼稚園。廊下に多数の水槽が置いてあって、メダカやら、たにしやら、ザリガニやら、ドジョウやら地域の生き物が多数居たよ。番外編で、ウーパールーパーもつがいでいた。しげしげと眺めると、みんなかわいいな。メダカなんて眺めていて飽きる事がない。

廊下には、おさんぽまっぷが張り出されていて、どこで取れたか、説明してあった。今度、探しに行ってみようかな。

開場の5分前ぐらいに、ポツポツと人が集まってきた。例の投票箱の空を3人の有権者が確認。そして施錠。儀式に立ち会えた。これが楽しみなのよね。人によっては、立ち会い証明書を求めて、それをSNSに載せている人もいるけど、オイラーはそこまでミーハーではない。

後ろにいたおばちゃんが、比例代表と裁判官の審査の票の投票箱を間違えそうになって、あわてて係の人から止められていた。うっかりしてたら間違えるよな。両票を同時に配布するってのは、設計不良ではなかろうか。

期日前投票を済ませていた女房に聞いたら、票は3回に分けてもらったそうな。施行員の人数不足(予算不足)で、こうなってしまったのか? とある村では、参議院の補欠、村長さん、村議会の選挙も同時挙行で、箱が6つも並ぶ所があるそうな。

こういう場合は、もう、パイプライン配置で設計しないと、間違いは必ず起きるぞ。どういう工夫をしたか、知りたいものだ。

.deps/el.Po

前回、自前で作成したconfigure,Makefileでコンパイルすると。下記のようなファイルが自動作成された。ヘッダーの依存関係のファイル(キャッシュ)と思われる。

ob$ head .deps/el.Po
el.o: el.c config.h /usr/include/stdlib.h /usr/include/sys/cdefs.h \
  /usr/include/machine/cdefs.h /usr/include/sys/_null.h \
  /usr/include/machine/_types.h /usr/include/sys/types.h \
  /usr/include/sys/endian.h /usr/include/sys/_endian.h \
  /usr/include/sys/_types.h /usr/include/machine/endian.h \
  /usr/include/time.h /usr/include/sys/_time.h

config.h:

/usr/include/stdlib.h:
  :

念のため、リナでやってみると、こんな風になった。

el.o: el.c /usr/include/stdc-predef.h config.h /usr/include/stdlib.h \
 /usr/include/x86_64-linux-gnu/bits/libc-header-start.h \
 /usr/include/features.h /usr/include/x86_64-linux-gnu/sys/cdefs.h \
 /usr/include/x86_64-linux-gnu/bits/wordsize.h \
 /usr/include/x86_64-linux-gnu/bits/long-double.h \
   :
 /usr/include/x86_64-linux-gnu/bits/types/locale_t.h \
 /usr/include/x86_64-linux-gnu/bits/types/__locale_t.h
/usr/include/stdc-predef.h:
config.h:
/usr/include/stdlib.h:

makeのスピードアップをするんだろうね。el.oはec.cやらconfig.hやらstdlib.hやらで出来上がっています。stdlib.hなんかは、その中で更にincludeしてるから、それも追跡しておいてねて事。これを毎回やってたんじゃ、たまりませんって訳だな。

configure of ruby

前回からのconfigure繋がりで、rubyのそれが、どうなってるか眺めてみた。

dnl Process this file with autoconf to produce a configure script.

AC_PREREQ(2.67)

m4_include([tool/m4/_colorize_result_prepare.m4])
m4_include([tool/m4/ac_msg_result.m4])
m4_include([tool/m4/colorize_result.m4])
m4_include([tool/m4/ruby_append_option.m4])

: ${GIT=git}
HAVE_GIT=yes
AC_ARG_WITH(git,
        AS_HELP_STRING([--without-git], [never use git]),
        [AS_CASE([$withval],
            [no],  [GIT=never-use HAVE_GIT=no],
            [yes], [],
            [GIT=$withval])])
AS_IF([test x"$HAVE_GIT" = xyes], [command -v "$GIT" > /dev/null || HAVE_GIT=no\
])
AC_SUBST(GIT)
AC_SUBST(HAVE_GIT)

自前で定義したm4のマクロを取り込んでいるね。それでも4000行を越る大作。色々なプラットフォームで動くようにするには、チェックが大変なのだな。おまけに、ユーザーの要求も入れて、GITがドータラコータラとやり始めたらきりがない。裏方は大変なのよ。

FreeBSDの上でconfigureを走らせてログを眺めていたら、 arc4random_buf がyesてなってたぞ。OpenBSDの専売特許とばかりに思っていた。configure.acの中では、どんな風にチェックしてるの?

AC_CHECK_FUNCS(arc4random_buf)
AC_CHECK_FUNCS(atan2l atan2f)
AC_CHECK_FUNCS(chroot)

何の脈略もなく、存在してるかチェックしてる(一応、アルファベット順に並べてあるな)。物語になっていないぞ。これがconfigureに展開されると、

for ac_func in arc4random_buf
do :
  ac_fn_c_check_func "$LINENO" "arc4random_buf" "ac_cv_func_arc4random_buf"
if test "x$ac_cv_func_arc4random_buf" = xyes; then :
  cat >>confdefs.h <<_ACEOF
#define HAVE_ARC4RANDOM_BUF 1
_ACEOF

fi
done

こんな風になるんか。重要なのは、 #define HAVE_ARC4RANDOM_BUF 1 ってやつだな。今度は、それが何処で使われているか調べてみる。

[sakae@fb /tmp/ruby-3.0.2]$ grep HAVE_ARC4RANDOM_BUF -rIl .
./config.log
./random.c
./configure
./.ext/include/i586-freebsd13.0/ruby/config.h

random.cを見ろと指示されたので参照。

#elif defined(HAVE_ARC4RANDOM_BUF)
static int
fill_random_bytes_syscall(void *buf, size_t size, int unused)
{
#if (defined(__OpenBSD__) && OpenBSD >= 201411) || \
    (defined(__NetBSD__)  && __NetBSD_Version__ >= 700000000) || \
    (defined(__FreeBSD__) && __FreeBSD_version >= 1200079)
    arc4random_buf(buf, size);
    return 0;
#else
    return -1;
#endif
}
#elif defined(_WIN32)

前後の関係を見ると、質の良い乱数発生器を決めているみたいだ。BSD属はバージョンを確認して安全を担保しているのね。

autoconf

上で出て来た AC_CHECK_FUNCS がどんな働きをするかautoconfのinfoを引いてみる。

-- Macro: AC_CHECK_FUNCS (FUNCTION..., [ACTION-IF-FOUND],
         [ACTION-IF-NOT-FOUND])
    For each FUNCTION enumerated in the blank-or-newline-separated
    argument list, define 'HAVE_FUNCTION' (in all capitals) if it is
    available.  If ACTION-IF-FOUND is given, it is additional shell
    code to execute when one of the functions is found.  You can give
    it a value of 'break' to break out of the loop on the first match.
    If ACTION-IF-NOT-FOUND is given, it is executed when one of the
    functions is not found.

    Results are cached for each FUNCTION as in 'AC_CHECK_FUNC'.
[sakae@fb /usr/local/share/autoconf-2.69/autoconf]$ grep AC_CHECK_FUNCS -rIl .
./autoconf.m4f
./autoheader.m4
./functions.m4

functions.m4

# Table of contents
#
# 1. Generic tests for functions.
# 2. Functions to check with AC_CHECK_FUNCS
# 3. Tests for specific functions.

# AC_CHECK_FUNCS(FUNCTION..., [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
# ---------------------------------------------------------------------
# Check for each whitespace-separated FUNCTION, and perform
# ACTION-IF-FOUND or ACTION-IF-NOT-FOUND for each function.
# Additionally, make the preprocessor definition HAVE_FUNCTION
# available for each found function.  Either ACTION may include
# `break' to stop the search.
AC_DEFUN([AC_CHECK_FUNCS],
[m4_map_args_w([$1], [_AH_CHECK_FUNC(], [)])]dnl
[AS_FOR([AC_func], [ac_func], [$1],
[AC_CHECK_FUNC(AC_func,
               [AC_DEFINE_UNQUOTED(AS_TR_CPP([HAVE_]AC_func)) $2],
               [$3])dnl])
])# AC_CHECK_FUNCS

どうも、これっぽいな。この後ろの方には、よく使われる関数を高速に調べるルーチンが鎮座してた。そしてこれらの展開系っぽいのが、autoconf.m4fってファイルに用意されてた。これで、スピードアップを図っているのだろう(多分)。

そして、これらのファイルが有る所に、c,erlang,fortrn,goなんて言う言語向けの関連品もあった。それから、libs.m4にはX11関連のライブラリィーについての設定もあった。

ああ、autoconfコマンド自身は、shell scriptなんだな。

autoconf-$version -> autom4te てな具合に使われるのかな。

AUTOM4TE(1)                      User Commands                     AUTOM4TE(1)

NAME
       autom4te - Generate files and scripts thanks to M4

ac_fn_c_check_func

上で出て来た、関数が有るかのチェックはconfigureスクリプトでは、下記のように行われる(再掲)。

for ac_func in arc4random_buf
do :
  ac_fn_c_check_func "$LINENO" "arc4random_buf" "ac_cv_func_arc4random_buf"
if test "x$ac_cv_func_arc4random_buf" = xyes; then :
  cat >>confdefs.h <<_ACEOF
#define HAVE_ARC4RANDOM_BUF 1
_ACEOF

fi
done

肝は、 ac_fn_c_check_func という関数になる。引数を3個取る。その実行結果がyesなら、confdefs.hにその旨を書出している。

この大事な関数は、1486行目(当社の場合)から定義されてた。

# ac_fn_c_check_func LINENO FUNC VAR
# ----------------------------------
# Tests whether FUNC exists, setting the cache variable VAR accordingly
ac_fn_c_check_func ()
{
  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
   :

間にコメントが入っていたりして分かりずらいが、簡単なC言語のソースをその場で作成して、コンパイルし、ちゃんとリンク出来るかを別に定義された関数( ac_fn_c_try_link )でチェックしてる。

どんな関数が作成されるかと言うと

#define arc4random innocuous_arc4random
#ifdef __STDC__
# include <limits.h>
#else
# include <assert.h>
#endif

#undef arc4random
#ifdef __cplusplus
extern "C"
#endif
char arc4random ();
#if defined __stub_arc4random || defined __stub___arc4random
choke me
#endif

int
main ()
{
return arc4random ();
  ;
  return 0;
}

こんなリンクが成功するかのチェックだ。これをコンパイルしてみると、成功する。本当にチェックしてるのか? 検証で、arc4randomをbadfuncなんて言う、ありそうも無い名前に書き換えて実行してみる。ファイル名は、期待を込めてbad.cにした。

[sakae@fb /tmp/hoge]$ cc bad.c
ld: error: undefined symbol: badfunc
>>> referenced by bad.c
>>>               /tmp/bad-c1a505.o:(main)
cc: error: linker command failed with exit code 1 (use -v to see invocation)

システムに無い関数はリンクできませぬって、ちゃんと検出してくれているね。ようするに、コンパイラにチェックさせるって事だ。なかなか賢いな。ボンクラーなオイラーは、そんなの/usr/includeあたりをgrepすればいいじゃんと思っていたぞ。

それはそうと、choke me なんていう意味不明な語句が出て来てる。オイラーなんて直に何でここにプロレス技が登場すんねん? と思った口だ。そう、レフィリィーに隠れての首締め技ね。

ぐぐった所、gcc以外のコンパイラーは、当然エラーになる。けど、gccは、これを飲み込んでケロリとするようになってるとか。

choke me

autoconfに何か説明がないか探してみる。こういう特異語は、一発ヒットするだろう。ここからは、視認性が良いOpenBSDのautoconf-2.69を対象にする。

vbox$ cd /usr/local/share/autoconf-2.69/
vbox$ fgrep 'choke me' -rIl .
./autoconf/c.m4
./autoconf/fortran.m4
./autoconf/lang.m4

雰囲気からすると、C言語用とFORTRAN言語用のやつがあって、それらを統合してるのが lang.m4って事かな。どれから見ても、さして手間がかからないと思われ。

c.m4

m4_define([AC_LANG_FUNC_LINK_TRY(C)],
[AC_LANG_PROGRAM(
   :
#ifdef __cplusplus
extern "C"
#endif
char $1 ();
/* The GNU C library defines this for functions which it implements
    to always fail with ENOSYS.  Some functions are actually named
    something starting with __ and the normal name is an alias.  */
#if defined __stub_$1 || defined __stub___$1
choke me
#endif
], [return $1 ();])])

コンパイラーの挙動の違いを吸収する為の苦労話が色々とかいてあったぞ。

今度は、lang.m4を見る

# _AC_LANG_COMPILER_GNU
# ---------------------
# Check whether the compiler for the current language is GNU.
#
# It doesn't seem necessary right now to have a different source
# according to the current language, since this works fine.  Some day
# it might be needed.  Nevertheless, pay attention to the fact that
# the position of `choke me' on the seventh column is meant: otherwise
# some Fortran compilers (e.g., SGI) might consider it's a
# continuation line, and warn instead of reporting an error.
m4_define([_AC_LANG_COMPILER_GNU],
[AC_CACHE_CHECK([whether we are using the GNU _AC_LANG compiler],
                [ac_cv_[]_AC_LANG_ABBREV[]_compiler_gnu],
[_AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [[#ifndef __GNUC__
       choke me
#endif
]])],
                   [ac_compiler_gnu=yes],
                   [ac_compiler_gnu=no])
ac_cv_[]_AC_LANG_ABBREV[]_compiler_gnu=$ac_compiler_gnu
])])# _AC_LANG_COMPILER_GNU

GNUのコンパイラーコレクションの挙動を調べるに都合がよい語句だって事かな。歴史的なものなんだな。

まて、騙されている感じがするぞ。choke me をabout us に変えてコンパイルしても、コンパイルは成功する。choke meは特異語でコンパイラーに内蔵されてるかと思ったけど、そうではないようだ。

そこで、自分がコンパイラーになった積もりで、CPPを実行(今風だと、cc -E)してみると、choke meは、すっかりフィルターされて除去されている。そりゃそうだわな。#if definedって指令で、定義を調べているからね。

フィルター条件を満たすようにして、コンパイルしてみると

[sakae@fb /tmp]$ cc test.c
z.c:14:1: error: unknown type name 'choke'
choke me
^
z.c:14:9: error: expected ';' after top level declarator
choke me
        ^
        ;
2 errors generated.

こんな風に、反則、反則と訴えてきましたよ。自分で自分の首を締めているからね。 上記は、FreeBSDのclangだけど、Debianのgccだと、

z.c:14:1: error: unknown type name ‘about’
   14 | about him
      | ^~~~~
z.c:14:10: error: expected ‘;’ before ‘int’
   14 | about him
      |          ^
      |          ;
......
   17 | int
      | ~~~

最近のgccはclang対抗で、エラー表示が親切になってますな。若干17行目のそれは、親切過ぎるきらいが有るな。

m4

m4-1.4.19のtar玉の中に同梱されてたexampleから面白そうなやつを取り出してみた。

[sakae@fb /tmp/m4-1.4.19/examples]$ cat debug.m4
define(`countdown', `$1 ifelse(eval($1 > 0), 1, `countdown(decr($1))', `Liftoff')')
debugmode(`aeqc')
traceon(`countdown')
countdown(2)

何やら、デバッグのやりかたみたい。countdownって関数を定義。デバッグモードを設定してから、トレースオン。そして実行。

[sakae@fb /tmp/m4-1.4.19/examples]$ m4 debug.m4

debugmode(aeqc)

m4trace: -1- countdown -> `2 ifelse(eval(2 > 0), 1, `countdown(decr(2))', `Liftoff')'
m4trace: -1- countdown -> `1 ifelse(eval(1 > 0), 1, `countdown(decr(1))', `Liftoff')'
m4trace: -1- countdown -> `0 ifelse(eval(0 > 0), 1, `countdown(decr(0))', `Liftoff')'
2 1 0 Liftoff

debugmodeで設定してるフラグは、manに説明があった。

-d flags
        Set trace flags.  flags may hold the following:

        a       print macro arguments.
        c       print macro expansion over several lines.
        e       print result of macro expansion.
        f       print filename location.
        l       print line number.
        q       quote arguments and expansion with the current quotes.
        t       start with all macros traced.
        x       number macro expansions.
        V       turn on all options.

        By default, trace is set to "eq".

m4でも文字列の結合が出来ます。

[sakae@fb /tmp/m4-1.4.19/examples]$ cat join.m4
divert(`-1')
# join(sep, args) - join each non-empty ARG into a single
# string, with each element separated by SEP
define(`join',
`ifelse(`$#', `2', ``$2'',
  `ifelse(`$2', `', `', ``$2'_')$0(`$1', shift(shift($@)))')')
define(`_join',
`ifelse(`$#$2', `2', `',
  `ifelse(`$2', `', `', ``$1$2'')$0(`$1', shift(shift($@)))')')
# joinall(sep, args) - join each ARG, including empty ones,
# into a single string, with each element separated by SEP
define(`joinall', ``$2'_$0(`$1', shift($@))')
define(`_joinall',
`ifelse(`$#', `2', `', ``$1$3'$0(`$1', shift(shift($@)))')')
divert`'dnl

これが、簡単な定義だ。会話的な使い方の例。

[sakae@fb /tmp/m4-1.4.19/examples]$ m4
include(`join.m4')

join(__, hello, workd)
hello__workd
joinall(_, I, like, m4, too)
I_like_m4_too

無理にスクリプトにしてみる。

[sakae@fb /tmp/m4-1.4.19/examples]$ cat z.m4
#! /usr/bin/m4
include(join.m4)dnl
join(__, hello, workd)
joinall(=, I, like, m4, too)
joinall(_, I, like, m4, too)

こいつに、実行属性を付けておいて

[sakae@fb /tmp/m4-1.4.19/examples]$ ./z.m4
#! /usr/bin/m4
hello__workd
I=like=m4=too
I_like_m4_too

冒頭にゴミが出るのは、いかんともしがたい。何故か? 一行目の冒頭にある #! (いわゆるシェバング)をOSが検出。m4を起動するってのは、お約束。起動したm4は、最初の行(#! /usr/bin/m4)を出力。だって、m4は、スクリプト言語のコメント記号 # には頓着しないから、普通に出力するのさ。

m4は、includeだとかdefineだとか、極限られた語句を検出して、所定の働きをする(それ以外の入力は、出力にそのまま転写する)。そのうち一番大事なのが、define(a,b)ってやつ。書き換え規則を定義してるんだ。aって語句が現れたら、それをbに置き換えなさい。

bの部分は、単純な語句の他、いわゆる(m4に定義されてる)関数を使える。関数を使って、その場で語句を発生して置き換えるって事が出来る。関数には引数を渡せないと意味がない。a($1,$2)とか書いてあげると、書き換えが実行される。

もう一発、FreeBSDのm4ソース群の中のTEST/sqroot.m4

define(square_root,
        `ifelse(eval($1<0),1,negative-square-root,
                             `square_root_aux($1, 1, eval(($1+1)/2))')')
define(square_root_aux,
        `ifelse($3, $2, $3,
                $3, eval($1/$2), $3,
                `square_root_aux($1, $3, eval(($3+($1/$3))/2))')')
square_root(256)
16
square_root(2000000)
1414

大活躍してるifleseの説明をmanから拾っておく。

ifelse(a, b, yes, ...)
             If the first argument a matches the second argument b then
             ifelse() returns the third argument yes.  If the match fails
             the three arguments are discarded and the next three
             arguments are used until there is zero or one arguments
             left, either this last argument or NULL is returned if no
             other matches were found.

m4は、計算機です。CPPとは、出来が違うんです。過去に似たような事を言ってた首相がいたな。


This year's Index

Home