GNU make で外部からの変数の追加2016年09月30日 12時17分16秒

Make では += 演算子を使って値を追加できる。そして、引数や環境変数から変数を変更する事は出来るが、追加することは出来ないようだ。特に、ここからは GNU make に関する動作について調べてみた。

Makefile では単純に二つの変数を表示する。

% cat Makefile 
LIST1 = A
LIST2 += A

print :
        @echo $(LIST1)
        @echo $(LIST2)
% gmake
A
A
そして、そのままでは A が表示される。

今度は、引数から値を変更してみる。

% gmake LIST1=B LIST2=B
B
B
% gmake LIST1+=B LIST2+=B
B
B
+= も = と同じ結果になる。Makefile 内の LIST2 += A が無効になっていて、引数からの LIST2+=B も変数に追加になっていないのが分かる。Makefile 内の変数の処理が終った後にコマンドラインのものが処理されて、上書きされている様な動作だ。

今度は環境変数を通して値を設定するとまた動作が変わる。ここでは csh 系を使っているので、env を指定している。sh 系では env は必要無い。

% env LIST1=B LIST2=B gmake
A
B A
環境変数だと、gmake 実行する前に値が設定されて、それを Makefile 内の記述が変更するようになる。LIST1 は = なので、A に変わり、LIST2 は += なので、A が追加されている。

大概のシェルだと += で環境変数を追加することは出来ない。構文エラーにこそされないものの、B の影は無い。

% env LIST1+=B LIST2+=B gmake
A
A
ところが、bash には += 演算子があるのでこうなる
% bash
bash% LIST1+=B LIST2+=B gmake
A
B A
bash% LIST1+=B LIST2+=B LIST2+=C gmake
A
BC A
なお、make と bash で += の空白の処理が違う。

.PHONY ターゲットの Makefile2015年08月02日 11時44分15秒

GNU Makefile で include で他の Makefile を読み込むことが出来る。もし、その Makefile を生成するルールが存在すると、再度 Makefile が生成された場合に、その Makefile は再評価されターゲットやルールなどが更新される。

しかし、この Makefile が .PHONY ターゲットだと、ファイルは更新されるが Make のルールの更新が行なわれない。症状としては、なぜ正しくルールが記述されているのに、実行されないのだろうとなる。

BSD Make の -V で変数を表示2014年07月02日 14時15分42秒

make を使っている時に特定の変数が何になっているか知りたいときがある。あちらこちらから環境変数を持ってきたり、沢山の条件式があったり、再帰的に変数が設定されていると、探すのが大変だ。

BSD make では -V 変数 を用いて、make のターゲットを作成しないで、変数の値のみを表示できる。-V オプションは一つの変数毎に必要になる。

FreeBSD のコードを元にした例。


% make -C /usr/src -f Makefile.inc1 -V MACHINE_ARCH -V TARGET_ARCH -V KNOWN_ARCHES
i386
i386
amd64 arm i386 i386/pc98 ia64 mips powerpc sparc64 sparc64/sun4v

変数は各一行毎に表示されている。

更に -X を付けると、再帰的な値の展開が抑制される。


% make -C /usr/src -f Makefile.inc1 -V MACHINE_ARCH -V TARGET_ARCH -XV KNOWN_ARCHES
i386
${MACHINE_ARCH}
amd64 arm i386 i386/pc98 ia64 mips powerpc sparc64 sparc64/sun4v

TARGET_ARCH は MACHINE_ARCH から導き出されているのが分かる。

今度は MACHINE_ARCH をコマンドラインから上書きしてみる。


% make -C /usr/src -f Makefile.inc1 -V MACHINE_ARCH -V TARGET_ARCH -V KNOWN_ARCHES TARGET_ARCH=dummy
"/usr/src/Makefile.inc1", line 138: Unknown target dummy:dummy.
% make -C /usr/src -f Makefile.inc1 -V MACHINE_ARCH -V TARGET_ARCH -V KNOWN_ARCHES TARGET_ARCH=mips
i386
mips
amd64 arm i386 i386/pc98 ia64 mips powerpc sparc64 sparc64/sun4v

業と、dummy に設定したためにエラーが出た。KNOWN_ARCHES の中から選べばエラーにならない。

