GNU Make / M4 の基本的なTips、n選

  • 6
    Like
  • 0
    Comment

はじめに

インフラのちょこっとしたインストールの仕組み(ChefとかPuppetとか持ち出すレベルではない構成管理)をGNU MakeとGNU M4で構築することがままあったので、その備忘録とメモです。

※小規模なインフラソフトウェアのインストール・デプロイ目的で使っているので、ソースコードのビルドなどは考えておりません。

GNU Make

  • ビルドツールですが、色々な自動化に使えます
  • 大抵の環境にデフォルトで入っている(Windowsではcygwin)ので、汎用性高いです
  • 3.81以降のバージョンを想定

参考URL

Makefileの基本的な文法

# コメント、#以降がコメント

# ここからひとつのルール
# `:`より前はターゲットという
target:
    @echo 'target body' # コマンド行
# ここまでひとつのルール

よく使う文法

defaultターゲット

特にターゲットを指定せずにコマンドラインからmakeを呼び出した際に、実行されるターゲット。

default: install

こう書いておくとデフォルトでinstallターゲットが実行される。

.PHONYターゲット

ターゲットは基本ファイルとして存在するものとして扱われる。そうであってほしくない場合(ファイルとして存在しないターゲットを作る場合)に、.PHONYターゲットを使う。

.PHONY: clean
clean:
    @echo I am clean target

cleanというファイルがあっても気にしなくなる。

アーキテクチャチェック

開発マシンのOSXと運用環境のLinuxで動作を変えたいことがあると思います。その場合下記のようにします。

arch=$(shell uname -s)
check-arch:
ifeq ($(arch),Linux) # インデントは必要ない
    @echo On Linux!
else ifeq ($(arch),Darwin)
    @echo On OSX!
else
    @echo On unknown OS!
endif

ファイルの存在チェック

あるファイルが存在するかどうかで分岐したい場合は次のようにします。

TMP_FILE=tmp.file
check-wildcard:
ifeq ($(wildcard $(TMP_FILE)),) # wildcard関数は受け取ったパターンを実際のファイル名として展開する、その際該当するファイルが無ければ空になる
    $(error $(M4SCRIPT) not found)
else
    @echo $(M4SCRIPT) is exist!
endif

testコマンドの実行結果で後のコマンドを実行させる

あるディレクトリがない場合は作る、みたいな条件判定させたい場合には下記のコードが便利です。

check-if:
    touch $(TMP_FILE)
    -@[[ -e $(TMP_FILE) ]] && echo $(TMP_FILE) is exist! # 最初のtestコマンドの実行結果が`0`なので、後のechoが実行される
    rm -f $(TMP_FILE)
    -@[[ -e $(TMP_FILE) ]] || echo $(TMP_FILE) is not found! # 最初のtestコマンドの実行結果が`1`なので、後のechoが実行される

別のMakefileを読みこませる

Makefileをパラメトリックにしたい場合、パラメーターの部分を外部ファイルに分離したいときがあります。そのような場合、includeディレクティブで外部のファイルを読み込むことができます。

include other_makefile

M4

  • テキストマクロプロセッサです
  • ソースコード中の特定のシンボルを置き換えたり出来ます
  • 使い過ぎるとメンテナンス不可能になり周囲の人間を絶望の淵に叩きこむので、要注意です(自戒)

参考URL

M4の基本的な文法

dnl以降がコメントになります。Discard to Next Line、次の行まで捨てる、の略らしいです。

dnl なんで、改行とか表示しないためにも、ディレクティブ(以降出てくるdefineとか)行に関しては行末にdnlを置く必要があります。

defineで置換マクロを定義できます。 下記のdefineで、M4_BASE_URLというマクロを定義し、それがm4の実行によってmk2.redという文字に置換されます。

define(`M4_BASE_URL',`mk2.red')dnl

`'でくくるとクオートされていることになり、その中に含まれる別のマクロは最初置換されません。
dnl M4_DEV_URLは、M4_BASE_URLとして置換されます。が、M4_BASE_URLはすでにmk2.redとして定義されているので、実際にはmk2.redが結果として残ります。つまり、置換可能な文字列が無くなるまで置換し続けます。

define(`M4_DEV_URL',`M4_BASE_URL')dnl 
dnl なので、こいつは置換させたくない、という場合は、二重のクオートを使用します。
define(`M4_STAGING_URL',``M4_BASE_URL'')dnl
dnl 対して`'で囲わないと、その部分がさっくり置換されます。
define(`M4_prod_URL',M4_BASE_URL`/prod')dnl mk2.red/prodになる

びゃーっと書いてしまったのですが、マクロ置換のプロセスだけまとめると(ただこのまとめは僕の感覚的なもので、実際のm4のソースコードを読んで理解したわけではないです)

  1. 定義されているマクロを置換する(置換後に、文字列からクオートが取り除かれる)
  2. 置換可能なマクロが無くなるまで、2を繰り返す

のような感じだと思えばm4のコードを書く上でそれほど困ることはないのかなと思います。

M4の便利な機能とか

divert

divertというマクロが存在します。機能としては、標準出力のON/OFFをm4実行中に切り替えられます。

divert(-1)dnl こっから出力されなくなる
divert(0)dnl こっから出力される

-Ddebug

デバッグ時だけあの情報見たい、みたいなときは次のようにします。

ifdef(`debug', `
`// !!! defined parameters'
`// BASE_URL='    BASE_URL
')

-Dオプションで、指定したマクロが設定されます。

$ m4 script.m4 -Ddebug

まとめ

正直、MakeもM4もとても豊かな表現・実装が出来るツールなので、この記事でカバーしているのはそのほんの一部分にしか過ぎません。なので、もっと調べれば自分の目的にあった記述方法を見つけられると思います。ただ、メンテナンシビリティを考えると基本的な機能だけで作り込んだ方が(特にM4)後々チームメンバーに喜ばれる可能性は高いかな、とも思います。