ラベル Makefile の投稿を表示しています。 すべての投稿を表示
ラベル Makefile の投稿を表示しています。 すべての投稿を表示

2013年7月17日水曜日

make が関数を展開するタイミング

2012年10月10日水曜日

LinuxカーネルのMakefileを解析する その8

前回からまたまた間があきました。

今回の話題は O= を付けた時の動作についてです。
ソースを見ていて、あれっ、どうやって動いてるんだっけ?という部分がありましたので記載しておきます。

例えば、Kernel のソースツリーで
$ make O=dir/to/store/output/files/ defconfig vmlinux
とやってみると、
ソースツリーを一切汚すことなく、
dir/to/store/output/files/ に出力結果を置いてくれます。

トップの Makefile の動きは 「LinuxカーネルのMakefileを解析する その1」で解説しました。

今回注目するのは script/Makefile.build です。
例えば、*.c から *.o を生成するサフィックスルールを見てみます。

Makefile.build の300行目過ぎにそれがあります。


# Built-in and composite module parts
$(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE
  $(call cmd,force_checksrc)
  $(call if_changed_rule,cc_o_c)


これは $(src)/%.c から $(obj)/%.o を生成してくれるルールを表しています。

Makefile.build の先頭に
src := $(obj)
がありますので、src と obj は同じディレクトリを指しています。
O= 指定がないときは、それでいいのですが、O= 指定ありの時は、 *.c と違うディレクトリに *.o を作りたいわけです。
obj と src が同じなのに、どうやって動いてるんだろう??としばらく悩みましたが、その答えはトップの Makefile の 150行目過ぎにある

VPATH := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))

export srctree objtree VPATH

にあります。

VPATHに $(srctree) が追加されています。

VPATHというのは、make がサフィックスルールにおいて検索するパスを指定するものです。
Cコンパイラでいうところの -I オプションみたいなものといういえばいいでしょうか。