GNU Make をシェルスクリプトに変換2013年12月06日 12時42分04秒

Make が使えない環境があったので、仕方がなくシェルスクリプトに変換。

Make を使うのは依存関係の解決の為。問題なくいけば全てを一度ずつ実行すればいいのでこれでも十分実用になる。


% gmake -B -n TMP='$${TMP}' | \
  sed -e '/^gmake\[[0-9].*\]/d' -e 's@$@ || exit 1@' > Makefile.sh

GNU Make を -B を用いて全ての手順を強制実行。しかし、-n を用いて、実際には何もせずコマンドを出力するだけ。GNU Make は再帰呼び出し等があるとディレクトリ等の情報を出力するので、それを sed で除去。そして、エラーがあったら停止したいので全ての行末に戻り値が 0 で無いときに exit を呼ぶ。

TMP は Make の変数をシェルスクリプトからでも変更できるように小細工。Make の変数にシェルの変数を代入する。Make 内で $ をエスケープする必要があるので、二つ渡すことになる。シェルスクリプトを実行するときに、% TMP=/tmp/data sh Makefile.sh 等のように、実行時に指定できる様になる。

Make の利点は依存関係の解決。コンパイルをするだけでなく、複雑な依存関係を持ったデータの移行処理等でも、とても有効に使える。

十以上の異なったシステムとプログラムからデータを一時吐かせて、何度も処理をして新しいデータ構造に再処理するプログラムを書いたことがある。途中で出来るデータにも依存関係があって、正しい順番に処理をしていかないといけない。自分でその手順を一気に処理するプログラムを書いてしまうと、問題があったときにどの段階で問題が起きているのか掴みづらい。また、後になって新しい依存関係が発覚して、あれこれとプログラムを書き換えるもの容易ではない。それこそ見事なスパゲッティができあがってくる。Make を用いて、各々の処理を書き、それを点とする。そして、各々の入力の依存関係を羅列、つまり点を繋いでいくことにより、Make が作業点の集まりから作業の流れを作ってくれる。これで、上手なマカロニが調理できる。問題があったとしても、全ての工程の入出力が残り、各々の処理も単純なものを繰り返しなので追いやすい。また、開発とテストも一つ一つの作業に注視すれば良いだけなので、進みも早い。

GNU Make で未定義を設定2013年09月22日 14時05分18秒

GNU Make で意図的に未定義を定義するのは若干骨が折れる。そもそも、未定義とは定義されていない事なのに、わざわざそれを代入しなければいけないのだから本末転倒なぐらいだ。

しかし、他人の権限下に問題があって、手が出せないこともある。VAR ?= on で未設定の時に自動で代入し、しかもその条件式が、未定義以外の時に有効になるようにされていたらもう他に手段が無い。変数に未定義だと代入しておく必要がある。

まず、よく一番やってしまう例がこれ。


% cat var.mk
VAR=''

ifdef VAR
var :
        echo "$(VAR)"x
endif

エラーが判りやすいように、ifdef で囲んである。

gmake -f var.mk
echo "''"x
''x

カンマが二つ出力されてしまう。

合格はこちら。


% cat var.mk
VAR=

ifdef VAR
var :
        echo "$(VAR)"x
endif

定義されていないので、ターゲットが見付からない。なお、Make の仕様で = の後の空白やタブは無視されるので、それらが入っていても問題はない。

gmake -f var.mk
gmake: *** No targets.  Stop.

しかし、お薦めはこちら。


% cat var.mk
VAR=#

ifdef VAR
var :
        echo "$(VAR)"x
endif

# を入れることで、その後ろはコメントとして無視される。もちろん出力はターゲットが見付からないと出る。

gmake -f var.mk
gmake: *** No targets.  Stop.

何故この形かというと、理由は二つある。何も目に見えるものだけが、真実とは限らないのだ。エディタでは非表示の文字を入れた者がいて、未定義のはずなのにとの混乱があったからだ。もう一つは、grep などで、探すときに一致させやすいのも利点になる。

Prevent Multiple Include in GNU Make2013年09月20日 12時50分03秒

GNU Make は随分と元の Make に手が加わっていて全く別物になっている。変数に値を設定する方法もいくつもあって、かなりややこしい。GNU Make を使いこなすには include が肝になる。

