OpenWrt Makefile 整体分析

分析版本: svn://svn.openwrt.org.cn/openwrt/branches/backfire

OpenWrt基本结构
–target/linux/ 目录里面是各平台(arch)的相关代码
–target/linux//config 文件的配置文件
–package 目录里面包含了我们在配置文件里面设定的所有编译好的软件包
–scripts/feeds update 来对软件包进行更新
–scirpts/feeds search X 查找软件包X
–package/symlinks 估计意思是更新软件源之类的

=========================我是分割线=================
==以下部分参考:http://www.right.com.cn/forum/thread-73443-1-1.html===
=======================================================
OpenWrt的主Makefile文件只有100行,可以简单分为三部分,1~17行为前导部分,19~31为首次执行部分,33~101为再次执行部分。

前导部分
CURDIR为make默认变量,默认值为当前目录。
前导部分主要把变量TOPDIR赋值为当前目录,把变量LC_ALL、LANG赋值为C,并使用变量延伸指示符export,把上述三个变量延伸到下层Makefile。
使用文件使用指示符include引入$(TOPDIR)/include/host.mk。在OpenWrt的主Makefile文件使用了多次include指示符,说明主Makefile文件被拆分成多个文件,被拆分的文件放在不同的目录。拆分的目的是明确各部分的功能,而且增加其灵活性。
在前导部分比较费解的是使用world目标,在makefile中基本规则为:
TARGETS : PREREQUISITES
COMMAND

即makefile规则由目标、依赖、命令三部分组成,在OpenWrt的主Makefile文件的第一个目标world没有依赖和命令。它主要起到指示当make命令不带目标时所要执行的目标,没有设定依赖和命令部分表明此目标在此后将会有其他依赖关系或命令。world目标的命令需要进一步参考$(TOPDIR)/include/toplevel.mk和主Makefile文件的再次执行部分。

首次执行部分
OPENWRT_BUILD是区分首次执行与再次执行的变量。在首次执行时使用强制赋值指示符override把OPENWRT_BUILD赋值为1,并使用变量延伸指示符export把OPENWRT_BUILD延伸。在OPENWRT_BUILD使用强制赋值指示符override意味着make命令行可能引入OPENWRT_BUILD参数。
引入$(TOPDIR)/include/debug.mk、$(TOPDIR)/include/depends.mk、$(TOPDIR)/include/toplevel.mk三个文件,由于TOPDIR是固定的,所以三个文件也是固定的。其中$(TOPDIR)/include/toplevel.mk的135行%::有效解释首次执行时world目标的规则。

再次执行部分
引入rules.mk、$(INCLUDE_DIR)/depends.mk、$(INCLUDE_DIR)/subdir.mk、target/Makefile、package/Makefile、tools/Makefile、toolchain/Makefile七个文件,rules.mk没有目录名,即引入与主Makefile文件目录相同的rules.mk。在rules.mk定义了INCLUDE_DIR为$(TOPDIR)/include,所以$(INCLUDE_DIR)/depends.mk实际上与首次执行时引入的$(TOPDIR)/include/depends.mk是同一个文件。
四个子目录下的Makefile实际上是不能独立执行。主要利用$(INCLUDE_DIR)/subdir.mk动态建立规则,诸如$(toolchain/stamp-install)目标是靠$(INCLUDE_DIR)/subdir.mk的stampfile函数动态建立。在package/Makefile动态建立了$(package/ stamp-prereq)、$(package/ stamp-cleanup)、$(package/ stamp-compile)、$(package/ stamp-install)、$(package/ stamp-rootfs-prepare)目标。
定义一些使用变量命名的目标,其变量的赋值位置在$(INCLUDE_DIR)/subdir.mk的stampfile函数中。目标只有依赖关系,可能说明其工作顺序,在$(INCLUDE_DIR)/subdir.mk的stampfile函数中有进一步说明其目标执行的命令,并为目标建立一个空文件,即使用变量命名的目标为真实的文件。

