Makefile Howto

  • Makefile Howto
    • 入门
      • Why called Makefile
        • make 命令依次查找如下文件 `GNUmakefile', `makefile' and `Makefile'
        • GNUmakefile 可能不被非 gnu 的 make 识别
        • 之所以用 Makefile,因为显示文件列表排在最前
      • Makefile 规则介绍
        • target ... : prerequisites ... command ...
        • 命令前面是一个 Tab 制表符,而不是空格!
        • 目标和依赖都可以是多个
        • 依赖也可以为空。例如 clean 不需要依赖任何文件
        • 依赖可以决定 target 是否 outofdate,命令告诉如何生成 target
      • 变量
        • 变量定义,如:
          • objects = main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o
        • 变量引用,如:$(objects)
        • 如果要显示 $ 字符,则可以 $$
      • 示例
        • objects = main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o edit : $(objects) cc -o edit $(objects) $(objects) : defs.h kbd.o command.o files.o : command.h display.o insert.o search.o files.o : buffer.h .PHONY : clean clean : -rm edit $(objects)
    • 进阶
      • Makefile 的五大要素
        • 显示规则
        • 隐含规则
        • 变量定义
        • 指令
        • 注视
      • 5-1. 显示规则
        • 指定目标以及该目标的依赖,以及生成目标文件的命令
        • 格式
          • 命令另起一行,首字符是 tab
            • target ... : prerequisites ... command ...
          • 命令可以和 依赖处于同一行,分号隔开
            • targets : prerequisites ; command command ...
        • 依赖
          • normal prerequisites
            • 作用1:指定编译顺序,先执行依赖本身的编译,之后再执行目标的编译
            • 作用2:确定依赖关系,根据依赖文件于目标文件的时间戳对比,确认是否 outofdate
          • order-only prerequisites
            • 格式:targets : normal-prerequisites | order-only-prerequisites 即用竖线分隔开普通依赖和顺序依赖
            • 顺序依赖只起到前述的作用1,而不会影响 target 的 update 状态
            • 也不会影响自动变量 $^ 等
              • 例如 DocBook Makefile
                • autolayout.xml: layout.xml | docbook.test
          • 测试一下
            • test : 1.xxx 2.xxx | 3.xxx @echo "test depends: $^" %.xxx : @echo "now make target: $@" 执行 make -n test 将显示 echo "now make target: 1.xxx" echo "now make target: 2.xxx" echo "now make target: 3.xxx" echo "test depends: 1.xxx 2.xxx"
      • 5-2. 隐含规则
        • built-in 隐含规则
          • 隐含的 C 规则
            • *.c 文件生成 *.o 文件,使用命令 $(CC) -c $(CPPFLAGS) $(CFLAGS)
          • 隐含的 C++ 规则
            • *.cc/*.C 文件生成 *.o 文件,使用命令 $(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
          • 隐含的 Pascal 规则
            • *.p 文件生成 *.o 文件,使用命令 $(PC) -c $(PFLAGS)
          • 链接目标文件规则
            • 将目标文件 *.o 链接为可执行文件,命令: $(CC) $(LDFLAGS) *.o $(LOADLIBES) $(LDLIBS)
        • 自定义模式规则(Pattern Rules )
          • 例如
            • %.o : %.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
            • % :: RCS/%,v $(CO) $(COFLAGS) $<
            • %.tab.c %.tab.h: %.y bison -d $<
          • 自定义的模式规则,可以替代自带的隐含规则
      • 5-3. 变量定义
        • 变量
          • 大小写敏感
        • 变量定义的 几种风格
          • 风格1: 递归扩展变量 (recursively expanded variable)
            • 变量定义格式是,变量和值之间用等号,即 =
            • 例如:
              • foo = $(bar) bar = $(ugh) ugh = Huh? all:;echo $(foo)
                • 将显示 Huh?
            • 再例如:
              • CFLAGS = $(include_dirs) -O include_dirs = -Ifoo -Ibar
            • 缺点是不能这么定义:CFLAGS = $(CFLAGS) -O ,将会死循环
          • 风格2: 简单扩展变量 (simply expanded variables)
            • 变量定义格式是,变量和值之间用冒号等号,即 :=
            • 例如
              • x := foo y := $(x) bar x := later 等价于: y := foo bar x := later
          • 另外 ?= 含义为:没有定义则赋值
            • FOO ?= bar 等价于 ifeq ($(origin FOO), undefined) FOO = bar endif
          • += 是为变量后面追加字符
        • 变量替换
          • $(var:a=b),是将 var 变量中每一个单词后面的 a 替换为 b
            • $(var:suffix=replacement) 等价于 $(patsubst %suffix,%replacement,$(var))
          • $(foo:%.o=%.c) ,由于出现了 %, 其功能和 patsubst 等价
            • $(var:pattern=replacement) 等价于 $(patsubst pattern,replacement,$(var))
        • 变量计算
          • $($(var))
            • x = $(y) y = z z = Hello a := $($(x))
            • x = y y = z z = u a := $($($(x)))
        • 通配符变量
          • 如果在变量定义中使用通配符,objects = *.o ,并不能展开通配符,*.o 被当做3个字符的字符串
          • 如下格式定义: objects := $(wildcard *.o)
          • 使用函数,将 .c 文件转换为 .o 文件: $(patsubst %.c,%.o,$(wildcard *.c))
        • 自动变量
          • $@
            • 目标文件。当目标文件有多个,$@是触发规则的那个目标文件
            • 当目标文件是 archive member,$@是 archive file,$% 是member name
          • $%
            • 当目标文件是 archive member,$@是 archive file,$% 是member name
            • 例如 目标若是 foo.a(bar.o),则 $%是 bar.o,$@是 foo.a
          • $<
            • 第一个依赖文件
          • $?
            • 比目标文件新的所有依赖文件,文件之间用空格分开
            • 当依赖文件是 archive members,$? 是 member name
          • $^
            • 所有依赖文件(包括比目标旧的依赖文件),文件之间用空格分开
            • 当依赖文件是 archive members,$? 是 member name
            • 当一个文件在依赖列表中被罗列多次, $^ 只包含一次
          • $+
            • 很 $^ 类似。
            • 当一个文件在依赖列表中被罗列多次, $+ 不同于 $^,包含多个
          • $*
            • Patterns Match 中和目标文件匹配的部分
            • 如: 目标为 `dir/a.foo.b' 并且目标表达式为 `a.%.b,则 $* 返回匹配的部分: `dir/foo'
          • $(@D), $(@F), $(*D), $(*F), $(%D), $(%F), $(<D), $(<F), $(^D), $(^F), $(+D), $(+F), $(?D), $(?F)
            • 分别标识上述变量中的目录部分(D),或者文件部分 (F)
            • 目录部分最后的 /,被删除
            • 如 `$(@F)' 等价于 `$(notdir $@)'.
      • 5-4. 指令
        • include
          • 包含其它文件
          • -include 含义为,如果被包含文件不存在,不报错
        • 条件判断
          • ifeq(var1, var2) ... else ... endif
            • 如果 var1, va2 相等
            • ifeq ($(CC),gcc) $(CC) -o foo $(objects) $(libs_for_gcc) else $(CC) -o foo $(objects) $(normal_libs) endif
          • ifneq "var1" "var2" ... else ... endif
            • 如果 var1, var2 不相等
          • ifdef var ... else ... endif
            • 如果 var 不为空
            • 例如
              • ifdef XML_CATALOG_FILES ENSURE_XSL = else ENSURE_XSL = if ! test -e "$(TOOLS_DIR)/xsl"; \ then $(TOOLS_DIR)/bin/find-xsl.py; fi endif
          • ifndef variable-name
        • 定义包含多行文本的变量
          • 例如下面的指令,定义了包含两条 echo 命令的变量 two-lines
            • define two-lines echo foo echo $(bar) endef
          • 例如:
            • define run-yacc yacc $(firstword $^) mv y.tab.c $@ endef foo.c : foo.y $(run-yacc)
      • 5-5. 注释
        • # 注释一行,\# 代表真正的 井号
        • # 注释行最后的 \ 字符,将会使下一行也成为注释
      • 规则中的命令
        • TAB 字符
          • 除了第一行命令可以于 target-and-prerequisites 同一行,用分号分隔外,都要在行首用 Tab 缩进。
          • 注释和空行被忽略。但要注意所谓空行,也要有一个 TAB 起始!
          • 条件指令不需要 有 Tab 起始?
        • @ 字符
          • 执行命令,但不显示命令本身。@ 字符脱掉之后,传递给 Shell 执行
          • make -s/--silent 可以起到同样效果
        • \ 续行符
          • 位于行尾的 \ ,作为续行符
        • cd 目录的作用范围
          • cd命令,改变目录,不会影响后续命令的路径
          • 除非和 cd 命令处于同一行,用分号分开
        • - 忽略错误
          • 在 TAB 之后的减号 -, 将忽略该命令的错误
          • 如:
            • clean: -rm -f *.o
      • 特殊目标:all
        • 执行 make 如果不指定目标,将执行第一个目标
        • 多目标 Makefile,则可以将第一个目标定为 all,将其它目标作为其依赖,这样就可以执行所有目标编译,并指定编译顺序。
      • Phony Targets
        • clean: rm *.o temp
          • clean 这样的 target 本身没有任何依赖, 如果目录中存在名为 clean 的文件,则 不再执行,因为认为 clean 的状态是更新的。
        • .PHONY : clean
          • 将 clean 加入 .PHONY ,则 clean 的执行不会收到存在同名文件的影响
        • .PHONY : all clean
          • 像 all 这样拥有依赖目标的,也可以加入到 phony 中
        • .PHONY 的替代方案 "FORCE"
          • 如:
            • clean: FORCE rm $(objects) FORCE:
          • 有的 make 不支持 .PHONY,则可以定义一个不存在的目标,没有任何依赖,也没有任何命令,如 FORCE:
          • FORCE 因为不存在,且没有任何依赖,其本身如果被当做依赖,则相应的目标必然执行。起到了 .PHONY 的作用
    • 函数
      • 格式
        • $(function arguments) 或者 ${function arguments}
        • function 和 arguments 之间空格分开
        • 各个 argument 之间用冒号分开
      • 字符串函数
        • 替换
          • $(subst from,to,text)
            • 子串替换。$(subst from,to,text) ,将 text 中出现的 from 用 to 替换
            • $(subst ee,EE,feet on the street)
          • $(patsubst pattern,replacement,text)
            • $(patsubst %.c,%.o,x.c.c bar.c)
        • 去掉首尾空格
          • $(strip string)
            • .PHONY: all ifneq $(strip $(needs_made)) "" all: $(needs_made) else all:;@echo 'Nothing to make!' endif
        • 查找、过滤
          • $(findstring find,in)
            • 找到,则返回 find, 否则返回空串
            • 如:
              • $(findstring a,a b c) $(findstring a,b c)
          • $(filter pattern...,text)
            • 在 text 中查找匹配 pattern(可为多个)的单词
            • 如:
              • sources := foo.c bar.c baz.s ugh.h foo: $(sources) cc $(filter %.c %.s,$(sources)) -o foo
          • $(filter-out pattern...,text)
            • 和 filter 函数相反,在 text 中查找除了 pattern(可为多个)之外的单词
            • 如:
              • objects=main1.o foo.o main2.o bar.o mains=main1.o main2.o $(filter-out $(mains),$(objects))
        • 排序、次序
          • $(sort list)
            • 如: $(sort foo bar lose)
          • $(words text)
            • 返回 text 中单词数量
          • $(word n,text)
            • 返回 text 中第几个单词,从1开始
            • $(word 2, foo bar baz)
          • $(wordlist s,e,text)
            • 返回 text 中第s个到第e个单词
            • $(wordlist 2, 3, foo bar baz)
          • $(firstword names...)
            • 返回第一个单词
            • 例如:测试 DocBook XSLT 引擎
              • # XSLT=java \ -Djavax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl \ ... XSLT=/usr/bin/xsltproc --nonet --timing ifeq ($(notdir $(firstword $(XSLT))),xsltproc) ... else ... endif
        • 联合
          • $(join list1,list2)
      • 文件名函数
        • $(dir names...)
          • 返回的目录名包括最后的斜杠
            • 如: $(dir src/foo.c hacks)
        • $(suffix names...)
          • 返回文件扩展名
            • 如:$(suffix src/foo.c src-1.0/bar.c hacks) 返回 .c .c
        • $(basename names...)
          • 注意:此 basename 和 shell 的 basename 不同!返回去掉扩展名之后的文件名包含目录名。
          • $(basename src/foo.c src-1.0/bar hacks) 返回 src/foo src-1.0/bar hacks
        • $(addsuffix suffix,names...)
          • 为文件增加扩展名
          • $(addsuffix .c,foo bar)
        • $(addprefix prefix,names...)
          • 增加前缀
          • $(addprefix src/,foo bar)
        • $(join list1,list2)
        • $(wildcard pattern)
          • 展开通配符
          • 例如
            • ALL_SOURCE := $(wildcard $(XML_SRCDIR)/*.xml) ALL_SOURCE := $(filter-out $(VERSION_SOURCE),$(ALL_SOURCE)) # 如果不用 wildcard, $(ALL_SOURCE) 依然是 *.xml ,仍然包括 version.xml,造成循环依赖 $(VERSION_SOURCE) : $(ALL_SOURCE) ... ...
      • foreach
        • find_files = $(wildcard $(dir)/*) dirs := a b c d files := $(foreach dir,$(dirs),$(find_files)) 等价于 files := $(wildcard a/* b/* c/* d/*)
      • call
        • 如:
          • reverse = $(2) $(1) foo = $(call reverse,a,b)
          • pathsearch = $(firstword $(wildcard $(addsuffix /$(1),$(subst :, ,$(PATH))))) LS := $(call pathsearch,ls)
          • map = $(foreach a,$(2),$(call $(1),$(a))) o = $(call map,origin,o map MAKE)
      • origin
        • $(origin variable)
        • 查看变量 variable 的来源,variable 不要带 $。 返回值:undefined,default,environment,environment override, command line,override,automatic
        • 如: DocBook Makefile 测试环境变量 XML_CATALOG_FILES
          • docbook.test: ifeq "$(XML_CATALOG_FILES)" "" $(error XML_CATALOG_FILES is blank!) endif ifeq "$(origin XML_CATALOG_FILES)" "undefined" $(error XML_CATALOG_FILES is $(origin XML_CATALOG_FILES) !) endif
      • SHELL 函数
          • contents := $(shell cat foo) files := $(shell echo *.c)
      • 出错处理函数
        • $(error text...)
          • 显示异常,并退出
          • 如: DocBook Makefile 测试环境变量 XML_CATALOG_FILES
            • docbook.test: ifeq "$(XML_CATALOG_FILES)" "" $(error XML_CATALOG_FILES is blank!) endif ifeq "$(origin XML_CATALOG_FILES)" "undefined" $(error XML_CATALOG_FILES is $(origin XML_CATALOG_FILES) !) endif
        • $(warning text...)
          • 显示警告,不退出
    • 诊断、调试
      • make -n
        • 不执行命令,只是显示每条命令的执行
      • @echo ...
        • 打印消息
      • $(error text...)
        • 显示异常,并退出
      • $(warning text...)
        • 显示警告,不退出
    • Makefile Samples
      • WHODO DocBook Makefile(s)
    • 关于本文
      • 版本
        • v0.1 at 2005/08
      • 作者
        • J
          • Jiang Xin

Makefile Howto//mm2html.xsl FreemindVersion:0.8.0