$(obj)/*.o を生成するときに、 $(src)/*.c を探しますが、見つかりません。
次にVPATHで指定されたパスを探しにいき、$(srctree)/$(src)/*.c を使うわけです。

VPATHというのは、私は使わない方がいいと思っている機能なので(どのファイルをコンパイルしているかわかりにくくなる)、うっかり失念しておりました。

2012年10月9日火曜日

make 3.80 と make 3.81 の違い

経験的に以下のような書き方ができることは知っていたけど、
これが許されるのはGNU make version 3.81以降のようだ。


ifeq ($(FOO),a)
  $(warning FOO is a)
else ifeq ($(FOO),b)
  $(warning FOO is b)
else ifeq ($(FOO),c)
  $(warning FOO is c)
else ifeq ($(FOO),d)
  $(warning FOO is d)
endif



make version 3.80 の場合、上記は以下のようにエラーになってしまう。

Makefile:5: FOO is a
Makefile:6: `else' 疑似命令の後ろに無関係な文字列があります
Makefile:8: `else' 疑似命令の後ろに無関係な文字列があります
Makefile:8: *** 一つの条件部につき一つしか `else' を使えません。中止。




ポータビリティのことを考えるなら、以下のように書いた方がよさそうだ。

ifeq ($(FOO),a)
  $(warning FOO is a)
else
  ifeq ($(FOO),b)
    $(warning FOO is b)
  else
    ifeq ($(FOO),c)
      $(warning FOO is c)
    else
      ifeq ($(FOO),d)
        $(warning FOO is d)
      endif
    endif
  endif
endif

しかしこれだと、インデントがないと、わけがわからなくなるな。
ネストが深くなると大変そうだ。


case文みたく、重なりがないことが明らかな場合は以下のようにする方が見やすそうだ。


ifeq ($(FOO),a)
  $(warning FOO is a)
endif
ifeq ($(FOO),b)
  $(warning FOO is b)
endif
ifeq ($(FOO),c)
  $(warning FOO is c)
endif
ifeq ($(FOO),d)
  $(warning FOO is d)
endif

2012年8月1日水曜日

Linux ライクな autoconf.h を簡単に作る

Linux の Makefile においては、.config ファイルの内容から
include/config/auto.conf

include/generated/autoconf.h
が作られる。

前者はすべての Makefile から include され、
後者は -include include/generated/autoconf.h としてすべてのCのソースに渡される。

Makefile でも Cのソースでも、同じ設定内容を参照できるようにするというアイデアは素晴らしいと思う。

自前のソフトでも同様のことをしたいが、Linux の scripts/kconfig/conf を使うほどでもないという場合、以下のように make の簡単なコードでほぼ同等のことをできる。


Makefile の実装例:

include .config


auto.conf: .config
@ ($(foreach v, $(filter CONFIG_%, $(.VARIABLES)), \
$(if $($v), echo $v='$($v)';))) > $@


autoconf.h: .config
@ ($(foreach v, $(filter CONFIG_%, $(.VARIABLES)), \
$(if $($v), echo \#define $v $(if $(filter y,$($v)),1,'$($v)');))) > $@





例えば、
CONFIG_FOO=y
CONFIG_BAR="abcdef"
CONFIG_BUZ=0x100
# CONFIG_QUX is not set.

という内容の .config を食わせてみる。

実行結果は以下の通り。

$ make auto.conf
$ make autoconf.h
$ cat auto.conf
CONFIG_FOO=y
CONFIG_BUZ=0x100
CONFIG_BAR="abcdef"
$ cat autoconf.h 
#define CONFIG_FOO 1
#define CONFIG_BUZ 0x100
#define CONFIG_BAR "abcdef"



sed などで、 .config を parse してもよいのだが、make の .VARIABLES に定義済み変数が全部入っているので、そこから CONFIG_ で始まるものを抽出すれば簡単である。

.config 中で =y のものは、 autoconf.h では 1 に define される。

本家 Linux では =m のものは autoconf.h の方では (変数名)_MODULE が定義されるが、今回のサンプルには実装していない。

make の ifdef はわかりにくい

シェルにおいて
$ FOO=
とすると、シェル変数 FOO は空ではあるが、定義はされている。
変数 FOO を未定義にするには
$ unset FOO
としなければならない。
つまり、シェルスクリプトで、「変数が空」であることと、「変数が未定義」であることは明確に違う。

一方、make においてはこのあたりが紛らわしいというか、非常にわかりにくいと思うので、整理しておきたい。



make には ifdef という構文があるが、これの挙動を見てみる。

(実験1)
以下の内容の Makefile を用意する。


FOO=

all:
ifdef
@echo FOO is defined.
else
@echo FOO is NOT defined.
endif




実行すると以下のようになる。

$ make
FOO is NOT defined.



FOO=
のように、空文字を代入した場合、 ifdefFOO を未定義だと判定した。

なるほど〜、makeにおいては
空の変数は未定義の扱いになる。
そして、make には unset がないから、変数を未定義に戻すには、空文字を代入すればいいんだ、と理解したくなるかもしれない。
だが、これは違う。


(実行2)
次の内容の Makefile を用意する。


FOO=


FOO ?= 1
BAR ?= 1


all:
@echo FOO = $(FOO)
@echo BAR = $(BAR)

この実行結果は以下のようになる。
$ make

FOO =
BAR = 1


?= というのは、変数が未定義の時のみ、右辺の値を代入してくれる。
実験結果を見ると、
?=BAR は未定義と判定したが、FOO は定義済みと判定したことになる。


(実験3)
以下の内容の Makefile を用意する。

FOO=

all:
@echo $(filter FOO, $(.VARIABLES))

実行結果は
$ make
FOO

となる。
.VARIABLES という makeの変数には、使用中の変数が全部入っている。
大量の変数が入っているのだが、その中に FOO も含まれていることがわかる。
つまり、 .VARIABLES には中身が空の変数も含めて、定義済みの変数がリストされている。


(実験1)〜(実験3)からわかることは、
makeにおいても、「変数が空」であることと「変数が未定義」であることは区別している。
(ただ、シェルと違って、いったん定義された変数を未定義に戻すことはできない。)
?= は変数が「未定義」であれば右辺値が代入される。
注意しておきたいことは、(実験1)からわかるように、 ifdef は「定義されているか」を判定するのではなく、「空でないかどうか」を判定するものだ。
これは、ひじょーーーーーに紛らわしい。
この ifdef の仕様は失敗じゃないの?と思う。

つまり
ifdef FOO
ifndef ($(FOO),)
と同じなのだ。

だから、 ifdef 自体、別になくても構わないし、私は使わない。
ifndef ($(FOO),)
を使うほうが、誤解がないから。



2012年7月26日木曜日

Make のポータビリティについて考える

Makefile のポータビリティについて考えてみる。

Makefile 中では、 $(MAKE)$(AWK) など、変数で記述することがよく行われる。
これはポータビリティを考える上で当然である。

たとえば、Linux 上で普通 make といえば、GNU make のことを指す。
一方、Free BSDでは、make は GNU拡張されていない普通のmake のことであり、GNU makeのコマンド名は gmake だったりする。
そこで、make というコマンド名を直接 Makefile 中に埋め込むのではなく、 $(MAKE) という変数名で記述するわけだ。

今回、考えたいのは echo コマンドについて。Makefile 中でもよく使われている。
echo コマンドは環境によって、仕様にかなり違いがあり、シェルビルトインのechoもあるので、ポータビリティを考えだすと結構複雑だったりする。

例えば、bash を使っている人は

$ echo "hoge\npiyo"
hoge\npiyo
$ echo -e "hoge\npiyo"
hoge
piyo
$ type echo
echo はシェル組み込み関数です

となる。

一方、tcsh では

$ echo "hoge\npiyo"
hoge
piyo
$ echo -e "hoge\npiyo"
-e hoge
piyo
$ which echo
echo: シェルに入っているコマンドです.

となる。


最近の Ubuntu や Mint に標準で入っている dashの場合、

$ echo "hoge\npiyo"
hoge
piyo
$ echo -e "hoge\npiyo"
-e hoge
piyo
$ type echo
echo is a shell builtin


つまり、bash, tcsh, dash いずれもシェルビルトインのechoを持つが、bashビルトインのechoはデフォルトでは \n といったエスケープシーケンスを解釈してくれず、エスケープシーケンスを解釈させるためには -e オプションをつける必要がある。

一方、tcsh や dash のビルトインのecho はデフォルトでエスケープシーケンスを解釈してくれる代わりに、 -e オプションは解釈してくれない。

さらにシェルビルトインではない /bin/echo の仕様はプラットフォームごとに違うのだが、Linux であれば

$ /bin/echo "hoge\npiyo"
hoge\npiyo
$ /bin/echo -e "hoge\npiyo"
hoge
piyo

となり、bashビルトインのechoと同じような挙動をする。

このようにechoの仕様は結構違う。

そこで、Makefile の中で echo を使った場合、call される echo はどれなのか?
(ここでは GNU make についてのみ考える。)

これについては O'REILLY の「GNU Make」 の5章の冒頭に書かれている。
原著は以下で参照できる。

要約するとこういうことだ。
・make はコマンドを1行ごとに取り出し、サブシェルに渡して実行する。
・ただし、ワイルドカードやリダイレクションなどシェルの特殊文字が使われていない場合、高速化のためにサブシェルに渡さず、makeが直接コマンドを実行する。
・呼び出すサブシェルはデフォルトで /bin/sh であるが、make の変数 SHELL により変更できる。
・ただし、この変数は環境変数から引き継ぐものではない。make は環境変数を makeの変数として取り込むが、 SHELL だけは例外である。


本当にそうなっているか、以下の Makefile で実験してみる。


all1:
@echo --version
@type echo

all2:
@echo --version | cat
@type echo | cat

all3: SHELL=/bin/bash
all3:
@echo --version
@type echo



以下は Fedora 上で実行した結果である。

$ make all1
echo (GNU coreutils) 8.15
Copyright (C) 2012 Free Software Foundation, Inc.
ライセンス GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

作者 Brian Fox および Chet Ramey。
make: type: コマンドが見つかりませんでした
make: *** [all1] エラー 127
$ make all2
--version
echo はシェル組み込み関数です
$ make all3
--version
echo はシェル組み込み関数です



all1 は make が直接コマンドを実行しており、 /bin/echo が実行されている。
(そのため、 --version オプションを解釈している。)

all2  はパイプを使っている。ワイルドカードやリダイレクト、パイプなどを使っているとサブシェル /bin/sh の中で実行する。
Fedoraでは /bin/sh は bashへのシンボリックリンクになっているので、結局は bashビルトインのecho を実行している。(そして --version オプションを解釈しない

all3 は make 変数 SHELL の値を /bin/bash に書き換えているので、bash ビルトインのecho を実行している。

前述のように、make 変数の SHELL は環境変数の SHELL とは無関係だ。
SHELL だけは環境変数から引き継が仕様になっている理由は、ユーザーの使っているシェルがmakefileの動作に影響しないためだ。
例えば、tcsh をデフォルトにしているユーザーは 環境変数 SHELL/bin/tcsh になっているはずだが、だからといって、 makefile のルールを tcsh で実行するのはナンセンスだ。スクリプトを C shell系で記述することはほとんどないわけだから。

all1all2 の実行結果の違いを見てもわかるように、ワイルドカード、リダイレクト、パイプなどのシェルの特殊文字を使っているかどうかによって、サブシェルを呼ぶか、make が直接実行するかが変わる点は注目に値する。

make がサブシェルを呼ぶ場合、makefile 内で明示的に変数 SHELL を書きかえない限りは、 /bin/sh を呼ぶ。
Redhat 系ならば /bin/sh はbash へのシンボリックリンクだが、Ubuntu や Mint は dash へのシンボリックリンクになっているので、前述のように echo の動作が変わってくることになり、環境によっては期待通りに動いてくれないかもしれない。
環境によっては /bin/sh がシンボリックリンクではなく、本物の Bourne Shell なこともあるだろう。

対策として、 ECHO=/bin/echo のように変数を用意し、 $(ECHO) を使用することで、常に /bin/echo を実行するようにするということが考えられる。

サポートするビルド環境を、Linux などの単一のプラットフォームに限定するならば、これでもよい。
だが、 /bin/echo の仕様はプラットフォームごとに結構違うので、別のプラットフォームに移植するときに問題になり、ポータビリティには欠けると思う。

例えば、Linux の /bin/echo-e オプションを解釈するが、FreeBSDの /bin/echo は解釈しない。
また、echo の改行を抑制するためには、Linux や FreeBSD では -n オプションを使用するが、Solaris や HP-UX は
echo "hoge\c"
のように \c で改行を抑制するようだ。

そういうわけで、多様なプラットフォームへの対応しないといけない場合、
Makefile に
SHELL=/bin/bash
と書いておき、常にbashビルトインのechoを使わせるようにするのがいいのではないかと思った。
(本当にこれで大丈夫なのかは確認していないのですが。。)
ただし、その場合、make が直接コマンドを実行するという最適化は行われなくなる。

まあ、一番良いのは、あまりマニアックなオプションは使わないにこしたことはないです。

2012年7月14日土曜日

make のリスタートあり/なし その2


今回も make がリスタートする条件をもう少し調査してみる。
前回見たように、基本的にインクルードファイルが更新されればmakeは最初からやり直します。
(ただしインクルードファイルが.PHONY指定されている場合は除く。)

ちょっと面白い実験。次の Makefile を用意。


$(warning restart)
include foo.mk


all:
@echo $@


foo.mk: FORCE
touch -t 07010000 $@


.PHONY: FORCE
FORCE:



$ touch foo.mk 
$ ls -l
合計 4
-rw-rw-r-- 1 hoge hoge 109  7月 14 18:45 Makefile
-rw-rw-r-- 1 hoge hoge   0  7月 14 18:46 foo.mk
$ make
Makefile:1: restart
touch -t 07010000 foo.mk
Makefile:1: restart
touch -t 07010000 foo.mk
all
$ ls -l
合計 4
-rw-rw-r-- 1 hoge hoge 109  7月 14 18:45 Makefile
-rw-rw-r-- 1 hoge hoge   0  7月  1 00:00 foo.mk
$ make
Makefile:1: restart
touch -t 07010000 foo.mk
all


インクルードファイルの日付が変化すれば、たとえ古くなっていても make はリスタートするようだ。



では、日付が変化しさえすれば、make はリスタートするのかというと、必ずしもそうでもない。

次のような Makefile を用意。


$(warning restart)
include foo.mk


all:
@echo $@


foo.mk: bar
:


bar: baz
touch foo.mk



$ touch bar 
$ touch baz
$ make
Makefile:1: restart
touch foo.mk
:
Makefile:1: restart
touch foo.mk
all
$ make
Makefile:1: restart
touch foo.mk
all


bar, baz の順にtouch してから make すると、 bar の構築ルールのところで、 foo.mk  が更新される。
続いて、 foo.mk の構築ルール : が実行されたあと、make がリスタートする。
(このとき、 : が実行される理由は以前「make の動きを解剖する」参照)

さらにもう一回、makeを実行するときは : は実行されず、make のリスタートもない。
make がリスタートするためにはインクルードファイルの日付が変更され、かつそのインクルードファイルの構築ルールが実行される必要があるようだ。

だから、Makefile 中で foo.mk の構築ルールの : を削除した場合も make のリスタートは起こらない。

make のリスタートあり/なし


以前、「make に無前ループ防止機能?」に書いたとおり、以下の make は毎回 foo.mk が再構築されて、makeがリスタートする。




$(warning restarting)
include foo.mk


all:
    @echo $(BAR)


foo.mk: FORCE
    echo "BAR:=1" > $@


.PHONY: all FORCE
FORCE:






FORCE 指定を外せば、当然ながら foo.mk が存在しない場合にだけ、 foo.mk が構築されて、make がリスタートする。



$(warning restarting)
include foo.mk


all:
@echo BAR is $(BAR)


foo.mk:
echo "BAR:=1" > $@


.PHONY: all






$ make
Makefile:1: restarting
Makefile:2: foo.mk: そのようなファイルやディレクトリはありません
echo "BAR:=1" > foo.mk
Makefile:1: restarting
BAR is 1
$ make
Makefile:1: restarting
BAR is 1








foo.mk.PHONY 指定すると、リスタートしてくれないようだ。



$(warning restarting)
include foo.mk


all:
@echo BAR is $(BAR)


foo.mk:
echo "BAR:=1" > $@


.PHONY: all foo.mk


$ ls
Makefile
$ make
Makefile:1: restarting
Makefile:2: foo.mk: そのようなファイルやディレクトリはありません
echo "BAR:=1" > foo.mk
BAR is
$ ls
Makefile  foo.mk
$ make
Makefile:1: restarting
echo "BAR:=1" > foo.mk
BAR is 1



make とシンボリックリンク

make とシンボリックリンクはかなり相性が悪いと思っていて、自分は使用しないようにしている。

理解しておくべき基本的な事柄として、make は依存関係間で日付を比較するときに、シンボリックリンクそのものの時刻ではなくて、リンク先のファイルの日付を参照する。
touchコマンドもシンボリックリンク自体の日付を更新するのではなくて、リンク先のファイルの日付を更新する。

シンボリックリンクによって、コンパイル対象のファイルまたはディレクトリを切り替えている場合には、注意を要する。
リンク先が切り替わったとしても、必ずしも再コンパイルしてくれません。

例えば、 foo.cbar.c という2つのファイルがあったとして、どちらか一方へ、 source.c というシンボリックリンクを貼り、コンパイルする。

Makefile は以下のようだとする。


app: source.c
        gcc -o $@ $^



$ ls
Makefile  bar.c  foo.c
$ ln -s foo.c source.c
$ make
gcc -o app source.c
$ ls
Makefile  app  bar.c  foo.c  source.c
$ ln -sf bar.c source.c 
$ make
make: `app' は更新済みです


最初に、 foo.c へリンクを貼り、makeしています。
次に、 bar.c へリンクを貼り直しています。
この時点でシンボリックリンク source.c の日付は更新されますが、これは make は見ません。
bar.capp を比較し、app の方が新しいため、make は再コンパイルしてくれないわけです。

複数のソースディレクトリがあって、コンパイル対象を切り替えたい(例えば Linux Kernel の arch みたいに)ことはしばしばあるが、シンボリックリンクは使わないほうがよいと思う。

以前のLinux Kernelはmake中に include/asm から include/asm-(ARCH)へシンボリックリンクを張っていたが、今は廃止している。

組み込みのブートローダーとして有名な u-boot はいまだに、arch の選択にシンボリックリンクを張っているが、make の不具合が多いような気がする。
経験的に、defconfig で対象ボードを切り替える前に、いったんmrproper しないと危険です。

2012年7月2日月曜日

make の動きを解剖する その2

前回、makeが構築ルールを実行する時はどんなときなのか、調べてみました。
前回は、PHONYターゲットについては触れていませんでした。

今回は、それについて補足調査します。

用意するMakefile

all: a
    @echo

a: b
    @echo -n a

b: c
    @echo -n b

c:
    @echo -n c

$ touch c
$ touch b
$ touch a
$ make



これは、当然の結果ですね。何も構築する必要がありません。


次に c を PHONYターゲットにします。

all: a
    @echo

a: b
    @echo -n a

b: c
    @echo -n b

.PHONY: c
c:
    @echo -n c


$ touch c
$ touch b
$ touch a
$ make
cb


c を PHONY ターゲットにした場合、問答無用で c の構築ルールが実行されるだけでなく、 c を依存関係に引き連れている b の構築ルールも必ず実行されます。
ただし、 a の構築ルールまでは実行されていないことから、その上の階層までは伝搬しないようです。

make の動きを解剖する

make では ファイル間の依存関係を精査し、必要なオブジェクトのみを再構築するのが基本である。
ただし、PHONYターゲットのように、必ずしもファイル実体を伴わないルールもある。
それらの依存関係が複雑にからんだ場合、それぞれの構築ルールを実行すべきか否か、makeはどのように決定しているのかだろうか。

私は make 内部実装はいっさい見ていないが、いくつかの実験を行い、内部の動きを推論することにする。


(実験1)
次の内容の Makefile を用意。

all: a
    @echo

a: b
    @echo -n a

b: c
    @echo -n b

c:
    @echo -n c



ファイル a とファイル b の間の関係としては、次の5通りがある。
それぞれ make を実行したときの表示結果を示す。

(1) ab も存在しない → 実行結果 cba
(2) a だけが存在 → 実行結果 cba
(3) b だけが存在 → 実行結果 cba
(4) ab も存在し、 a の方が新しい → 実行結果 cb
(5) ab も存在し、 b の方が新しい → 実行結果 cba


この結果から推論できること:
(推論1)

a: b
    @echo -n a

という依存関係と構築ルールがあったとき、「ファイル a もファイル b も存在し、かつ a の方が新しい」(これを以下、条件1と呼ぶ)が成立すると、構築ルールを実行しない。
条件1が成立しないときは構築ルールを実行する。


では、条件1が成り立つかどうか評価するのは、どの時点なのだろうか?


(実験2)
次のMakefile を用意する。

all: a
    @echo

a: b
    @echo -n a

b: c
    @echo -n b
    @touch b

c:
    @echo -n c


条件1が成り立つ状態にしてから、このmakefileを実行してみる。
$ touch b
$ touch a
$ make
cba


(推論2)
この実験結果から、条件1が成り立つかどうか評価しているのは、 b の構築が終わったあとだと考えられる。
なぜならば、make を開始した時点では、条件1が成立している。
@touch b」を行った後で、条件1が成立しなくなる。
@echo -n a」が実行されているという結果を見れば、 ab の日付比較は直前に行われているはず。
これはmakeの動きとしては当然である。
依存関係ツリーの下位から順番に構築している中で、更新されるファイルがあるはずだからだ。


しかし、事態はそう簡単ではない。
推論2を否定する実験結果を示そう。

(実験3)

all: a
    @echo

a: b
    @echo -n a

b: c
    @echo -n b
    @touch b
    @touch a

c:
    @echo -n c



を実行してみる。

$ make
cba

a: b
    @echo -n a

の直前では、条件1が成立しているはずなのに「@echo -n a」が実行されている。
残念ながら、(推論2)は間違っているようだ。



では、(実験1)〜(実験3)をすべて説明できる推論として次を考える。

(推論3)
a: b
    @echo -n a

という依存関係と構築ルールがあったとき、「make開始時点のファイル a と、このルールを実行する直前のファイル b を比較する。両者とも存在し、 a の方が新しい」(これを以下、条件2と呼ぶ)が成立すると、構築ルールを実行しない。
それ以外の時は、構築ルールを実行する。

さて、この推論が正しいかどうか実験してみる。


(実験4)

all: a
    @echo

a: b
    @echo -n a

b: c
    @echo -n b
    @touch -t 201207021200 b
    @touch -t 201207021300 a

c:
    @echo -n c



$ touch -t 201207021159 a
$ make
cba
$ rm b
$ touch -t 201207021201 a
$ make
cb


make を実行前のファイルaの時刻が11時59分なら、条件2が成立しないので、「@echo a」を実行している。
一方、ファイルaの時刻が12時01分なら、条件2が成立するので、「@echo a」を実行しない。
Makefileの中で、ファイルaの時刻を13時に更新しているが、これは見ていない。
これは(推論3)を支持する結果である。


(実験5)

all: a
    @echo

a: b
    @echo -n a

b: c
    @echo -n b

c:
    @echo -n c
    @rm a



$ touch b
$ touch a
$ make
cb

途中で、ファイル a は消されているにもかかわらず、「@echo -n a」は実行されていない。
make開始時点で a が存在しているかどうかしか見ていないから。



実験5を書きかえて、以下のようにしてみる。

(実験6)

all: clean a
    @echo

a: b
    @echo -n a

b: c
    @echo -n b

c:
    @echo -n c

clean:
    rm -f a


$ touch b
$ touch a
$ make
rm -f a
cba


これは(推論3)が間違っていることを示す結果です。

(実験7)

all: touch a
    @echo

a: b
    @echo -n a

b: c
    @echo -n b

c:
    @echo -n c

touch:
    touch b
    touch a


$ make
touch b
touch a
cb


実験6, 実験7も説明できる推論として以下を考える。

(推論4)
a: b
    @echo -n a

という依存関係と構築ルールがあったとき、「この依存関係ツリーを処理する直前のファイル a と、このルールを実行する直前のファイル b を比較する。両者とも存在し、 a の方が新しい」が成立すると、構築ルールを実行しない。
それ以外の時は、構築ルールを実行する。


いろいろ実検してたが、makeが構築ルールを実行するかどうかの判断は結構複雑のようだ。
実装コードを見ずに、実検してみた限りでは、(推論4)のようになっていると思うのだが。。

2012年6月27日水曜日

Makefile での .PHONY と FORCE の違い

2012年6月14日木曜日

make に無限ループ防止機能?

以下の Makefile は無限ループになるだろう、と思っているのだが、実際にやってみるとならない場合もあるみたいだ。。


$(warning restarting)
include foo.mk

all:
    echo $(BAR)

foo.mk: FORCE
    echo "BAR:=1" > $@

.PHONY: all FORCE
FORCE:


教科書的には make の動作は以下のようになります:

make は include 命令に出会うと、そのインクルードファイルを読み込みます。
ただし、インクルードファイルがなくても、エラーを報告した上で処理を続行します。
makefile を読み終わった時点で、インクルードファイルを更新するルールがないか探します。
もしルールが見つかると、通常のターゲットの更新として処理します。
ルールによりインクルードファイルが更新されると、make を最初からやりなおします。

この例の場合、
include foo.mk
がありますので、 foo.mk を構築するルールがないか探します。

foo.mk: FORCE
    echo "BAR:=1" > $@

が見つかりました。

そこで make はまず FORCE を更新し(といってもPHONYターゲットなので何もできませんが)、続いて foo.mk を更新するために
echo "BAR:=1" > $@
を実行します。

foo.mk が更新されたので、 make を最初からやりなおします。

ですが、  foo.mk: FORCE という依存関係があるので、またまた foo.mk を更新すべきと判断します。で foo.mk を更新し、また make を最初からやりなおし、といったループを繰り返すわけです。

理屈的にはこうなはずなのですが、手元の Ubuntu 11.10 マシンでやってみたら以下のようになりました。

$ make
Makefile:1: restarting
echo "BAR:=1" > foo.mk
Makefile:1: restarting
echo "BAR:=1" > foo.mk
Makefile:1: restarting
echo "BAR:=1" > foo.mk
echo 1
1

リスタート3回で止まった。不思議!



もう一回やってみると

$ make
Makefile:1: restarting
echo "BAR:=1" > foo.mk
Makefile:1: restarting
echo "BAR:=1" > foo.mk
Makefile:1: restarting
echo "BAR:=1" > foo.mk
Makefile:1: restarting
echo "BAR:=1" > foo.mk
Makefile:1: restarting
echo "BAR:=1" > foo.mk
echo 1
1

となった。

何回か同じことを繰り返すと、無限ループに入ったぞ、と判断してやめるのかな?
リスタート回数は毎回違うんですけど。。

make のバージョンは

$ make --version
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.
これはフリーソフトウェアです. 利用許諾についてはソースを
ご覧ください.
商業性や特定の目的への適合性の如何に関わらず, 無保証です.

This program built for x86_64-pc-linux-gnu








一方、別の RHEL マシンでやってみると、無限に繰り返すようです。
make のバージョンは同じく 3.81 です。



$ make --version
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.
これはフリーソフトウェアです. 利用許諾についてはソースを
ご覧ください.
商業性や特定の目的への適合性の如何に関わらず, 無保証です.


This program built for x86_64-redhat-linux-gnu


このあたりの挙動はディストリビューションによって違うんでしょうか??
よくわかりません。

2012年6月12日火曜日

Makefile 変数のスコープ


Linux の Makefile には以下のような記述が出てきます。

clean: rm-dirs  := $(CLEAN_DIRS)
clean: rm-files := $(CLEAN_FILES)


(ターゲット): (代入文)

という記述方法ですが、これっていったいどういう機能を持つのでしょうか?


以下のような簡単な Makefile を書いて実験してみます。



foo := 1
all: bar := 1
sub: baz := 1


all: sub


all sub:
@ echo foo = $(foo) in $@
@ echo bar = $(bar) in $@
@ echo baz = $(baz) in $@


$(warning foo = $(foo) in warning)
$(warning bar = $(bar) in warning)
$(warning baz = $(baz) in warning)




$ make


を実行すると

Makefile:12: foo = 1 in warning
Makefile:13: bar =  in warning
Makefile:14: baz =  in warning
foo = 1 in sub
bar = 1 in sub
baz = 1 in sub
foo = 1 in all
bar = 1 in all
baz = in all


と表示されました。

$(warning ) 文からは barbaz の値が見えてないですし、
all の構築ルールの中では baz の値が見えてないですね。



また

$ make sub

を実行すると

Makefile:12: foo = 1 in warning
Makefile:13: bar =  in warning
Makefile:14: baz =  in warning
foo = 1 in sub
bar = in sub
baz = 1 in sub


sub の構築ルールの中では bar の値が見えないですね。


foo := 1 がグローバル変数的な振る舞いをするのに対して、

all: bar := 1 と書くと、all を構築している間だけこの代入文が見えます。
all の構築が終わると消えます。

sub: baz := 1 も同様。

ローカル変数的に使うことができますね。

2012年6月11日月曜日

LinuxカーネルのMakefileを解析する その7


前回から間があきましたが、今回はLinux Makefile のコンパイルオプションについて解析してみた。

Makefile 中でのコンパイルオプションの与え方は Documentation/kbuild/makefiles.txt の 「3.7 Compilation flags」に記載されている。
ざっと読んでみると、

ccflags-y, asflags-y, ldflags-y
これが記載されている Makefile で有効。
(EXTRA_CFLAGS, EXTRA_AFLAGS, EXTRA_LDFLAGS も過去互換のためにサポートされているが、非推奨)

subdir-ccflags-y, subdir-asflags-y
これが記載されているMakefileとそのサブディレクトリで有効。

CFLAGS_$@, AFLAGS_$@
はファイル単位で追加したいオプションを指定する。
例) CFLAGS_foo.o := -D BAR
CPPFLAGS_$@ はないみたい。

CFLAGS_REMOVE_$@
でファイル単位で削除したいオプションを指定する。
AFLAGS_REMOVE_$@, CPPFLAGS_REMOVE_$@ はないみたい。。



このあたりのオプションがどのように実装されているか、見てみた。


コンパイルオプションはほとんど scripts/Makefile.lib の中で加工されている。
scripts/Makefile.lib は scripts/Makefile.build からインクルードされている。


まず、EXTRA_* の過去互換性のためのルールがある。

# Backward compatibility
asflags-y  += $(EXTRA_AFLAGS)
ccflags-y  += $(EXTRA_CFLAGS)
cppflags-y += $(EXTRA_CPPFLAGS)
ldflags-y  += $(EXTRA_LDFLAGS)




また、サブディレクトリにも伝搬するように、
$(subdir-asflags-y), $(subdir-ccflags-y)
KBUILD_SUBDIR_ASFLAGS, KBUILD_SUBDIR_CCFLAGS にいれて export している。


# flags that take effect in sub directories
export KBUILD_SUBDIR_ASFLAGS := $(KBUILD_SUBDIR_ASFLAGS) $(subdir-asflags-y)
export KBUILD_SUBDIR_CCFLAGS := $(KBUILD_SUBDIR_CCFLAGS) $(subdir-ccflags-y)




以下の部分で、_c_flags, _a_flags, _cpp_flags にまとめます。
KBUILD_CPPFLAGS, KBUILD_CFLAGS, KBUILD_AFLAGS は トップのMakefileとarch/*/Makefileで規定されていて、
make 全体に影響を与えます。