定义一些使用固定的目标规则。
其中:clean是清除编译结果的目标,清除$(BUILD_DIR) $(BIN_DIR) $(BUILD_LOG_DIR)三个目录的用意是十分明确。暂时不知道为什么执行make target/linux/clean。
dirclean是删除所有编译过程产生的目录和文件的目标,执行dirclean目标依赖于clean,因此将执行clean目标所执行的命令,然后删除$(STAGING_DIR) $(STAGING_DIR_HOST) $(STAGING_DIR_TOOLCHAIN) $(TOOLCHAIN_DIR) $(BUILD_DIR_HOST) $(BUILD_DIR_TOOLCHAIN)目录,以及删除$(TMP_DIR)目录。上述目录的变量均在rules.mk定义。好像删除staging_dir目录就意味着删除staging_dir目录下的所有子目录,不知道为什么要强调删除$(STAGING_DIR_HOST) $(STAGING_DIR_TOOLCHAIN) $(TOOLCHAIN_DIR)目录。同样删除builde_dir目录就意味着删除builde_dir目录下的所有子目录,不知道为什么要强调删除$(BUILD_DIR_TOOLCHAIN)目录。
tmp/.prereq_packages目标是对所需软件包的预处理。目标依赖于.config,即执行make menuconfig后将会进行一次所需软件包的预处理。不知什么原因在编译前删除tmp目录,执行时无法建立tmp/.prereq_packages文件。
prereq应该是预请求目标,在OpenWrt执行Makefile时好像都要先执行prereq目标。
prepare应该是准备目标,是world依赖的一个伪目标。依赖于文件.config和$(tools/stamp-install) $(toolchain/stamp-install)目标。
world就是编译的目标。依赖于prepare为目标和前面提到的变量命名目标。采用取消隐含规则方式执行package/index目标。package/index目标在package/Makefile的92行定义。
package/symlinks和package/symlinks-install是更新或安装软件包来源的目标,使用$(SCRIPT_DIR)/feeds脚本文件完成。
package/symlinks-clean是清除软件包来源的目标,也是使用$(SCRIPT_DIR)/feeds脚本文件完成。
最后使用伪目标.PHONY说明clean dirclean prereq prepare world package/symlinks package/symlinks-install package/symlinks-clean属于伪目标。通过伪目标说明可以知道可以执行的目标。

===================我是分割线==================
====>Makefile:
TOPDIR:=${CURDIR} #定义直接变量取得当前make的工作目录
LC_ALL:=C
LANG:=C
export TOPDIR LC_ALL LANG #定义直接变量,把变量延伸,使得在包含的mk中也可用

world:
#指示make不带目标时要执行的命令,也就是默认执行的命令,由于其不带规则和依赖,所以表明此目标在此后将会有其他依赖关系或命令,当执行”~$ make”时,即跳转到toplevel.mk执行。======1======

include $(TOPDIR)/include/host.mk #引入文件

ifneq ($(OPENWRT_BUILD),1) #判断是否是首次执行OPENWRT_BUILD在toplevel.mk中定义为0
# XXX: these three lines are normally defined by rules.mk
# but we can’t include that file in this context
empty:=
space:= $(empty) $(empty)
_SINGLE=export MAKEFLAGS=$(space);

override OPENWRT_BUILD=1 #如果是首次执行,使用override强制openwrt_build为1
export OPENWRT_BUILD #延伸openwrt_build变量的使用
include $(TOPDIR)/include/debug.mk #引入文件
include $(TOPDIR)/include/depends.mk
include $(TOPDIR)/include/toplevel.mk
else
include rules.mk #不是首次执行,则引入以下文件
include $(INCLUDE_DIR)/depends.mk
include $(INCLUDE_DIR)/subdir.mk
include target/Makefile
include package/Makefile
include tools/Makefile
include toolchain/Makefile

$(toolchain/stamp-install): $(tools/stamp-install)
$(target/stamp-compile): $(toolchain/stamp-install) $(tools/stamp-install) $(BUILD_DIR)/.prepared
$(package/stamp-cleanup): $(target/stamp-compile)
$(package/stamp-compile): $(target/stamp-compile) $(package/stamp-cleanup)
$(package/stamp-install): $(package/stamp-compile)
$(package/stamp-rootfs-prepare): $(package/stamp-install)
$(target/stamp-install): $(package/stamp-compile) $(package/stamp-install) $(package/stamp-rootfs-prepare)
$(BUILD_DIR)/.prepared: Makefile
@mkdir -p $$(dirname $@)
@touch $@

prepare: $(target/stamp-compile)

clean: FORCE
$(_SINGLE)$(SUBMAKE) target/linux/clean
rm -rf $(BUILD_DIR) $(BIN_DIR) $(BUILD_LOG_DIR)

dirclean: clean
rm -rf $(STAGING_DIR) $(STAGING_DIR_HOST) $(STAGING_DIR_TOOLCHAIN) $(TOOLCHAIN_DIR) $(BUILD_DIR_HOST) $(BUILD_DIR_TOOLCHAIN)
rm -rf $(TMP_DIR)

