POINT
Make の組み込み関数は文字列操作が主な目的
文字列置換 と同じ
call は 関数.Call というより 特殊な文字列置換
list 中から 目的の file を選択
変数参照に見えるが , 区切りの Argument をもつ
何かの値に展開され, 変数への代入 , Shell にわたされる
POINT
Command 列を 変数に格納することで 応用範囲が広がる
Macro, 変数 への展開結果をかえれるように Argument をわたせる
Make の関数には 数値 という型はない( 文字列として扱う )
# (call has-duplicate, list)
# 数値も 文字列として比較
#
has-duplicate = $(filter \
$(word $1) \
$(words $(sort $1)))
関数が Nest される場合は, 最下層から ひとつずつ読む
# (call file-to-class-name rootdir file)
file-to-class-name := $(subst /,., \ # "/" -> "."
$(subst .java,, \ # ".java" を除去
$(subst $1/,,$2))) # "$1/" を除去
SYNTAX
$(function arg[, arg...])
# Argument の参照は $N とする
echo $1
# 展開するには call 組み込み関数 を利用
# call は 関数 というより
# 特殊な マクロ展開
#
$(call MACRONAME arg, [arg...])
POINT
WARNING
[,] 以降の SPC は arg として解釈
# "," の後の " " は不要
$(call intro, yamada) -> $1 = [ yamada]
# パターンの適用も OK
# [%] == 0文字以上
$(function %.obj)
POINT
Windows は File を開くと, 他の Process からの書き込みを防止するため
File を Lock する
POINT
Make の EmbededFunc の共通点
■ 文字列関数
■ filter
SYNTAX :
$(filter ptn ...,text )
DESC
text を " " 区切り文字として ptn 一致のものを取得
grep みたいなもの
# ui/*.o のみを filter する
#
$(ui_lib): $(filter ui/%.o,$(OBJS))
$(AR) $(ARFLAGS) $@ $^
words := he the hen other the%
get-the:
@echo he matches $(filter he, $(words))
@echo %he matches $(filter %he, $(words))
WARNING
単語は一部を含むではなく, すべて一致する必要あり
ptn[%] は一度しか使用できない
複数あれば 最初のみ ptn[%]
残りは文字あつかい( [%]という文字にマッチ )
■ filter-out
SYNTAX
$(filter-out ptn ...,text )
DESC
ptn に一致しない text 内の単語をかえす
# ptn 不一致を取得
$(filter-out ptn, text)
srcs := main.c sub.c main.h sub.h
# xxx.h を除去する
to_compile := $(filter-out %.h, $(srcs))
■ findstring
SYNTAX
$(findstring serStr,text)
DESC
text 中の serStr を検索
見つかれば serStr( 検索文字列 ) 自身をかえす
wildcard[%] は使用不可
if() とあわせると便利
TIP
[#]: make の変換結果を見るための, echo より簡易な方法
# 複数の Tree ( src, DebugBinary, ReleaseBinary のどこにいるか調べる)
find-tree:
# PWD = $(PWD)
# Tab から始まるため, Make は SubShell への Command と解釈
# Make 関数で置換してから わたす
#
# $(findstring d:/0916, $(PWD))
# $(findstring d:/0915, $(PWD))
# $(findstring d:/0914, $(PWD))
■ subst
SYNTAX
$(subst serStr,repStr,text)
DESC
文字列( text ) を置換( Substitute )
[%] の指定はできない
POINT
# ext を変更するときに便利
# .c -> .o
#
SRCS := main.c sub.c foo.c
$(subst .c,.o,$(SRCS))
WARNING
# [,] の後の空白は文字扱いになる
# ERROR
# ".c " が ".o" にかわる
$(subst .c ,.o,(SRCS))
# ERROR
# "main.c" ---> "main .o"
$(subst .c, .o,(SRCS))
# OK
# RET : main.o sub.o foo.o
$(subst .c,.o,(SRCS))
■ patsubst
SYNTAX
$(patsubst serPtn,repPtn, text)
DESC
wildcard 使用の検索、置換
[%]: は match した文字列に置換される
WARNING
# 末尾 の [/] を除去
# serPtn: text 全体で一致する必要あり
# 途中の [/] は置換されない
#
strip-tail-slash = $(patsubst %/,%,$(dirPath))
BAD
%/ -> [aaa/bbb/]ccc
OK
%/ -> [aaa/bbb/ccc/]
$(variable:ser=rep)
ser: 通常文字列 -> 単語の末尾に一致 -> rep に置換
OBJS := $(SRCS:.cpp=.o)
ser: [%]文字列 -> patsubst 同様
■ words
SYNTAX
$(words text)
DESC
単語数をかえす
CURPATH := $(subst /, ,$(HOME))
words:
echo $$HOME has $(words $(CURPATH)) Directory
# [/] -> [SPC] 変換
CURPATH := $(subst /, ,$(HOME))
words:
@echo $(HOME)
@echo my home path has $(words $(CURPATH))dirs
@echo first dir is $(word 1, $(CURPATH))
@echo second dir is $(word 2, $(CURPATH))
■ word
SYNTAX
$(word n,text)
DESC
[n] 番目の text を返す
1 が最初
n > nrText ならば 何も返さない
POINT
# list 最後の取得
current := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
■ firstword
SYNTAX
$(firstword text)
DESC
1 番目の 単語をかえす
== $(word 1,text)
# 単語 List に変換
# "xxx.yyy.zzz" -> "xxx yyy zzz"
#
version_list := $(subst ., ,$(MAKE_VERSION))
major_version := $(firstword $(version_list))
■ wordlist
SYNTAX
$(wordlist start,end,text)
DESC
[start:end] list をかえす
start > $(words text)
start > end
なら何も返さない
end > $(words text)
なら [start:$(words text)]をかえす
# $(call uid_gid username)
#
uid_gid = $(wordlist 3,4 \
$(subst :, ,\
$(shell grep "^$1:" /etc/passwd)
)
)
■ call
SYNTAX
$(call cmd,arg...)
■ sort
SYNTAX
$(sort list)
DESC
単語の重複, 前後の SPACE を除去してソートする
# STDIN から makefile の内容を入力
#
make -f- < < < 'x:;@echo =$(sort a n d d c e)='
■ shell
SYNTAX
$(shell cmd)
DESC
ひとつの Argument をうけつけて, 展開後に SubShell にわたす
STDOUT への出力は 結果としてかえる
[\] は " " に置換される
STDERR, Exit Status は返されない
POINT
$(shell cmd) は subshell 実行後の結果に置換される
stdout := $(shell echo normal msg)
stderr := $(shell echo err msg 1>&2)
# STDERR への出力は shell 関数の戻値にならない
#
shell-value:
# $(stdout)
# $(stderr)
# RET : [Mon Sep 17 10:41:29]
# make がこの行を解釈すれば ERR
$(shell date)
# こちらは OK
$(shell oman test)
# make をはじめる前に 必要な dir を作成する
# bash の for 文を利用して Directory 作成
#
POINT
_MKDIRS 自体は意味はない
$(shell) が実行されることが目的
REQ_DIRS := foo bar goo
_MKDIRS := $(shell for d in $(REQ_DIRS); \
do \
[[ -d $$d ]] || mkdir -p $$d; \
done)
WARNING
単純変数 と 再帰変数の違いに注意すること
ST := $(shell date)
CT := $(shell date) # CT が利用されるたびに評価
# date uniq file を作成
RELEASE_TAR := mpwm-$(shell date +%F).tar.gz
# $(call file-to-class-name, filename)
# 相対 Path を Java クラス名に変換
#
file-to-class-name := $(subst /,.,$(patsubst %.java,%,$1))
■ file.名関数
■ wildcard
SYNTAX
$(wildcard ptn...)
DESC
ptn に match した file 名に展開
一致しないなら ""
TIP
ptn は Target, Depend で展開されるのと同じ
Shell の 展開文字 を利用する
[ ~, *, ?, [...], [^...] ]
"%" をまちがって利用しないように
# $(shell) で代用するとこうなる
SRCS := $(shell ls *.cpp)
# shell の ptn match 同様( Directory 込みで match )
# RET
# ./dir/aaa.cpp ./dir/bbb.cpp
$(wildcard ./dir/*.cpp)
# xxx.c, xxx.h をセット
SRCS := $(wildcard *.c *.h)
POINT
Directory .cpp をすべて取得可能
SRCS := main.cpp sub.cpp ... といったベタガキは不要
# if と併用することで File の有無をチェック
#
dotemacs := $(wildcard ~/.emacs)
WARNING
Current Directory 基準( 再帰的ではない )
# Sub Directory にはマッチしない
# RET :
# ./foo.cpp ./bar.cpp
$(wildcard *.cpp)
■ dir|notdir
SYNTAX
$(dir list ...)
DESC
list の各単語の Directory 部分を返す
# src Directory 検索
#
srcdir:=$(sort \
$(dir \
$(shell find . -iname "*.cpp" )))
# $(call srcdir,dirlist)
# WARNING
# 即時評価では問題あり
$(call srcdir,/pro/intp)
srcdir=$(sort \
$(dir \
$(shell find $1 -iname "*.cpp" )))
@echo $(call getshbody,test.sh)
getshbody=$(notdir $(subst .sh,,$1))
# File の出力先と同じ Directory で実行
#
$(OUT )/myfile.out: $(SRC)/src.in
cd $(dir $@); \
generate-myself $^ > $(notdir $@)
■ suffix
SYNTAX
$(suffix name ...)
DESC
list から suffix をかえす
# RET
# bat
$(suffix test.bat)
# 同一 suffix Check
same-suffix = $(filter 1,$(words $(sort $(suffix $1))))
TIP
findstring 関数と共に if() で使用
■ basename
SYNTAX
$(basename list...)
DESC
File Suffix を除去した文字列をかえす
# RET
# main sub
test:
echo $(basename main.cpp sub.cpp)
# $(call file-to-class-name rootdir filename)
file-to-class-name := $(subst /,., \
$(basename \
$(subst $1/,,$2)
)
)
# $(call get-java-class-name filename)
#
# Java のクラス名をかえす
#
get-java-class-name = $(notdir $(basename $1))
■ addsuffix
SYNTAX
$(addsuffix suffix list)
DESC
list の各単語に suffix 追加
POINT
wildcard の patten は shell と同じ
filter は [%] のみ
# $(call find-program, %)
find-program = $(filter $1 # 最後に filtering
$(wildcard # wildcard で検索
$(addsuffix /*, # wildcard 用に "/*" を追加
$(sort # sort
$(subst :, , # PATH 区切り[:] -> " " 単語に分解
$(subst ::,:.:,
$(patsubst :%,.:%,
$(patsubst :%,:.,$(PATH)
)
)
)
)
)
)
)
■ addprefix
SYNTAX
$(addprefix pfxk,name...)
DESC
# file が空でないかテスト
# "-a -s " が prefix
# Command 内で利用されることを想定
#
validfile = test -s . $(addprefix -a -s ,$1)
■ join
SYNTAX
$(join pfxlist,sfxlist)
DESC
prefix list, suffix list の各単語を連結
dir notdir で分離した単語を結合できる
# 苗字 と 名前
fname := yamada suzuki
sname := taro itiro
fullname := $(join $(fname),$(sname))
■ 実行制御
POINT
実行制御をすることで幅広い処理ができる
error も本質的には 実行制御
■ if
SYNTAX
$(if cond,then-part,else-part)
DESC
cond が "文字列を含めば" : 真
NULL 文字列 : 偽
cond の前後の空白は除去した後, 結果がチェックされる
true ならば then-part マクロが展開
false ならば else-part マクロが展開
# PATH 区切り文字を決める
#
# $(COMSPEC) は windows のみ定義
#
PATH_SEP := $(if $(COMSPEC),;,:)
POINT
# if - filter で 文字列のチェックをする
# Version Check
$(if $(filter $(MAKE_VERSION),3.80),, \
$(error this ver in not 3.80) )
POINT
Macro 言語 と Programing 言語は異なる
Macro 言語 は Macro の定義, 展開をして 結果をえるのが Macro 言語
■ error
■ error
SYNTAX
$(error text)
DESC
Error 報告をして
ExitCode 2 をかえす
# make assert
# $(call assert cond,msg)
#
define assert
$(if $1,,$(error assertion failed: $2))
endef
#
# make assert
# $(call assert-exit-file filePtn,msg)
# wildcard を利用して filePtn にマッチする file があるかチェック
#
define assert-exit-file
$(call assert $(wildcard $1),$1 not exist)
endef
# make assert
# $(call assert-not-null makeVar)
#
# WARNING
# VOID:=aiueo
# $(call assert-not-null VOID) != $(VOID)
#
define assert-not-null
# $($1) とあるのは 引数 として 変数をもらうため
#
$(call assert $($1),var $1 is null)
endef
POINT
Make の変数 は 他の変数も代入できる
変数がはいった 変数を展開するには 2 回展開する
# NULLVAR
# RET
# "NULLVAR is null"
#
(call assert-not-null NULLVAR)
■ warning
SYNTAX
$(warning text)
DESC
警告 Message を表示
error とは違い, Make は終了しない
POINT
makefile( FILE ), LINE を表示してくれる
POINT
展開されないので, すべての場所で利用できる
■ foreach
SYNTAX
$(foreach var,list,body)
DESC
list の単語を順に var に設定して, body を展開
展開された body の結果は " " 区切りで累積する
SPC 区切りの文字列 list として展開
# RET
# music_1.wmv music_2.wmv music_3.wmv ...
#
tmp:
@echo $(foreach idx,1 2 3 4 5 6 7 8 9,$music_$idx.wmv)
# $(call grep-string serstr, list)
#
# List から serstr を含む文字のみ抽出
#
define grep-string
$(strip
$(foreach w,$2
$(if $(findstring $1,$w),$w))))
endef
list := foo.h bar.h foo.cpp
# RET
# "foo.h foo.cpp"
test:
echo $(call grep-string,foo,$(list))
POINT
Make の関数は PreProcessor と同じ
その場で展開するため, 展開後の結果がおきかわる
VAR := foo
# TopLevel で展開された結果は ERROR
$(call grep-string,foo,$(list))
letters := $(foreach letter,a b c d,$(letter))
show-words:
# letter has $(words $(letters)) words: $(letters)
VARLIST := SRCS OBJS HOME
$(foreach i,$(VARLIST),\
$(if $($1),,$(shell echo $i has no value > /dev/stderr))\
)
■ strip
SYNTAX
$(strip text)
DESC
前後の空白除去と
list 間の空白をひとつにする
[ aaa bbb ccc ] -> [aaa bbb ccc]
TIP
$(if) 判定の引数をきれいにするのに利用する
# [,] の後の " " が arg2 に含まれてしまう
#
$(call func,arg1, arg2)
VAR:= aaa bbb ccc
test:
echo $(VAR)
echo __$(strip $(VAR))__
# echo __aaa bbb ccc __
# __aaa bbb ccc __
# echo __aaa bbb ccc__
# __aaa bbb ccc__
■ origin
SYNTAX
$(origin var)
DESC
変数の出所を調べる
undefined: 未定義
default: 組み込み ( DB で定義すみ )
enviroment: ENV
enviroment override: (-e)--enviroment-overrides
file: makefine 定義
command line: CommandLine 定義
override : overwride 命令
automatic : 自動変数
$(origin MAKE_VERSION) # 変数名自体
make testtgt DATE=`date`
$(origin DATE) # cmdline
# 変数 宣言 チェック
define assert-defined
$(call assert,\
$(filter-out undefined,$(origin $1)),\
$1 is undefined)
endef
■ 高度なユーザー定義関数
POINT
Debug 機能は多くないので自前で用意すること
POINT
[=] を利用するのは, := では関数の評価に問題あり
# 関数 call を Trace
#
debug-enter = $(if $(debug-trace),\
($warning Enter $0$(echo-args)))
debug-leave = $(if $(debug-trace),\
($warning Leave $0))
# WARNING
# 引数の数を数えることはできない( 数も固定 )
#
comma := ,
echo-args = $(subst ' ','$(comma) ',\
$(foreach a,1,2,3,4,5,'$($a)'))
debug-trace = 1
define a
$(debug-enter)
echo $1 $2 $3
$(debug-leave)
endef
define b
$(debug-enter)
$(call a,$1,$2,hi)
$(debug-leave)
endef
test:
$(call b,5,$(MAKE))
■ eval.関数と.val
SYNTAX :
$(eval exp)
DESC
Exp を Make の構文解析に直接渡す( eval 自体は値を返さない )
POINT
EXp が make に描かれていたことを 考えれば OK
POINT
$(eval SRCS:=foo.c bar.c)
# $(call pro-var,pfx,list)
#
# list から 変数 $1_s, $1_h $1_o に file をセットする
#
define pro-var
$1_s = $(filter %.c,$2)
$1_h = $(filter %.h,$2)
$1_o = $(subst .c,.o,$(filter %.c,$2))
$($1_o): $($1_h)
endef
# lss=ls.c glob.c < - make が tgt としてみなした
#
# WARNING
# TopLevel では 複数行の Macro は Error になる
# [区切り文字] がない といわれる
# eval はこれを解消するためにできた
#
#
$(call pro-var,ls,ls.c ls.h glob.c glob.h)
#
# $(eval lss=ls.c glob.c) # 代入式が評価される
#
$(eval $(call pro-var,ls,ls.c ls.h glob.c glob.h))
TIP
複数行の macro を扱う
2. 関数置換後の str を再評価
2. eval の引数は 2度展開される
make が eval を展開する
2. eval 自身
POINT
Macro とは define によって定義される 特殊な変数
( ie. Macro は変数のひとつ )
Macro と 変数の違いは , Macro が [\n] を含めることができること
POINT
eval を使う理由は, 複数の変数を展開をするため
POINT
複数行に 展開される Macro を makfile の Top で利用するには eval() にわたすこと
WARNING
最初の Macro の展開だけでは, Macro 内の変数の代入はされない
# 展開後の 代入がされない
ls_s = ls.c
ls_h = ls.h
ls_o = ls.o
# ので これは 正しくない
# 変数代入の前に参照された
$(ls_o): $(ls_h)
# そこで, $$ として展開を遅延させる
$$($1_o) : $$($1_h)
POINT
まとめ
eval の引数は 2 回展開される
1 回目 : Make が eval の引数を展開
2 回目 : eval 自身の展開
# これでも OK
# eval を代入に利用することで, Macro 内でおこなう
#
define pro-var
$(eval $1_s = $(filter %.c,$2))
$(eval $1_h = $(filter %.h,$2))
$(eval $1_o = $(filter %.o,$2))
$($1_o): $($1_h)
endef
POINT
まとめとして, 次のように使う
define pro-var
$(eval $1_s = $(filter %.c,$2))
$(eval $1_h = $(filter %.h,$2))
$(eval $1_o = $(filter %.o,$2))
programs += $1
$1: $($1_o)
$($1_o): $($1_h)
endef
# TopLevel で展開する
# POINT
# 文字列の置換にすぎないことを覚えておくこと
#
$(eval $(call pro-var ls,ls.c ls.h ))
$(eval $(call pro-var mv,mv.c mv.h ))
WARNING
複数行に展開される macro は構文 ERROR
■ hook.関数
POINT
# $1, $2 を引数に展開
#
$(call func,foo,bar)
# これは ERROR にならない
#
$(call invalidfunc,foo,bar)
POINT
hook を利用すると 関数がさらに便利になる
user は hook に自分の好きな処理を追加できる
# ar 後 後処理を build-library-hook という macro で用意しておく
define build-library
$(AR) $(ARFLAGS) $@ $1
$(call build-library-hook,$@)
endef
# hook を使うには
# hook 関数( という変数 )に処理をいれる
#
$(foo_lib): build-library-hook = $(RANLIB) $1
$(foo_lib): $(fooobj)
$(call build-library,$^)
$(bar_lib): build-library-hook = $(CHMOD) 444 $1
$(bar_lib): $(barobj)
$(call build-library,$^)
■ 関数に値を渡す
DESC
call を利用
2. 関数外の変数
3. tgt 固有変数