orig_c_flags   = $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS) $(KBUILD_SUBDIR_CCFLAGS) \
                 $(ccflags-y) $(CFLAGS_$(basetarget).o)
_c_flags       = $(filter-out $(CFLAGS_REMOVE_$(basetarget).o), $(orig_c_flags))
_a_flags       = $(KBUILD_CPPFLAGS) $(KBUILD_AFLAGS) $(KBUILD_SUBDIR_ASFLAGS) \
                 $(asflags-y) $(AFLAGS_$(basetarget).o)
_cpp_flags     = $(KBUILD_CPPFLAGS) $(cppflags-y) $(CPPFLAGS_$(@F))



さらに、KBUILD_SRC が指定されている(つまり O=, M= が指定されている)ならば、インクルードパスの加工を行う。
KBUILD_SRC が指定されてなければ、 _*_flags がそのまま __*_flags になる。

ifeq ($(KBUILD_SRC),)
__c_flags = $(_c_flags)
__a_flags = $(_a_flags)
__cpp_flags     = $(_cpp_flags)
else


# -I$(obj) locates generated .h files
# $(call addtree,-I$(obj)) locates .h files in srctree, from generated .c files
#   and locates generated .h files
# FIXME: Replace both with specific CFLAGS* statements in the makefiles
__c_flags = $(call addtree,-I$(obj)) $(call flags,_c_flags)
__a_flags =                          $(call flags,_a_flags)
__cpp_flags     =                          $(call flags,_cpp_flags)
endif