さて、この include だが、いわゆる C/C++ のヘッダーファイルの様に、無造作に読み込みたいが、複数回読み込むと問題が起きる。まず、変数に += を使っていると、無限に増えていくこと、また開けるファイルに上限があるなど、悩ましい。

GNU Make には条件式もあるのだが、この動作がややこしく、さらになやましいものにしている。なお、条件には ifndef の様に定義の有無を調べるものと、infeq の様に変数の値を調べる物がある。

そして、実験。四つのファイルを準備して、少しずつ違う式を書く。二つは ifndef で、あとのものは infeq で。そして、各々で :== を使う。:= は、読み込んでいる最中に評価され、読み込まれた時点で即刻、値が代入される。= の値の代入は遅延型で、全てのファイルを読み込んだ後になる。大きな違いは、VAR = $(VAR) 等と、自身に代入できないことだ。これは無限ループになり値を決められない。


% cat ifndef:=.mk
ifndef INCLUDED
INCLUDED := 1

include ifndef:=.mk

endif
% cat ifndef=.mk
ifndef INCLUDED
INCLUDED = 1

include ifndef=.mk

endif
% cat ifneq:=.mk
ifneq ( $INCLUDED, 1 )
INCLUDED := 1

include ifneq:=.mk

endif
% cat ifneq=.mk
ifneq ( $INCLUDED, 1 )
INCLUDED = 1

include ifneq=.mk

endif

さて、この四つの場合の各々の意図は汲めたであろうか。なるべく判りやすい様に、ファイルにそのままの名前を付けてみた。

同じ順に実行した結果はこうなる。


% gmake -f ifndef:=.mk
gmake: *** No targets.  Stop.
% gmake -f ifndef=.mk
gmake: *** No targets.  Stop.
% gmake -f ifneq=.mk
ifneq=.mk:4: ifneq=.mk: Too many open files
gmake: *** No targets.  Stop.
% gmake -f ifneq:=.mk
ifneq:=.mk:4: ifneq:=.mk: Too many open files
gmake: *** No targets.  Stop.

ifndef で既に定義されているかだと、一度しか読み込まれない。そのため、ターゲットが無く終了している。ifneq で変数の値を調べようとしたが、読み込んでいる最中はどちらの代入式でも効果が無いようだ。そして、ファイルが開けなくなって終了している。

つまり、C/C++ の様に複数回読み込まれるのを防ぐには ifndef を用いる必要がある。


ifndef INCLUDED
INCLUDED = 1

include ifndef=.mk

endif

実はがっかりした結果だった。ifneq を用いると、一回目に読み込まれる動作と二回目に読み込まれる動作を変えられるかと期待したが、狸の皮算用だった。

大量のファイルを同時に圧縮する2012年03月25日 11時43分36秒

時折、大量のファイルを圧縮することがある。一つずつのファイルの大きさは、数 MB から数 GB まで、多種多様。そんなファイルが数百にも上る。Solaris など、特にコア数が多いホスト上の時は、手っ取り早く並列処理したい。CPU リソースは余っている事が多いので、100 以上あるコアのうち、20、30 ぐらい使ったところで気がつく人はほとんどおらず、苦情も来ない。Solaris ほどコアの数があると、CPU を全てしっかり使い切るのは結構難しい。とは言ったものの nice 20 で処理しても、流石に一人で全ての CPU を 100% 使うのは気が引ける。

実は、大きさの異なる仕事を並列的に処理するスクリプトを書くのはとても難しい。シェルスクリプトを筆頭に、特定の数だけ並列化するライブラリがない。そうなると、自分で子プロセスを生成し、管理することになる。それに、各々の仕事量が異なっているので、仕事の割り振りも工夫が必要になる。一つだけ、大きい仕事ばかり抱えて、他のプロセスが直ぐに終わってしまったのでは、効果が下がる。そう考えていくと、ささっと書いて使い捨てられるほど、簡単にはいかない。

いくつかそのようなスクリプトの尻拭いをしなければいけないことがあったが、どれも醜いものだった。コード数が不必要に長い。余計な処理ばかり増えて、本題の作業効率が余り上がっていない。負荷が偏っていて、無駄が多い。どれも一生懸命、書いたのは見て取れるのだが、直す人の気苦労などまったくわかっちゃいない。

