ときどきの雑記帖 RE* (新南口)
Amsterdam Compiler Kit がビルドできない問題の原因と解決策
アムステルダム
アムステルダムという名前はアムステル川のダムから来ているのだとか。 【0655・2355】さらば、アムステルダム | どーがレージ | NHKオンライン
さて。Amsterdam Compiler Kitをとりあえず動かす - Qiita で、Ubuntu の1604では正常にビルドできるのに1804では失敗するということなので調べてみた。
TL;DR
最新版では解決している(はずな)ので、githubから持ってきてそれをビルドすればよい。
どんなエラーが起きるのか
こんな感じ。 ただしたくさん出ているwarning は適当に削ってある。
CPROGRAM /tmp/ack-build/bin/em_m2
INSTALL /tmp/ack-build/staging/lib/ack/em_m2
INSTALL /tmp/ack-build/staging/share/man/man6/em_m2.6
INSTALL /tmp/ack-build/staging/share/ack/descr/pc86
INSTALL /tmp/ack-build/staging/share/ack/pc86/include/ack/config.h
INSTALL /tmp/ack-build/staging/share/ack/pc86/include/unistd.h
CPP /tmp/ack-build/obj/i86/mach/proto/as/comm2.y.m
INSTALL /tmp/ack-build/obj/plat/pc86//preprocessed-comm2.y
YACC /tmp/ack-build/obj/plat/pc86//y.tab.c
/tmp/ack-build/obj/plat/pc86//preprocessed-comm2.y: warning: 1 shift/reduce conflict [-Wconflicts-sr]
CC /tmp/ack-build/obj/i86//tmp/ack-build/obj/plat/pc86//y.tab.o
/tmp/ack-build/obj/plat/pc86//preprocessed-comm2.y:33:34: error: conflicting types for ‘__fsid_t’
typedef struct { int __val[2]; } __fsid_t;
^~~~~~~~
In file included from /usr/include/x86_64-linux-gnu/sys/types.h:29:0,
from /usr/include/stdlib.h:394,
from /tmp/ack-build/obj/plat/pc86//preprocessed-comm2.y:2:
/usr/include/x86_64-linux-gnu/bits/types.h:143:26: note: previous declaration of ‘__fsid_t’ was here
__STD_TYPE __FSID_T_TYPE __fsid_t; /* Type of file system IDs. */
^~~~~~~~
つまりは、__fsid_t という型が複数回定義されていると。
なぜエラーが起きるのか
複数回定義されているから。ではあるのだけど、ではなぜそんなことになったのか。 それは、上記のエラーが発生したファイル (y.tab.c) の元ファイル (comm2.y) が 一度Cのプリプロセッサを通されて preprocessed-comm2.y に変換されていて、 そのため多重インクルードを防ぐ目的でCコンパイラで一般的に使用されている インクルードガードをすり抜けて __fsid_t を定義しているファイルを インクルードしてしまったのが原因(と思われる)。
一度プリプロセッサを通した preprocessed-comm2.y の先頭部分はこんな感じで
%{
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
typedef unsigned char __u_char;
typedef unsigned short int __u_short;
typedef unsigned int __u_int;
typedef unsigned long int __u_long;
typedef signed char __int8_t;
typedef unsigned char __uint8_t;
typedef signed short int __int16_t;
typedef unsigned short int __uint16_t;
typedef signed int __int32_t;
typedef unsigned int __uint32_t;
typedef signed long int __int64_t;
typedef unsigned long int __uint64_t;
typedef long int __quad_t;
typedef unsigned long int __u_quad_t;
typedef long int __intmax_t;
typedef unsigned long int __uintmax_t;
typedef unsigned long int __dev_t;
typedef unsigned int __uid_t;
typedef unsigned int __gid_t;
typedef unsigned long int __ino_t;
typedef unsigned long int __ino64_t;
typedef unsigned int __mode_t;
typedef unsigned long int __nlink_t;
typedef long int __off_t;
typedef long int __off64_t;
typedef int __pid_t;
typedef struct { int __val[2]; } __fsid_t;
引用部最後の行が問題の __fsid_t。
今回エラーを引き起こしたこの __fsid_t は glibc ではメッセージにもある通り、 /usr/include/x86_64-linux-gnu/bits/types.h で定義されている。 このファイルは適当なインクルードファイルをインクルードしたときに 必要に応じてインクルードされるファイルなのだけれども、 coom2.y から潜って眺めていくと
mach/prooto/as/comm2.y にある
/*
* delay inclusion of machine dependent stuff (see comm0.h)
*/
#define _include #include
%{
#include "comm0.h"
#include "comm1.h"
mach/prooto/as/comm0.h
/* ========== Machine independent type declarations ========== */
#ifdef _include
_include <stdlib.h>
_include <stdio.h>
_include <string.h>
_include <ctype.h>
_include <signal.h>
#else
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#endif
#ifdef ASLD
#include "arch.h"
#endif
#include "out.h"
にある stdlib.h や、 別ルートでは h/out.h にある
/* $Id$ */
#ifndef __OUT_H_INCLUDED
#define __OUT_H_INCLUDED
#include <stdint.h>
stdint.hがインクルードしている。
out.h 経由でインクルードした方は展開結果のテキストが preprocessed-comm2.y に書き込まれ、 一方 _include として「遅延インクルード」される stdlib.h は yaccが変換した y.tab.c をCコンパイラがコンパイルするときに処理される。
なぜプリプロセッサを2回通すのかは面倒くさくなったので調べてない。
なぜ1804ではエラーが発生し1604では発生しなかったのか
#include <stdlib.h>
int main()
{
__fsid_t t;
}
というテストプログラムを1604と1804でコンパイルすると、 1804ではコンパイルできるが1604ではコンパイルエラーになる(らしい)。
1604で使われているglibcのバージョンは 2.23。 一方1804では2.27。それぞれの stdlib.h の先頭を見ると…
2.23
#ifndef _STDLIB_H
#ifdef __need_malloc_and_calloc
#define __Need_M_And_C
#endif
#ifndef _ISOMAC
# include <stddef.h>
#endif
#include <stdlib/stdlib.h>
/* Now define the internal interfaces. */
#if !defined __Need_M_And_C && !defined _ISOMAC
# include <sys/stat.h>
__BEGIN_DECLS
2.27
#ifndef _STDLIB_H
#ifndef _ISOMAC
# include <stddef.h>
#endif
#include <stdlib/stdlib.h>
/* Now define the internal interfaces. */
#if !defined _ISOMAC
# include <sys/stat.h>
__need_malloc_and_calloc 辺りのシンボル定義に絡んだ問題だろうか。 stdlib.h の更新履歴は sourceware.org Git - glibc.git/history - include/stdlib.h にあるが、面倒になって(ry
なお、__need_malloc_and_calloc が導入されたのはかなり初期の時点だった模様。
同様の事例が報告されていないか
__fsid_t ではないけれども、重複定義でエラーが起きていたことが Failed to compile in FreeBSD 10.3 i386 · Issue #1 · davidgiven/ack で報告されていた。それによると
Compile ack default branch in FreeBSD 10.3 i386 failed with following error:
CPP /tmp/ack-build/obj/i86/mach/proto/as/comm2.y.m
INSTALL /tmp/ack-build/obj/plat/pc86//preprocessed-comm2.y
YACC /tmp/ack-build/obj/plat/pc86//y.tab.c
yacc: 1 shift/reduce conflict.
CC /tmp/ack-build/obj/i86//tmp/ack-build/obj/plat/pc86//y.tab.o
/tmp/ack-build/obj/plat/pc86//preprocessed-comm2.y:96:3: error: conflicting types for '__mbstate_t'
} __mbstate_t;
^
In file included from /usr/local/lib/gcc48/gcc/i386-portbld-freebsd10.1/4.8.5/include-fixed/stdlib.h:47:0,
from /tmp/ack-build/obj/plat/pc86//preprocessed-comm2.y:2:
/usr/include/sys/_types.h:113:3: note: previous declaration of '__mbstate_t' was here
} __mbstate_t;
^
plat/pc86/build.mk:29: recipe for target '/tmp/ack-build/obj/i86//tmp/ack-build/obj/plat/pc86//y.tab.o' failed
gmake: *** [/tmp/ack-build/obj/i86//tmp/ack-build/obj/plat/pc86//y.tab.o] Error 1
__mbstate_t も単純な値型ではないので、重複インクルードしたらまあそうなるだろう。
I don’t get this error, but I find a similar problem with OpenBSD 6.0 amd64. The C compiler sees two copies of <machine/_types.h>.
This happens because we run cpp twice. We run cpp before yacc, and then cc runs cpp as usual when compiling y.tab.c from yacc. The source file mach/proto/as/comm2.y has #define _include #include. Any #include happens in the first cpp, but _include happens in the second cpp.
Now comm2.y does #include “comm0.h”, and mach/proto/as/comm0.h does #include <stdint.h> and #include “out.h”, and h/out.h has another #include <stdint.h>. So the first cpp enters <stdint.h>. But comm0.h also does _include <stdlib.h>. So the second cpp enters <stdlib.h>. In OpenBSD, both <stdint.h> and <stdlib.h> include <machine/_types.h>. So both the first cpp and the second cpp enter <machine/_types.h>.
The file obj/plat/linuxppc/as/yacc/yaccinput/comm2.y is the output of the first cpp. I can see that all typedefs from <machine/_types.h> and <stdint.h> have been copied to the yacc input. The #include <stdlib.h> is also in the yacc input. The files obj/plat/linuxppc/as/yacc/y.tab.[ch] are the yacc output. Again, I see both the typedefs and the #include in y.tab.c. So when we compile y.tab.c, the compiler sees two copies of <machine/_types.h>: one copy in y.tab.c and one included from <stdlib.h>.
OpenBSD declares __mbstate_t in <sys/_types.h>, but luckily for me, <stdint.h> never includes <sys/_types.h>, so I never get the error of two __mbstate_t declarations.
修正方法
最新版に合わせてmach/proto/comm0.h のout.h をインクルードしている辺りを
/* ========== Machine independent type declarations ========== */
#ifdef _include
#ifdef ASLD
_include "arch.h"
#endif
_include "out.h"
#else
#ifdef ASLD
#include "arch.h"
#endif
#include "out.h"
#endif
のようにすればOK。
ところで
(省略)
typedef long int __off_t;
typedef long int __off64_t;
typedef int __pid_t;
typedef struct { int __val[2]; } __fsid_t;
で、__fsid_t は当然として、ほかの __pid_t とか __off64_t がエラーになってないのは なんでなんすかね(予想はつくけど面倒なので規格に当たったりはしない)。