#处理依赖问题===========9=========
tmp/.prereq_packages: .config
@echo “this is in main Makefile tmp/.prereq_packages”
unset ERROR; \
for package in $(sort $(prereq-y) $(prereq-m)); do \
$(_SINGLE)$(NO_TRACE_MAKE) -s -r -C package/$$package prereq || ERROR=1; \
done; \
if [ -n “$$ERROR” ]; then \
echo “Package prerequisite check failed.”; \
false; \
fi
touch $@

# check prerequisites before starting to build

#在toplevel.mk中的prereq目标执行完之后,跳转回到主Makefile文件,执行其prereq目标的规则================8====================
prereq: $(target/stamp-prereq) tmp/.prereq_packages
@echo “this is in main Makefile”

prepare: .config $(tools/stamp-install) $(toolchain/stamp-install)

#当所有的依赖已经准备好之后,开始执行编译==============10=============
world: prepare $(target/stamp-compile) $(package/stamp-cleanup) $(package/stamp-compile) $(package/stamp-install) $(package/stamp-rootfs-prepare) $(target/stamp-install) FORCE
$(_SINGLE)$(SUBMAKE) -r package/index

# update all feeds, re-create index files, install symlinks
package/symlinks:
$(SCRIPT_DIR)/feeds update -a
$(SCRIPT_DIR)/feeds install -a

# re-create index files, install symlinks
package/symlinks-install:
$(SCRIPT_DIR)/feeds update -i
$(SCRIPT_DIR)/feeds install -a

# remove all symlinks, don’t touch ./feeds
package/symlinks-clean:
$(SCRIPT_DIR)/feeds uninstall -a

.PHONY: clean dirclean prereq prepare world package/symlinks package/symlinks-install package/symlinks-clean

endif

=====>./include/toplevel.mk:
RELEASE:=Backfire #为了避免出现循环
SHELL:=/usr/bin/env bash
PREP_MK= OPENWRT_BUILD= QUIET=0

include $(TOPDIR)/include/verbose.mk

ifeq ($(SDK),1)
include $(TOPDIR)/include/version.mk
else
REVISION:=$(shell $(TOPDIR)/scripts/getver.sh) #shell 执行操作系统的命令
endif

OPENWRTVERSION:=$(RELEASE)$(if $(REVISION), ($(REVISION)))
export RELEASE
export REVISION
export OPENWRTVERSION
export IS_TTY=$(shell tty -s && echo 1 || echo 0)
export LD_LIBRARY_PATH:=$(if $(LD_LIBRARY_PATH),$(LD_LIBRARY_PATH):)$(STAGING_DIR_HOST)/lib
export DYLD_LIBRARY_PATH:=$(if $(DYLD_LIBRARY_PATH),$(DYLD_LIBRARY_PATH):)$(STAGING_DIR_HOST)/lib

# prevent perforce from messing with the patch utility
unexport P4PORT P4USER P4CONFIG P4CLIENT

# prevent user defaults for quilt from interfering
unexport QUILT_PATCHES QUILT_PATCH_OPTS

# make sure that a predefined CFLAGS variable does not disturb packages
export CFLAGS=

ifeq ($(FORCE),)
.config scripts/config/conf scripts/config/mconf: tmp/.prereq-build
endif

SCAN_COOKIE?=$(shell echo $$$$)
export SCAN_COOKIE

SUBMAKE:=umask 022; $(SUBMAKE)

prepare-mk: FORCE ;