実は、その様に百行から数百行にわたる、たった一行のプログラムで並列処理の仕事配分をしてくれるプログラムがある。その名も make。make だけだとちょっと扱いづらいので awk も当てる。

残念ながら、make にコマンドラインから直接 make ルールを打ち込む人たち等は見たことがない。make をシェルスクリプトの様に、手順を追って書く人たちや、make に依存関係の処理を任せれば簡単なのに、スクリプトで難解な発明をし、四苦八苦している人たちはよく見かけるが。

さて、愚痴と能書きが多くなったがさて本題。make での古いやつだと並列処理が出来ないものもあるので、GNU make や BSD make など若干新しいものが必要になる。


% cat bzip20
#!/bin/sh

exec $* | \
  nawk 'BEGIN{ print "ALL:" }
    { print $1 ".bz2 : " $1;
    print "\tbzip2 " $1;
    print "ALL : " $1 ".bz2" }' | \
  gmake -j 20 -f -
% bzip20 "ls *.txt"
% bzip20 "find . -name '*.log'"

exec を通して、ファイルを探し、それらのファイルを圧縮するターゲットを作る。ALL を一番最初にしているので、全てが圧縮される。exec を通しているので、圧縮順を気にせず適当に渡したり、逆に大きさ順に並べて、極力処理が偏らないように出来る。

GNU make の -include2012年03月04日 03時22分32秒

GNU make には -include がある。include は常にファイルを読み込み、ファイルが存在しない場合はエラーになる。-include はファイルが存在する場合に読み込む。

この、条件式による読み込みが他の make には無い特有の動作になっている。これを用いる事により、動的に makefile の内容を変更させる時に幅を持たせることが容易になる。また、複数に分かれて書かれている makefile を複合して呼び出すことも可能になる。

単純な動作を組み合わせて例示する。


% cat Makefile
-include $(FILES)

まず、Makefile は一行のみ。

% head 1.mk 2.mk 3.mk 
==> 1.mk <==
all ::
        echo 1

==> 2.mk <==
all ::
        echo 2

==> 3.mk <==
all ::
        echo 3

そして、makefile を三つ準備した。判りやすい様にファイルの名前は、表示する数字と同じになっている。更に重要な点は :: を用いてターゲットを作成している。二重コロンを用いることにより、同じ名前の複数のターゲットを別々の位置で定義できるようになる。

gmake への引数を変えることにより、任意の makefile を実行することが出来る。


% gmake FILES=1.mk
echo 1
1
% gmake FILES='3.mk 1.mk'
echo 3
3
echo 1
1

また、同じファイルを複数回読むことも出来る。

% gmake FILES='3.mk 1.mk 2.mk 1.mk'
echo 3
3
echo 1
1
echo 2
2
echo 1
1

これを応用すると、任意のコンパイラを使いたいときや、特定のライブラリだけ一時的に入れ換えたい時などに、自動で組み込むような makefile を作れる。

GNU make での変数の値を出力2011年02月07日 20時02分27秒

BSD make には -V オプションがある。これは引数を取り、ターゲットを実行する変わりに変数を出力して終わる。

% make -V CFLAGS
-O2 -pipe

しかし、GNU make には似たようなオプションは見当たらない。-p を付けると全ての変数を出してくれるが。

そこで、簡単な make ファイルを stdin を通して渡すことで、BSD make の -V 相当を行なってみた。まずは、元になる Makefile から。


% cat Makefile
VAR = var
VAR = rav

a :
        echo ${VAR}

ごく単純にしてある。そこで、echo を用いる。

% echo -e 'echo ::\n\t@echo ${VAR}\ninclude Makefile' | gmake -
echo rav
rav

この echo は -e を受け取ってタブや改行を処理できるものでなければ動かない。しっかり、最後の値の rav を表示している。

gmake -p を grep したものと比べてみる。


% gmake -p | grep VAR
VAR = rav
.VARIABLES := 
        echo ${VAR}

GNU make の変数が設定される場所を探す2011年01月26日 17時58分15秒

以前に make ファイルを一括で grep にて、nawk と grep を使って行なったが、--print-data-base を使っても同じ情報が手に入る。

% gmake -p