OpenWRT开发之——BuildPackage剖析

http://my.oschina.net/hevakelcj/blog/417448

前言

在之前的博文里详细地讲述了如何在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 ),是将作为Makefile文件中的一部分。

$(eval $(call BuildPackage,cpp11-demo)),是引用BuildPackage变量中的内容,并将内部中的$(1)替换为cpp11-demo,然后将处理后的内部作为Makefile的一部分。


1. 研究BuildPackage

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

即默认的目标构建。


2. 研究Build/DefaultTargets

如下为 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

<明日再续>

你可能感兴趣的:(OpenWRT开发之——BuildPackage剖析)