#依赖于FORCE保证及时目标存在也会执行此命令=================4=============
prepare-tmpinfo: FORCE
mkdir -p tmp/info #-r禁止使用任何隐含规则 -f制定需要执行的文件-j输出规则中的命令 -r命令运行时不输出命令的输出 mkdir –p 一次性创建多层目录
$(_SINGLE)$(NO_TRACE_MAKE) -j1 -r -s -f include/scan.mk SCAN_TARGET=”packageinfo” SCAN_DIR=”package” SCAN_NAME=”package” SCAN_DEPS=”$(TOPDIR)/include/package*.mk $(TOPDIR)/overlay/*/*.mk” SCAN_DEPTH=5 SCAN_EXTRA=””
$(_SINGLE)$(NO_TRACE_MAKE) -j1 -r -s -f include/scan.mk SCAN_TARGET=”targetinfo” SCAN_DIR=”target/linux” SCAN_NAME=”target” SCAN_DEPS=”profiles/*.mk $(TOPDIR)/include/kernel*.mk $(TOPDIR)/include/target.mk” SCAN_DEPTH=2 SCAN_EXTRA=”” SCAN_MAKEOPTS=”TARGET_BUILD=1″
for type in package target; do \
f=tmp/.$${type}info; t=tmp/.config-$${type}.in; \
[ “$$t” -nt “$$f” ] || ./scripts/metadata.pl $${type}_config “$$f” > “$$t” || { rm -f “$$t”; echo “Failed to build $$t”; false; break; }; \
done
./scripts/metadata.pl package_mk tmp/.packageinfo > tmp/.packagedeps || { rm -f tmp/.packagedeps; false; }
touch $(TOPDIR)/tmp/.build

.config: ./scripts/config/conf $(if $(CONFIG_HAVE_DOT_CONFIG),,prepare-tmpinfo)
@+if [ \! -e .config ] || ! grep CONFIG_HAVE_DOT_CONFIG .config >/dev/null; then \
#”+”无论如何这些命令都会被执行
[ -e $(HOME)/.openwrt/defconfig ] && cp $(HOME)/.openwrt/defconfig .config; \
$(_SINGLE)$(NO_TRACE_MAKE) menuconfig $(PREP_MK); \
fi

scripts/config/mconf:
@$(_SINGLE)$(SUBMAKE) -s -C scripts/config all

$(eval $(call rdep,scripts/config,scripts/config/mconf))

scripts/config/conf:
@$(_SINGLE)$(SUBMAKE) -s -C scripts/config conf
#-C 意思就是转到此目录下执行make -s全面禁止命令的显示,跳转到scripts/config下执行此目录下的Makefile文件的conf标签=============4=========

config: scripts/config/conf prepare-tmpinfo FORCE
$< Config.in #$< 描述了所有的依赖 config-clean: FORCE $(_SINGLE)$(NO_TRACE_MAKE) -C scripts/config clean defconfig: scripts/config/conf prepare-tmpinfo FORCE touch .config $< -D .config Config.in oldconfig: scripts/config/conf prepare-tmpinfo FORCE $< -$(if $(CONFDEFAULT),$(CONFDEFAULT),o) Config.in menuconfig: scripts/config/mconf prepare-tmpinfo FORCE if [ \! -e .config -a -e $(HOME)/.openwrt/defconfig ]; then \ cp $(HOME)/.openwrt/defconfig .config; \ fi $< Config.in prepare_kernel_conf: .config FORCE ifeq ($(wildcard staging_dir/host/bin/sed),) prepare_kernel_conf: @+$(SUBMAKE) -r tools/sed/install else prepare_kernel_conf: ; endif kernel_oldconfig: prepare_kernel_conf $(_SINGLE)$(NO_TRACE_MAKE) -C target/linux oldconfig kernel_menuconfig: prepare_kernel_conf $(_SINGLE)$(NO_TRACE_MAKE) -C target/linux menuconfig #建立其依赖 ===========6==============转到include/prereq-build.mk下执行,主要就是检查各种依赖关系是否已经安装好。 tmp/.prereq-build: include/prereq-build.mk mkdir -p tmp rm -f tmp/.host.mk @$(_SINGLE)$(NO_TRACE_MAKE) -j1 -r -s -f $(TOPDIR)/include/prereq-build.mk prereq 2>/dev/null || { \
echo “Prerequisite check failed. Use FORCE=1 to override.”; \
false; \
}
touch $@

download: .config FORCE
@+$(SUBMAKE) tools/download
@+$(SUBMAKE) toolchain/download
@+$(SUBMAKE) package/download
@+$(SUBMAKE) target/download

clean dirclean: .config
@+$(SUBMAKE) -r $@

#再次找到prereq目标,然后建立其依赖prepare-tmpinfo .config========3=========
prereq:: prepare-tmpinfo .config
@+$(MAKE) -r -s tmp/.prereq-build $(PREP_MK)
#-r 不运行命令,也不输出,仅仅检查目标是否需要更新========5===========
@+$(NO_TRACE_MAKE) -r -s $@ #所有的准备完之后,跳转回主Makefile文件======7=======

#双::号规则允许在多个规则中为同一个目标创建不同命令,NO_TRACE_MAKE在include/verbose.mk中定义为 :=$(MAKE) V=99,PREP_MK在开始定义为0,+代表之后的命令都要执行,也就是表示执行-r禁止使用任何隐含规则-s禁止所有执行命令的显示 $ make prereq =========2=========
%::
@+$(PREP_MK) $(NO_TRACE_MAKE) -r -s prereq
@+$(SUBMAKE) -r $@

help:
cat README

docs docs/compile: FORCE
@$(_SINGLE)$(SUBMAKE) -C docs compile

docs/clean: FORCE
@$(_SINGLE)$(SUBMAKE) -C docs clean

distclean:
rm -rf tmp build_dir staging_dir dl .config* feeds package/feeds package/openwrt-packages bin
@$(_SINGLE)$(SUBMAKE) -C scripts/config clean

ifeq ($(findstring v,$(DEBUG)),)
.SILENT: symlinkclean clean dirclean distclean config-clean download help tmpinfo-clean .config scripts/config/mconf scripts/config/conf menuconfig tmp/.prereq-build tmp/.prereq-package prepare-tmpinfo
endif
.PHONY: help FORCE
.NOTPARALLEL:

快速了解 GNU Makefile 笔记

1. Make 程序会把第一条规则之后的多有以[Tab]字符开始的行都作为命令行来处理
2. 通过.PHONY特殊目标将clean目标声明为伪目标,防止在磁盘上存在一个clean文件名的时候,clean目标的规则无法执行
3. – 的意思是忽略命令的执行错误,比如:-rm edot $(objects)、 sinclude= -include
4. 一个完整的Makefile中,包含5个东西:显示规则,隐含规则,变量的定义,指示符和注释。
5. 默认查找Makefile文件名顺序:GNUmakefile、makefile、Makefile 推荐使用Makefile
6. Include 指示符告诉make暂停读取当前的Makefile,转去读取include指定的文件,完成后再继续当前Makefile的读取
7. MAKEFILE_LIST 变量make读取的文件名会被自动的追加到变量的定义域中
8. Makefile规则中,如果使用一个没有依赖只有命令行的双冒号规则去更新一个文件,那么每次执行make时,此规则的目标文件将会无条件的更新。
9. “所有匹配规则“
%:force
@$(MAKE) –f MAKEFILE $@
Force:;
10. 它的依赖是“force”,保证了即使目标文件已经存在也会执行这个规则
强制目标:没有命令或依赖的规则,如果一个规则没有命令或依赖,而且它的目标不是一个存在的文件名,在执行此规则时,目标总会被认为是最新的。
11.make的执行过程如下:

1. 依次读取变量“MAKEFILES”定义的makefile文件列表

2. 读取工作目录下的makefile文件(根据命名的查找顺序“GNUmakefile”,“makefile”,“Makefile”,首先找到那个就读取那个)

3. 依次读取工作目录makefile文件中使用指示符“include”包含的文件

4. 查找重建所有已读取的makefile文件的规则(如果存在一个目标是当前读取的某一个makefile文件,则执行此规则重建此makefile文件,完成以后从第一步开始重新执行)

5. 初始化变量值并展开那些需要立即展开的变量和函数并根据预设条件确定执行分支

6. 根据“终极目标”以及其他目标的依赖关系建立依赖关系链表

7. 执行除“终极目标”以外的所有的目标的规则(规则中如果依赖文件中任一个文件的时间戳比目标文件新,则使用规则所定义的命令重建目标文件)

8. 执行“终极目标”所在的规则

12.make 参数选项:
-f 指定make读取的Makefile文件 –f NAME 、–file=NAME
-n 那么make执行时只显示所要执行的命令,但不会真正的去执行这些命令 –just-print
-s 则是禁止所有执行命令的显示,就好像所有的命令均使用@开始一样 –slient
-j 可以告诉make在同一时刻可以允许多条命令同时被执行。–job
-k 当由于某种原因被终止时,会等待其它的子进程完成后才退出。
-l 告诉make限制当前运行的任务的数量—max-load 一般跟一个浮点数 -l 2.5
-I make将会忽略所有规则中命令执行错误 –ignore-errors
-w 可以让make在开始编译一个目录之前和完成此目录的编译之后给出相应的提示信息—print-directory

13.文件通配符:*、?、 [ ]
可以用在规则的目标,依赖,此时make会自动将其展开
可出现在规则的命令中,其展开是在shell执行此命令时完成
二是需要通过函数wildcard来实现

14.自动环变量
$? 表示依赖文件列表中被改变过的所有文件
$^ 表示所有的是通过目录搜索得到的依赖文件的完整路径名
$@ 表示规则的目标
$< 表示规则中通过目录搜索得到的依赖文件列表的第一个依赖文件 15.变量定义中使用的通配符不会被展开objects=”*.o” 则变量objects的值就是*.o 如果需要变量的值代表所有的.o文件需要使用函数wildcard实现(objects=$(wildcard *.o)) 16.vpath 指定搜索路径 vpath %.h ../headers如果不能再当前目录下找到,则到目录../headers下寻找。 要注意vpath 和VPATH 以及GPATH变量定义搜索路径时,生成目标文件的路径问题。 17. SUBDIRS = foo bar baz .PHONY: subdirs $(SUBDIRS) subdirs: $(SUBDIRS) $(SUBDIRS): $(MAKE) -C $@ foo: baz 上边的实现中使用了一个没有命令行的规则“foo: baz”,用来限制子目录的make顺序。此规则的含义时在处理“foo”目录之前,需要等待“baz”目录处理完成。 18.Makefile的特殊目标 .PHONY:所有的依赖被作为伪目标,当用make 命令行指定此目标时,目标所在的规则定义的命令都会被无条件的执行。 .SUFFIXES : 所有依赖指出了一系列在后缀规则中需要检查的后缀名 .DEFAULT:被用在重建那些没有具体规则的目标 .PRECIOUS:当命令在执行过程中被中断时,make不会删除它们 .INTERMEDIATE:的依赖文件在make时被作为中间过程文件对待。没有任何依赖文件的目标“.INTERMEDIATE”没有意义。 .SECONDARY:的依赖文件被作为中间过程文件对待。但这些文件不会被自动删除 .DELETE_ON>ERROR:如果规则的命令执行错误,将删除已经被修改的目标文件
.IGNORE:指定依赖文件,则忽略创建这个文件所执行命令的错误。给此目标指定命令是没有意义的。当此目标没有依赖文件时,将忽略所有命令执行的错误
.SILENT:的依赖列表中的文件,make在创建这些文件时,不打印出重建此文件所执行的命令。同样,给目标“.SILENT”指定命令行是没有意义的。没有任何依赖文件的目标“.SILENT”告诉make在执行过程中不打印任何执行的命令。
.EXPORT_ALL_VARIABLES:它的功能含义是将之后所有的变量传递给子make进程
.NOTPARALLEL:则所有命令按照串行方式执行
19. 某些情况下,需要对相同的目标使用不同的规则中所定义的命令,这种情况我们可使用另外一种方式“双冒号”规则来实现。
20.:: 对于一个没有依赖而只有命令行的双冒号规则,当引用此目标时,规则的命令将会被无条件执行。当同一个文件作为多个双冒号规则的目标时。这些不同的规则会被独立的处理,而不是像普通规则那样合并所有的依赖到一个目标文件。
21.gcc 支持一个-M的选项来实现自动找寻源文件中包含的头文件,并生成一个依赖关系。
当我们不需要标准库的依赖规则描述时,应该使用-MM则输出的便是我们自己写的.h的头文件
22.$$$$ 表示当前的进程号
23. 在一个规则的命令中,命令行“cd”改变目录不会对其后的命令的执行产生影响。就是说其后的命令执行的工作目录不会是之前使用“cd”进入的那个目录。如果要实现这个目的,就不能把“cd”和其后的命令放在两行来书写。而应该把这两条命令写在一行上,用分号分隔
foo : bar/lose
cd bar; gobble lose > ../foo
25.Make命令运行结束后返回0为正确的执行。返回非0为出错
26.mkdir -p 命令的使用,一次性创建多层次的目录
27.Make的递归调用 $(MAKE) –C subdir
28.CURDIR此变量代表了make当前的工作路径,使用-C选项进入目录后,此变量会被从新赋值
29.上层Makefile中定义的某一个变量需要传递给子make时,应该在上层Makefile中使用指示符“export”对此变量进行声明。
30.在多级递归调用的make执行过程中。变量“MAKELEVEL”代表了调用的深度。
31. 采用define endef 定义命令包。
32.变量定义
= 风格的变量时递归方式扩展的变量。
:= 直接展开式变量
?= 条件赋值操作符,只有变量之前没有赋值的情况下才会对这个变量进行赋值。
$(foo:.o=.c) 变量的替换引用,替换foo变量中所有以.c结尾的文件为.o结尾
+= 追加变量
Override 变量在定义时使用了“override”,则后续对它值进行追加时,也需要使用带有“override”指示符的追加方式。否则对此变量值的追加不会生效。
% 模式变量,单独使用“%”作为目标时,指定的变量会对任何类型的目标文件都有效
33.+之后的命令都需要执行。

GNU MAKE内嵌函数

$(subst FROM,TO,TEXT) 把字串TEXT中的FROM替换为TO,返回替换后的新字串

$(patsubst PATTERN,REPLACEMENT,TEXT) 搜索TEXT中以空格分开的单词,将符合模式TATTERN的替换为REPLACEMENT,返回替换后的新字串
eg:$(patsubst %.c,%.o,x.c c.c bar.c) 把字串x.c c.c bar.c中以.c结尾的单词替换成以.o结尾的字符,返回的结果是:x.o o.o bar.o

$(strip STRINT) 去掉字串STRING开头和结尾的空字符,返回无前导和结尾空字符,使用单一空格分割的多个单词字符串

$(findstring FIND,IN) 搜索字串IN,查找FIND字串,如果在IN中存在FIND则返回FIND,否则返回空。搜索需要时严格的文本匹配

$(filter PATTERN…,TEXT) 过滤掉字串TEXT中所有不符合模式PATTERN的单词,保留所有符合此模式的单词,可以使用多个模式,模式中一般需要包含模式字符%,存在多个模式时,模式表达式之间使用空格分割。返回所有符合模式的字串。

$(filter-out PATTERN…,TEXT) 过滤掉字串TEXT中所有符合模式PATTERN的单词,保留所有不符合此模式的单词,和filter函数相反。返回所有不符合模式的字串。

$(sort LIST) 给字串LIST的单词以首字母为准进行排序(升序),并去掉重复的单词,返回空格分割没有重复的单词。

$(word N,TEXT) 取字串TEXT中第N个单词(N从1开始),返回字串TEXT中第N个单词

$(wordlist S,E,TEXT) 从字串TEXT中取出从S开始到E的单词(S,E表示单词在字串中位置的数字),返回字串TEXT中从第S到E的单词串。(S和E都从1开始,当S比TEXT中的数字大时,返回空。如果E大于TEXT字数,返回从S开始到结尾的单词串,如果S大于E也是返回空)

$(words TEXT) 统计字串TEXT中单词的数目,返回TEXT字串中的单词数。

$(firstword NAMES…) 取字串NAMES…中的第一个单词,返回NAMES…的第一个单词,以空格分割序列

$(dir NAMES) 从文件名序列NAMES中取出各个文件名目录部分,返回空格分割的文件名序列NAMES…中每一个文件的目录部分。

$(notdir NAMES…) 从文件名序列NAMES…中取出非目录部分,返回空格分割的文件名序列NAMES…中每一个文件的非目录部分。

$(suffix NAMES…) 从文件名序列NAMES…中取出各个文件名的后缀,返回以空格分割的文件名序列NAMES…中每一个文件的后缀序列。

$(basename NAMES…) 从文件名序列NAMES…中取出文件名的前缀部分,返回前缀序列,没有前缀则返回空字串

$(addsuffix SUFFIX,NAMES…) 为NAMES中的每一个文件名添加后缀SUFFIX,将SUFFIX加到每一个文件的后面,返回以空格分割的添加了后缀SUFFIX的文件序列

$(addprefix PREFIX,NAMES…) 为NAMES中的每一耳光文件添加前缀PREFIX,返回以空格分割的添加了前缀PREFIX的文件序列。

$(join LIST1,LIST2) 将字串LIST1和字串LIST2各个单词进行对应连接,就是将LIST2中的第一个单词追加到LIST1第一个单词字后合并为一个单词,将LIST2中第二个单词追加到LIST1的第二个单词之后合并为第一个单词,返回单空格分割的合并后的字串序列。

$(wildcard PATTERN) 列出当前目录下所有符合模式PATTERN格式的文件名,返回空格分割的存在当前目录下的所有符合模式PATTERN的文件名

$(foreach VAR,LIST,TEXT) 类似foreach(VAR,LIST){ TEXT }

$(if CONDITION,THEN,ELSE) 第一个参数CONDITTION的展开结果非空,则条件为真,将第二个参数的THEN作为函数的返回值,CONDITION的结果为空,返回ELSE的结果,当不存在第三个参数,且CONDITION为空时,返回空。

$(call VARIABLE,PARAM,PARAM…) call函数是唯一一个可以创建指定参数化的函数引用的函数,在执行时,将它的参数“PARAM”依次赋值给临时变量“$(1)”、“$(2)”(这些临时变量定义在“VARIABLE”的值中,参考下边的例子)…… call函数对参数的数目没有限制,也可以没有参数值,没有参数值的“call”没有任何实际存在的意义。执行时变量“VARIABLE”被展开为在函数上下文有效的临时变量,变量定义中的“$(1)”作为第一个参数,并将函数参数值中的第一个参数赋值给它;变量中的“$(2)”一样被赋值为函数的第二个参数值;依此类推(变量$(0)代表变量“VARIABLE”本身)。之后对变量“VARIABLE” 表达式的计算值。
Ø 返回值:参数值“PARAM”依次替换“$(1)”、“$(2)”…… 之后变量“VARIABLE”定义的表达式的计算值。
示例:
reverse = $(2) $(1)
foo = $(call reverse,a,b)
变量“foo”的值为“ba”。这里变量“reverse”中的参数定义顺序可以根据需要来调整

$(value VARIABLE) 不对变量VARIBLE进行任何展开操作,直接返回变量VARIBALE代表的值。

eval函数
函数功能:函数“eval”是一个比较特殊的函数。使用它我们可以在我们的Makefile中构造一个可变的规则结构关系(依赖关系链),其中可以使用其它变量和函数。函数“eval”对它的参数进行展开,展开的结果作为Makefile的一部分,make可以对展开内容进行语法解析。展开的结果可以包含一个新变量、目标、隐含规则或者是明确规则等。也就是说此函数的功能主要是:根据其参数的关系、结构,对它们进行替换展开。
返回值:函数“eval”的返回值时空,也可以说没有返回值。
函数说明:“eval”函数执行时会对它的参数进行两次展开。第一次展开过程发是由函数本身完成的,第二次是函数展开后的结果被作为Makefile内容时由make解析时展开的。明确这一点对于使用“eval”函数非常重要。在理解了函数“eval”二次展开的过程后。实际使用时,当函数的展开结果中存在引用(格式为:$(x))时,那么在函数的参数中应该使用“$$”来代替“$”。因为这一点,所以通常它的参数中会使用函数“value”来取一个变量的文本值。
我们看一个例子。例子看起来似乎非常复杂,因为它综合了其它的一些概念和函数。不过我们可以考虑两点:其一,通常实际一个模板的定义可能比例子中的更为复杂;其二,我们可以实现一个复杂通用的模板,在我们的所有Makefile中包含它,以可作到一劳永逸。相信这一点可能是大多数程序员所推崇的。
示例:
# sample Makefile
PROGRAMS = server client
server_OBJS = server.o server_priv.o server_access.o
server_LIBS = priv protocol
client_OBJS = client.o client_api.o client_mem.o
client_LIBS = protocol
# Everything after this is generic
.PHONY: all
all: $(PROGRAMS)
define PROGRAM_template
$(1): $$($(1)_OBJ) $$($(1)_LIBS:%=-l%)
ALL_OBJS += $$($(1)_OBJS)
endef
$(foreach prog,$(PROGRAMS),$(eval $(call PROGRAM_template,$(prog))))
$(PROGRAMS):
$(LINK.o) $^ $(LDLIBS) -o $@
clean:
rm -f $(ALL_OBJS) $(PROGRAMS)
我们来看一下这个例子:它实现的功能是完成“PROGRAMS”的编译链接。例子中“$(LINK.o)”为“$(CC) $(LDFLAGS)”,意思是对所有的.o文件和指定的库文件进行链接。
“$(foreach prog,$(PROGRAM),$(eval $(call PROGRAM_template,$(prog))))”展开为:
server : $(server_OBJS) –l$(server_LIBS)
client : $(client_OBJS) –l$(client_LIBS)

$(origin VARIABLE) 查询参数VARIABLE的出处,返回VARIABLE的定义方式。
函数的返回情况有以下几种:
1. undefined
变量“VARIABLE”没有被定义。
2. default
变量“VARIABLE”是一个默认定义(内嵌变量)。如“CC”、“MAKE”、“RM”等变量(参考 10.3 隐含变量 一节)。如果在Makefile中重新定义这些变量,函数返回值将相应发生变化。
3. environment
变量“VARIABLE”是一个系统环境变量,并且make没有使用命令行选项“-e”(Makefile中不存在同名的变量定义,此变量没有被替代)。
4. environment override
变量“VARIABLE”是一个系统环境变量,并且make使用了命令行选项“-e”。Makefile中存在一个同名的变量定义,使用“make -e”时环境变量值替代了文件中的变量定义。
5. file
变量“VARIABLE”在某一个makefile文件中定义。
6. command line
变量“VARIABLE”在命令行中定义。
7. override
变量“VARIABLE”在makefile文件中定义并使用“override”指示符声明。
8. automatic
变量“VARIABLE”是自动化变量。

$(shell cat foo) 函数shell实现的功能和shell中用()相同,实现的是命令的扩展,意味着需要一个shell命令作为它的参数,返回的结果是此命令在shell中的执行结果。返回空字符、。

$(error TEXT…) 产生致命错误,并提示TEXT…信息给用户,之后退出make的执行。

$(warning TEXT…) 提示TEXT信息,make的执行过程继续,返回空字符