これから c_flags, a_flags, cpp_flags, ld_flags が作られる。


c_flags        = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE)     \
$(__c_flags) $(modkern_cflags)                           \
-D"KBUILD_STR(s)=\#s" $(basename_flags) $(modname_flags)


a_flags        = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE)     \
$(__a_flags) $(modkern_aflags)


cpp_flags      = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE)     \
$(__cpp_flags)


ld_flags       = $(LDFLAGS) $(ldflags-y)


-Wp,-MD,$(depfile) は依存関係自動生成に使うプリプロセッサ用にオプションですね。




ようやく、これが scripts/Makefile.build のパターンルールの中で使われます。

%.c → %.o には

cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<


%.S → %.o には

cmd_as_o_S       = $(CC) $(a_flags) -c -o $@ $<


%.c → %.s には

cmd_cc_s_c       = $(CC) $(c_flags) -fverbose-asm -S -o $@ $<


%.c → %.i には

cmd_cc_i_c       = $(CPP) $(c_flags)   -o $@ $<


%.S → %.s には
cmd_as_s_S       = $(CPP) $(a_flags)   -o $@ $< 


%.lds.S → %.lds には
cmd_cpp_lds_S = $(CPP) $(cpp_flags) -P -C -U$(ARCH) \
                 -D__ASSEMBLY__ -DLINKER_SCRIPT -o $@ $<

