関数(Function)(組み込み関数)


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
[.] Macro, 変数 の違いは [\n] が組み込まれるかどうかだけ
WARNING [,] 以降の SPC は arg として解釈
# "," の後の " " は不要 $(call intro, yamada) -> $1 = [ yamada]
# パターンの適用も OK # [%] == 0文字以上 $(function %.obj)
POINT [.] Windows は File を開くと, 他の Process からの書き込みを防止するため File を Lock する POINT Make の EmbededFunc の共通点
[.] Text をもらって, ある操作をして 別の Text をかえす [.] Argument は [,] 区切り [.] Argument のひとつとして SPACE 区切りの 単語 List をもらう 単語間の Space は区切り扱いだが, それ以外は無視 [.] Pattern 指定も OK [%] : 0 コ以上の文字



文字列関数





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 $@)



notdir filename


test.txt
$(notdir "d:/test.txt")



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
[.] Make の関数はまず 引数が変数の場合,展開する
$(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
[.] User 定義関数とは 単に 文字列を格納した変数のこと [.] call は 関数という文字列 の中で $1, $2 があれば, 引数に置換するだけ そのため変数に文字列がなくても何もしない
# $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 固有変数