如何自动设好 Android.mk 的 LOCAL_SRC_FILES 和 LOCAL_C_INCLUDES

474 查看

原文请猛戳:
http://galoisplusplus.coding.me/blog/2014/08/31/optimize-local-src-files-and-local-c-includes-in-android-dot-mk/

用cocos2d-x开发Android游戏时,需要在Android.mk文件中,为LOCAL_SRC_FILES变量指定要编译的源代码,以及为LOCAL_C_INCLUDES变量指定头文件。当项目文件越来越多时,这种手动修改很浪费时间。好在Android.mk其实就是一个makefile,我们可以借助makefile语法来自动完成这部分工作。

<!-- more -->

使用外部命令

最简单的方式就是调用shell外部命令。首先我们指定要搜索的源文件根目录,设为SRC_ROOT这个变量。LOCAL_C_INCLUDES变量直接就是用find <path> -type d命令去搜索根目录下的目录。LOCAL_SRC_FILES稍微复杂一些,首先我们先用find <path> -type f得到所有的普通文件路径,再指定源代码文件名的匹配模式(例如我用的是c++,所以我指定了变量SRC_SUFFIX存放一般c++源代码文件的后缀名),用filter命令筛选出所有的源代码文件路径。

完整的代码如下:

# WARNING: Shell command is used, it is only works on a UNIX-like OS.
# Replace it with Makefile rules if you want to run on Windows.
SRC_SUFFIX := *.cpp *.c 
SRC_ROOT := $(LOCAL_PATH)/../../Classes
ALL_FILES := $(shell find $(SRC_ROOT) -type f)
SRC_FILES := $(filter $(subst *,%,$(SRC_SUFFIX)),$(ALL_FILES))
LOCAL_SRC_FILES := hellocpp/main.cpp
LOCAL_SRC_FILES += $(SRC_FILES:$(LOCAL_PATH)/%=%)

SRC_DIRS := $(shell find $(SRC_ROOT) -type d)
LOCAL_C_INCLUDES := $(SRC_DIRS)

使用纯Makefile语法

使用外部命令是最简单实用的解决方案,但正如上面的代码注释所提及的,这种方式只能在Unix系统上才能用,对于需要跨平台适用的情况,还是需要采用纯Makefile语法才行。

我们知道,Makefile的wildcard命令可以部分实现类似find的功能,例如找到当前目录下的.c文件可以用$(wildcard *.c),可惜wildcard毕竟不够强大,该命令的结果并不包含子目录以下的.c文件。想要实现这一功能,我们可以借用StackOverflow上大神用纯Makefile语法写的递归wildcard

# recursive wildcard
rwildcard = $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d)))

rwildcard命令传入两个参数,第一个参数$1是目录,第二个参数$2是匹配模式。该命令首先用$(wildcard $1*)得到目录下的所有文件和一级子目录,再遍历一遍:对于当前$d变量是目录的情况,对$d/目录递归调用rwildcard;对于$d是普通文件的情况,递归调用会因为$(wildcard $d/*)找不到匹配而终止,接下来便调用filter函数对$2的模式进行筛选。

完整的代码如下:

SRC_SUFFIX := *.cpp *.c 
SRC_ROOT := $(LOCAL_PATH)/../../Classes
# recursive wildcard
rwildcard = $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d)))
SRC_FILES := $(call rwildcard,$(SRC_ROOT)/,$(SRC_SUFFIX))
LOCAL_SRC_FILES := hellocpp/main.cpp
LOCAL_SRC_FILES += $(SRC_FILES:$(LOCAL_PATH)/%=%)

筛除不需要编译的源代码文件

上面介绍的方法有一个适用的前提,那就是$SRC_ROOT下每个源代码文件都需要被编译。而有时候这个条件并不成立,像本渣所在项目中就用到了一些外部库,这些库的源代码是不用被编译的(例如设为ASIO_HEADER_ONLY的Asio库)。这个时候就需要把这部分源代码排除在LOCAL_SRC_FILES之外。

第一种方法:filter-out

第一种方法是用Makefile的filter-out

# ASIO library is set as ASIO_HEADER_ONLY, so it will be excluded from source code
EXCLUDE_SRC_FILES := $(SRC_ROOT)/3rdParty/Asio/asio/impl/%.cpp
EXCLUDE := $(filter $(EXCLUDE_SRC_FILES),$(SRC_FILES))
SRC_FILES := $(filter-out $(EXCLUDE_SRC_FILES),$(SRC_FILES))

这种方式虽然可行,但是filter-out无法用于多级目录的模式匹配,所以这种方法暴露了太多关于外部库源代码路径的细节。有没有可能指定要排除的库名或关键字,再根据这个信息去筛除匹配的源代码文件呢?

第二种方法:改进rwildcard

第二种方式是在rwildcard中加入一个判断用于筛除:如果当前的目录/文件名匹配到所要筛除的关键字,则什么都不做;否则就继续递归调用和执行filter命令。

# ASIO library is set as ASIO_HEADER_ONLY, so it will be excluded
EXCLUDE_LIB := Asio
# recursive wildcard
rwildcard = $(foreach d,$(wildcard $1*),$(if $(findstring $(EXCLUDE_LIB),$d),,$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d)))
SRC_FILES := $(call rwildcard,$(SRC_ROOT)/,$(SRC_SUFFIX))

这种方式是好一些了,而且直接在递归wildcard搜索时就进行了排除,不过rwildcard变得更复杂了,可读性不佳。

第三种方法:FILTER_OUT_PATTERN

最后的方法也来自于StackOverflow大神用纯Makefile语法改造过的filter-out,不过本质上和第二种方法的实现是类似的,这里就不详细解释了:

# ASIO library is set as ASIO_HEADER_ONLY, so it will be excluded from source code
EXCLUDE_SRC_PATTERN := asio
FILTER_OUT_PATTERN = $(foreach v,$(2),$(if $(findstring $(1),$(v)),,$(v)))
SRC_FILES := $(call FILTER_OUT_PATTERN,$(EXCLUDE_SRC_PATTERN),$(SRC_FILES))

进一步拓展

假如需要剔除的库有很多,我们当然希望能在FILTER_OUT_PATTERN中加入所有要剔除的库的关键字。一种方式是拓展上述第二种方法,加个foreach,但个人不太喜欢;另一种方式是拓展第三种方法:

# The key names of excluded lib here are just used for illustration.
EXCLUDE_SRC_PATTERN := asio asio1 asio2 asio3
FILTER_OUT_PATTERN = $(foreach v,$(2),$(if $(findstring $(1),$(v)),,$(v)))
define FILTER_FUN
SRC_FILES := $(call FILTER_OUT_PATTERN,$(1),$(SRC_FILES))
endef
$(foreach p,$(EXCLUDE_SRC_PATTERN),$(eval $(call FILTER_FUN)))

这里不外乎是用eval做宏展开,具体请参见The eval Function