GNU make で現在のディレクトリ名を取得2017年05月17日 14時51分27秒

CURDIR が現在のディレクトリのフルパス持っているので、notdir で落す。
DIRNAME = $(notdir $(CURDIR))

フレームワークとしての make の二重コロン2017年04月12日 12時27分55秒

二重コロンは clean ターゲットのように、一つの動作を分けて書くこと以外にも、フレームワークとして使うことも出来る。make では include を使うことにより、他の make ファイルを読むことが出来る。

例えば、make check や make install といった、各々のターゲットを準備しておくと、make のデフォルトの動作として使うことが出来て、それぞれに敵した動作は後で追加出来る。

% cat uyota-framework.mk 
check ::
        true
install ::
        true
% cat Makefile 
target :
        @echo Hello
include uyota-framework.mk
% make install
true
% gmake install
true

最近は、Jenkins やその他の解析ツールなどで、特定のシェルスクリプトや make のターゲットを順次実行するようなフレームワークなどがあちこちで見られる。新規のものだったら、一つずつ追加なので良いが、既にあるものを大量に対応するのには手間と時間が掛かる。そんなときに、共通 make ファイルに追加することで、利用可能なターゲットから順次対応していく時などに、この方法が役に立つ。

make で一重コロンを用いて複数回動作を記述するのは間違っている2017年04月11日 12時01分47秒

make の二重コロンは動作を複数に分けて記述する事が出来る。同じことを一重コロンで行うと、make によって動作が変わるようだ。
% cat Makefile 
target :
    @echo Hello
target :
    @echo Goodbye
% make
make: "Makefile" line 4: warning: duplicate script for target "target" ignored
make: "Makefile" line 2: warning: using previous script for "target" defined here
Hello
% gmake
Makefile:4: warning: overriding recipe for target 'target'
Makefile:2: warning: ignoring old recipe for target 'target'
Goodbye
まあ、元々間違った記述なわけで、どちらの実装が正しいとかはないだろう。BSD make は最初のターゲットを、GNU make は最後のターゲットを実行している。

ただし、どちらともエラーとして動作を中断していない。つまり、この間違った記述をしてしまうと、特定の環境では意図した動作をするのに、移植しようとしたら突如変な動作をするようになる典型である。

make で二重コロンと一重コロンは共存できない2017年04月09日 14時09分06秒

二重コロンを用いて複数回動作を記述できるので便利なようだが、「二重コロンと一重コロンは共存できない」という制約がある。
% cat Makefile 
target :
    @echo Hello
target ::
    @echo Goodbye
% make
make: "Makefile" line 3: Inconsistent operator for target
make: "Makefile" line 4: warning: duplicate script for target "target" ignored
make: "Makefile" line 2: warning: using previous script for "target" defined here
make: Fatal errors encountered -- cannot continue
% gmake
Makefile:3: *** target file 'target' has both : and :: entries.  Stop

全ての make ファイルが自分の支配下にあれば、全て二重コロンにしてしまえば問題はないのだが、他人の make ファイルを基にしたルールを記述していると、この問題は簡単には回避できない。このターゲットを二重コロンにしてくれと頼んで変えてもらえたら何とかなるが、拒否されたら他の名前のターゲットを使うくらいしか回避策はない。

make で二重コロンを用いて複数回動作を記述する2017年04月07日 11時05分31秒

make ファイルに依存関係を記し、その条件を満たすときの動作を記述するにはコロンを用いる。なお、以下の実行環境は FreeBSD 11.0-RELEASE だが、SunOS 等に入っている古い make から GNU make まで、共通の動作だ。
% cat Makefile 
target :
    @echo Hello
% make
Hello
% gmake
Hello
なお、echo の前の「@」は実行時のコマンドの出力を抑制する。まあ、echo を二回しても無駄だと言うわけだ。

実は、コロンを二回使って、ダブルコロンでも動作する。

% cat Makefile 
target ::
    @echo Hello
% make
Hello
% gmake
Hello

さて何が違うかと言うと、一重コロンでは、依存関係と実行動作を一度のみ記述する規則になっている。つまり、同じターゲットを二回書いたら間違えた書式と言うことになる。例えば、.cpp ファイルから .o ファイルを生成するのに、一つは g++ を使うように書かれていて、もう一つ同じルールで llvm を使うように書かれていた場合だ。make 側はどちらのコンパイラを使って欲しいのか、記述者の意図は全く分からない。コンパイラだけでなく、コンパイラオプションなどにも同様の事が言える。

それに引き換え、二重コロンは同じターゲットを複数回に分けて記述する規則になっている。一つのターゲットを意図的に、複数回に分けて記述するのだ。良くある例としては、clean ターゲット等があげられる。コンパイル時の生成物の .o を消すコマンド、特定のライブラリや実行ファイルを各々消すコマンドなどは分けて記述すると可読性も増す。

なお、複数の二重コロンは記述された順番に実行される。

% cat Makefile 
target ::
    @echo Hello
target ::
    @echo Goodbye
% make
Hello
Goodbye
% gmake
Hello
Goodbye

若干注意点があるので、何回かに分けて書く。

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 などで、探すときに一致させやすいのも利点になる。