という風に使われるようです。



ところで、cppflags-yEXTRA_CPPFLAGS って Makefile.lib には用意されているのですが、ほとんど使われている形跡がないですね。
-Dmacro-Idir などはプリプロセッサオプションなので、cppflags-y に追加すべきと思うのですが、もっぱら ccflags-y に方に追加されているようです。



もう一つ気になるのは用語の非統一感です。

ccflags-y, EXTRA_CFLAGS, CFLAGS_$@, CFLAGS_REMOVE_$@
 → なんで cflags-y じゃなくて ccflags-y にしたの?

asflags-y, AFLAGS_$@
 → なんで ASFLAGS_$@ じゃなくて AFLAGS_$@ なの?


make でよく使われるのは CFLAGS, CPPFLAGS, ASFLAGS, LDFLAGS だと思うんですが。。(make --print-data-base で確認)

2012年6月7日木曜日

make の -C オプションについて

make の -C オプションについて。

man make を読むと、

-C dir
makefile を読み込むなどの動作の前に、ディレクトリ dir に移動する。
(中略) このオプションは通常、make を再帰的に呼び出すときに使われる。

と書いてあります。

ほとんどの場面で 「make -C dir」は「cd dir; make」と同じと考えていいと思うが、
わずかに挙動が違う部分があることに最近気づいた。

