在之前的博文里详细地讲述了如何在OpenWrt下建立一个软件包(package),如:[OpenWrt对C++11的支持],[OpenWrt创建软件包]。
但是有个问题博主始终没有弄明白。为什么我们 make 一下,管理器就为我们从网上仓库下载软件源码,并编译打包。这个过程是怎么回事儿?还有,为什么我们在 package/<包名>/ 下的Makefile文件下的最后一行是:
1
|
$(
eval
$(call BuildPackage,xxx))
|
这句话的语意是什么?BuildPackage是什么?
带着这个问题,我们今天来研究一下 BuildPackage 以及展开后的Makefile全貌。
如果对Makefile的格式不太熟悉的同学,请访问我之前的博文:[Makefile学习笔记],了解Makefile的基本语法。
我们还是来看看一个完整的Makefile内貌,如cpp11-demo的Makefile:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
include $(TOPDIR)
/rules
.mk
PKG_NAME:=cpp11-demo
PKG_RELEASE:=1
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)
/package
.mk
define Package
/cpp11-demo
SECTION:=utils
CATEGORY:=Utilites
TITLE:=$(PKG_NAME)
DEPENDS:=+libstdcpp
endef
define Build
/Prepare
$(MKDIR) -p $(PKG_BUILD_DIR)
$(CP) .
/src/
* $(PKG_BUILD_DIR)
endef
define Package
/cpp11-demo/install
$(INSTALL_DIR) $(1)
/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/$(PKG_NAME) $(1)
/usr/bin
endef
$(
eval
$(call BuildPackage,cpp11-demo))
|
知道Makefile基本语法的同学们都知道 define xxxx ... endef 其实就是给 xxxx 变量赋值,只不过是多行文本而已。
依此,可以将上面的简化为:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
include $(TOPDIR)
/rules
.mk
PKG_NAME:=cpp11-demo
PKG_RELEASE:=1
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)
/package
.mk
Package
/cpp11-demo
=XXXXXX
Build
/Prepare
=YYYYYY
Package
/cpp11-demo/install
=XXXXX
$(
eval
$(call BuildPackage,cpp11-demo))
|
从上面,没有看到一个目标的定义,如下面这样:
1
2
3
4
5
6
7
8
9
|
ALL:$(target)
objects:=main.o
$(target):$(objects)
$(CC) $(CFLAGS) -o $@ $^
.PHONY clean
-$(RM) $(objects)
|
那,这些目标在哪里?
玄机就在最后一行的BuildPackage,它根据上面定义的变量生成了所有上面的一切。
$(eval
$(eval $(call BuildPackage,cpp11-demo)),是引用BuildPackage变量中的内容,并将内部中的$(1)替换为cpp11-demo,然后将处理后的内部作为Makefile的一部分。
BuildPackage 变量定义在 include/package.mk 文件中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
define BuildPackage
$(Build
/IncludeOverlay
)
$(
eval
$(Package
/Default
))
$(
eval
$(Package/$(1)))
ifdef DESCRIPTION
$$(error DESCRIPTION:= is obsolete, use Package
/PKG_NAME/description
)
endif
ifndef Package/$(1)
/description
define Package/$(1)
/description
$(TITLE)
endef
endif
BUILD_PACKAGES += $(1)
$(STAMP_PREPARED): $$(
if
$(QUILT)$(DUMP),,$(call find_library_dependencies,$(DEPENDS)))
$(foreach FIELD, TITLE CATEGORY SECTION VERSION,
ifeq ($($(FIELD)),)
$$(error Package/$(1) is missing the $(FIELD) field)
endif
)
$(
if
$(DUMP), \
$(Dumpinfo
/Package
), \
$(foreach target, \
$(
if
$(Package/$(1)
/targets
),$(Package/$(1)
/targets
), \
$(
if
$(PKG_TARGETS),$(PKG_TARGETS), ipkg) \
), $(BuildTarget/$(target)) \
) \
)
$(
if
$(PKG_HOST_ONLY)$(DUMP),,$(call Build
/DefaultTargets
,$(1)))
endef
|
tip1
BuidPackage的第一行就是:$(Build/IncludeOverlay),其定义:
1
2
3
4
5
|
define Build
/IncludeOverlay
$(
eval
-include $(wildcard $(TOPDIR)
/overlay/
*/$(PKG_NAME).mk))
define Build
/IncludeOverlay
endef
endef
|
Build/IncludeOverlay作用就是 inlcude $(TOPDIR)/overlay/*/$(PKG_NAME).mk 文件。
比如我要更改lua这个包里面的某个变量,如果我们直接修改 package/xx/lua/Makefile 文件,总有不妥之处。因为这个文件是从网上源码上同步下来的。我们最好不要去直修改它们。若需要用户自定义某些内容,不防使用overlay。就是在 trunk/overlay/ 路径下的任意子目录下建立与包名对应的 .mk 文件,比如lua,那么就建 lua.mk。
我们在 trunk/overlay/xxx/lua.mk 文件里重新定义我们的变量或操作。
tip2
其中第9~13行:
1
2
3
4
5
|
ifndef Package/$(1)
/description
define Package/$(1)
/description
$(TITLE)
endef
endif
|
如果没有定义Package/$(1)/description,那么就以$(TITLE)来定义一个默认的。这里$(1)为 "cpp11-demo"。
tip3
1
|
BUILD_PACKAGES += $(1)
|
将cpp11-demo追加到变量 BUILD_PACKAGES 后面,可能是将来编译的时候需要。
tip4
1
|
$(STAMP_PREPARED): $$(
if
$(QUILT)$(DUMP),,$(call find_library_dependencies,$(DEPENDS)))
|
如果 $(QUILT)$(DUMP) 都没有定义,那么以$(DEPENDS)为参引用 find_library_dependencies 变量。
其中 find_library_dependencies 定义如下:
1
2
3
4
5
6
7
8
|
find_library_dependencies = $(wildcard $(patsubst %,$(STAGING_DIR)
/pkginfo/
%.version, \
$(filter-out $(BUILD_PACKAGES),$(foreach dep, \
$(filter-out @%, $(patsubst +%,%,$(1))), \
$(
if
$(findstring :,$(dep)), \
$(word 2,$(subst :,$(space),$(dep))), \
$(dep) \
) \
))))
|
博主暂不解释,有点复杂,略过。其功能应该是找出依赖的库。
生成:
1
|
XXXX/.prepared : aa bb cc
dd
|
tip5
1
2
3
4
5
|
$(foreach FIELD, TITLE CATEGORY SECTION VERSION,
ifeq ($($(FIELD)),)
$$(error Package/$(1) is missing the $(FIELD) field)
endif
)
|
这几句是判断 FIELD, TITLE, CATEGORY, SECTION, VERSION 这几个变量有没有定义,如果没有定义那就就报错。
tip6
1
2
3
4
5
6
7
8
|
$(
if
$(DUMP), \
$(Dumpinfo
/Package
), \
$(foreach target, \
$(
if
$(Package/$(1)
/targets
),$(Package/$(1)
/targets
), \
$(
if
$(PKG_TARGETS),$(PKG_TARGETS), ipkg) \
), $(BuildTarget/$(target)) \
) \
)
|
虽然写得有点复杂,但意思是:
将 Package/$(1)/targets 或 PKG_TARGETS 中的每一项或 ipk 作为$(target),引用 $(BuildTarget/$(target))。其中:
BuildTarget/bin 定义在 include/package-bin.mk
BuildTarget/ipk 定义在 include /package-ipk.mk
具体这两个变量的内容博主暂时不去研究。其内容莫非是描述如何生成bin与ipk目标。这里留个引子,下次再研究。
如果没有定义 Package/$(1)/targets 与 PKG_TARGETS,那么上次就默认将ipk作为target,引用 BuildTarget/ipk 变量了。
tip7
1
|
$(
if
$(PKG_HOST_ONLY)$(DUMP),,$(call Build
/DefaultTargets
,$(1)))
|
如果没有定义 PKG_HOST_ONLY 与 DUMP,那么这句可以简化为:
1
|
$(call Build
/DefaultTargets
,$(1))
|
即默认的目标构建。
如下为 Build/DefaultTargets 的定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
define Build
/DefaultTargets
$(
if
$(QUILT),$(Build
/Quilt
))
$(
if
$(USE_SOURCE_DIR)$(USE_GIT_TREE),,$(
if
$(strip $(PKG_SOURCE_URL)),$(call Download,default)))
$(call Build
/Autoclean
)
download:
$(foreach hook,$(Hooks
/Download
),
$(call $(hook))$(sep)
)
$(STAMP_PREPARED) :
export
PATH=$$(TARGET_PATH_PKG)
$(STAMP_PREPARED):
@-
rm
-rf $(PKG_BUILD_DIR)
@
mkdir
-p $(PKG_BUILD_DIR)
$(foreach hook,$(Hooks
/Prepare/Pre
),$(call $(hook))$(sep))
$(Build
/Prepare
)
$(foreach hook,$(Hooks
/Prepare/Post
),$(call $(hook))$(sep))
touch
$$@
$(call Build
/Exports
,$(STAMP_CONFIGURED))
$(STAMP_CONFIGURED): $(STAMP_PREPARED)
$(foreach hook,$(Hooks
/Configure/Pre
),$(call $(hook))$(sep))
$(Build
/Configure
)
$(foreach hook,$(Hooks
/Configure/Post
),$(call $(hook))$(sep))
rm
-f $(STAMP_CONFIGURED_WILDCARD)
touch
$$@
$(call Build
/Exports
,$(STAMP_BUILT))
$(STAMP_BUILT): $(STAMP_CONFIGURED)
$(foreach hook,$(Hooks
/Compile/Pre
),$(call $(hook))$(sep))
$(Build
/Compile
)
$(foreach hook,$(Hooks
/Compile/Post
),$(call $(hook))$(sep))
$(Build
/Install
)
$(foreach hook,$(Hooks
/Install/Post
),$(call $(hook))$(sep))
touch
$$@
$(STAMP_INSTALLED) :
export
PATH=$$(TARGET_PATH_PKG)
$(STAMP_INSTALLED): $(STAMP_BUILT)
$(SUBMAKE) -j1 clean-staging
rm
-rf $(TMP_DIR)
/stage-
$(PKG_NAME)
mkdir
-p $(TMP_DIR)
/stage-
$(PKG_NAME)
/host
$(STAGING_DIR)
/packages
$(STAGING_DIR_HOST)
/packages
$(foreach hook,$(Hooks
/InstallDev/Pre
),\
$(call $(hook),$(TMP_DIR)
/stage-
$(PKG_NAME),$(TMP_DIR)
/stage-
$(PKG_NAME)
/host
)$(sep)\
)
$(call Build
/InstallDev
,$(TMP_DIR)
/stage-
$(PKG_NAME),$(TMP_DIR)
/stage-
$(PKG_NAME)
/host
)
$(foreach hook,$(Hooks
/InstallDev/Post
),\
$(call $(hook),$(TMP_DIR)
/stage-
$(PKG_NAME),$(TMP_DIR)
/stage-
$(PKG_NAME)
/host
)$(sep)\
)
if
[ -f $(STAGING_DIR)
/packages/
$(STAGING_FILES_LIST) ];
then
\
$(SCRIPT_DIR)
/clean-package
.sh \
"$(STAGING_DIR)/packages/$(STAGING_FILES_LIST)"
\
"$(STAGING_DIR)"
; \
fi
if
[ -d $(TMP_DIR)
/stage-
$(PKG_NAME) ];
then
\
(
cd
$(TMP_DIR)
/stage-
$(PKG_NAME);
find
./ > $(TMP_DIR)
/stage-
$(PKG_NAME).files); \
$(call locked, \
mv
$(TMP_DIR)
/stage-
$(PKG_NAME).files $(STAGING_DIR)
/packages/
$(STAGING_FILES_LIST) && \
$(CP) $(TMP_DIR)
/stage-
$(PKG_NAME)/* $(STAGING_DIR)/; \
,staging-
dir
); \
fi
rm
-rf $(TMP_DIR)
/stage-
$(PKG_NAME)
touch
$$@
ifdef Build
/InstallDev
compile: $(STAMP_INSTALLED)
endif
define Build
/DefaultTargets
endef
prepare: $(STAMP_PREPARED)
configure: $(STAMP_CONFIGURED)
dist: $(STAMP_CONFIGURED)
distcheck: $(STAMP_CONFIGURED)
endef
|
<明日再续>