フレームワークとしての 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 で += の空白の処理が違う。

Solaris でメモリの利用量を記録する2016年08月24日 12時24分00秒

olaris C++ コンパイラがメモリ不足で落ちる時のエラーを調べるのに作ったスクリプト。
#!/bin/sh

CC=....

$CC $1 &

pid=$!
while ps -p `pgrep -p $pid` -o vsz
    do sleep 1
done | grep -v VSZ > $1.mem
CC にコンパイルオプションを渡しておき、$1 にてファイル名を指定している。ps -p で pid を指定して ps を行い、ps -o vsz にてメモリの利用量を表示する。

top でも見たり出来るが、記録をしづらい。また、表示プロセス数が多いと圏外にいってしまったりする。

前回

trap を用いて shell スクリプトが中断されたらファイルを消す2016年07月28日 11時11分04秒

シェルスクリプトで一時ファイルを生成した時に、途中で中断させるとファイルが残ってしまう。trap を用いて、中断された時にファイルを消すように出来る。
#!/bin/sh

TMPFILE=`basename $0`
TMPFILE=`mktemp -t $TMPFILE`
trap "$rm $TMPFILE; exit 1" HUP INT QUIT KILL TERM

echo temporary > $TMPFILE
sleep 600 
rm $TMPFILE

自己解凍・実行形式のシェルスクリプト2016年07月22日 11時45分08秒

シェルスクリプトを実行すると、自分で組み込まれたファイルを展開し実行する実装をしたことがある。一つのプログラムとしてまとめるのが困難で、いくつもの細かいプログラムを使って処理する必要があった。しかし、複数のファイルを各々インストールするのも手間が掛かりすぎるために、一つにまとめたのだ。

tar でファイルをまとめて、uuencode を使って文字に変換し、エディタで開いたりしても、問題ない形にする。実行するときは、自身に uudecode をかけて、まとめた分を展開し、処理を移行すれば良い。

-C を複数回使うと、付属の tar では動作が異なり GNU tar を使うのが一番移植性があった。二度目の -C が一度目からの相対位置になるか、プログラムが実行された位置を元にするのか等の違いがある。gtar は前回の位置を基準にする。

$ gtar cvf - program.pm -C ../../dir1 *.pm -C ../../dir2 *.pm | compress -c uuencode encoded.tar.Z >> driver.sh
driver.sh と言う名前のシェルスクリプトを使った。そして、driver.sh の方で行うのが展開作業。
mkdir $tmp
cat $0 | uudecode -o /dev/stdout | uncompress -c | ( cd $tmpdir ; tar xf - )

...
$tmp/program.pm
引数の処理などをしてから、$0 で自分自身を読み出す。uudecode は -o /dev/stdout の形がどの UNIX でも使えるようだ。そして、tar に入れておいた program.pm を呼び出して、プログラムの実行を移す。

Android から移した写真ファイルの日付を修正2016年06月10日 14時45分37秒

アンドロイドから Windows へと写真を移動した。NTFS のドライブ。全てでは無いようだが、ファイルの更新時刻が変わってしまっているのを見付けた。時間と日付で並べて、探すときもあるので若干不便。

調べてみると、アンドロイドで撮った写真は日付と時間の名前で保存されている。awk を使ってファイル名から touch の日付に変換して、system を使って変換。

% ls
20131229_145738.jpg
20131229_164025.jpg
20131229_175337.jpg
20131230_133658.jpg
20131230_134607.jpg
20140107_161406.jpg
% ls *_* | nawk '{f=$0;
  gsub(/(_|...jpg)/,"",$0);
  cmd=sprintf("touch -t %s %s\n", $0, f);
  system(cmd)}'

Purify でプログラムが起動しない2015年08月04日 11時10分12秒

Purify はオブジェクトファイルに検査の為のコードを埋め込み、実行時の各種問題の検査をしてくれる。プログラムのリンクの時間とプログラムの実行時間がとてつもなく遅いのが珠に傷なのだが、その検知力は他を追随しない。ゆっくりと時間を掛けてプログラムを生成し、時間に余裕をもってテストを行なう。

さて、久方に、Purify を使った。しかし、Purify を組み込んだプログラムが起動しない。エラーメッセージがいかの様に出る。

the two libraries might interfere with each other, leading to unpredictable results. This happens occasionally when you share the cache between multiple machines running the same version of the OS.
要約すると、「プログラムが同名の複数の実装のライブラリを組み込んでいる。」となる。

Purify を用いないとプログラムは起動する。Purify だと駄目。

ldd などを用いて詳しく調べると、libm が SunStudio とシステムの両方に参照されていた。プログラムは起動こそするものの、どちらの実装が使われるのかはそれこそ運次第。libm は数学系のライブラリなので枯れてはいるので、おそらく問題が表面化しないのだろう。

もし上記のエラーで困ったのだったら、-L にて指定されているライブラリのパスを調べたら良いだろう。