実験のため、次のような /home/foo/Makefile, /home/foo/bar/Makefile
という2つの Makefile を用意。

/home/foo/Makefileの内容:

all:
    @ echo CURDIR = $(CURDIR)
    @ echo PWD = $(PWD)
    @ cd bar; $(MAKE)


/home/foo/bar/Makefileの内容:

all:
    @ echo CURDIR = $(CURDIR)
    @ echo PWD = $(PWD)


/home/foo にて make を実行してみると以下のように表示された。


CURDIR = /home/foo
PWD = /home/foo
make[1]: ディレクトリ `/home/foo/bar' に入ります
CURDIR = /home/foo/bar
PWD = /home/foo/bar
make[1]: ディレクトリ `/home/foo/bar' から出ます


さて、 cd bar; $(MAKE) の部分を $(MAKE) -C bar と書き変えるとどうだろう?


/home/foo/Makefileの内容:

all:
    @ echo CURDIR = $(CURDIR)
    @ echo PWD = $(PWD)
    @ $(MAKE) -c bar ← 変更する


/home/foo/bar/Makefileの内容:

all:
    @ echo CURDIR = $(CURDIR)
    @ echo PWD = $(PWD)




実行結果は以下のようになった。

CURDIR = /home/foo
PWD = /home/foo
make[1]: ディレクトリ `/home/foo/bar' に入ります
CURDIR = /home/foo/bar
PWD = /home/foo ← 注目!!!
make[1]: ディレクトリ `/home/foo/bar' から出ます


/home/foo/bar/Makefile における $(PWD) の値が変わった。

からくりはこうだ。

CURDIR は make が提供する変数で、現在実行している make プロセスのカレントディレクトリを返す。
一方、PWD は環境変数であって、make 変数ではない。

cd bar; make」で再帰的に make を呼び出した場合、
cd bar; の時点で PWD/home/foo/bar に更新される。

一方、「make -C bar」で再帰的に make を呼び出した場合、
環境変数 PWD/home/foo を指したまま、 /home/foo/bar/Makefile に引き継がれる。


このことから、make を実行しているカレントディレクトリを指す用途には、常に $(CURDIR) もしくは $(shell pwd) を使うべきだとわかる。
$(PWD) は使うべきでない。
上の例で明らかなように、上位階層の Makefile がどのように再帰的に呼び出しているかによって  $(PWD) が返す値が変わってくるので、必ずしも期待通りに動かないからだ。

ちなみに、変数がどこからやってきているかは

$ make --print-data-base
と打ってみればわかる。

やってみると以下のように表示された。

# makefile 変数
CURDIR := /home/masahiro

(中略)

# 環境変数
PWD = /home/masahiro


さらにちなみにだが、上の例で /home/foo/bar/Makefile$(PWD) の部分を $$PWD に書き変えるとまた違う結果になる。

make の構築ルールはサブシェル上で実行されることを知っている人は多いだろう。

echo PWD = $(PWD)
については、$(PWD) を make が展開した後にサブシェルに渡しているので、サブシェルは
echo PWD = /home/foo
を実行している。

一方、
echo PWD = $$PWD
については、make が $$$ に変換してサブシェルに渡すので、サブシェルは
echo PWD = $PWD
を実行することになる。$PWD はサブシェルによって /home/foo/bar へと展開される。