Android.mk高级写法
原本只是想记录一些常用的使用技巧,但是越写越得意(>_<),忍不住想要做出一份相对完善的说明文档,以供大家研究探讨。
写这篇文章的起因当然是实际工程需要,在搭建一个网游的android客户端时遇到种种恶心的问题,比如文件过多导致"Argument list too long" 的错误,又比如增加和删除文件时都需要维护好Android.mk配置,虽然可以通过写个脚本自动生成android.mk,但是终归不是很漂亮的解决方 案。通过本文所提到的几个方法,可以使Android.mk变得十分简洁,减少增加和删减工程文件时对Android.mk的修改,工程文件数目较多时会 非常实用。
我先附上修改后的cocos2d-x工程配置(在本文的最后,我还会附上原始的配置,有兴趣的同学可以对比下看看),然后在此基础上逐一说明:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := cocos2dx_static LOCAL_MODULE_FILENAME := libcocos2d #1 定义查找所有cpp文件的宏 define all-cpp-files-under $(patsubst ./%,%, $(shell find $(LOCAL_PATH) -name "platform" -prune -o -name "*.cpp" -and -not -name ".*")) endef define all-subdir-cpp-files $(call all-cpp-files-under,.) endef #2 定义查找所有c文件的宏,android有默认定义,此处可酌情省略 define all-c-files-under $(patsubst ./%,%, $(shell find $(LOCAL_PATH) -name "platform" -prune -o -name "*.c" -and -not -name ".*")) endef define all-subdir-c-files $(call all-c-files-under,.) endef #3 通过查找获取所有工程文件列表 CPP_FILE_LIST := $(call all-subdir-cpp-files) \ $(wildcard $(LOCAL_PATH)/platform/*.cpp) \ $(wildcard $(LOCAL_PATH)/platform/android/*.cpp) \ $(wildcard $(LOCAL_PATH)/platform/android/jni/*.cpp) C_FILE_LIST := $(call all-subdir-c-files) #4 加入工程文件,之所以不直接加是需要进行一个LOCAL_PATH的替换 LOCAL_SRC_FILES := $(CPP_FILE_LIST:$(LOCAL_PATH)/%=%) LOCAL_SRC_FILES += $(C_FILE_LIST:$(LOCAL_PATH)/%=%) LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) \ $(LOCAL_PATH)/include \ $(LOCAL_PATH)/kazmath/include \ $(LOCAL_PATH)/platform/android LOCAL_EXPORT_LDLIBS := -llog\ -lz \ -lGLESv2 #5 加入头文件 LOCAL_C_INCLUDES := $(LOCAL_PATH) \ $(LOCAL_PATH)/include \ $(LOCAL_PATH)/kazmath/include \ $(LOCAL_PATH)/platform/android #6 需要链接的系统默认库 LOCAL_LDLIBS := -lGLESv2 \ -lEGL \ -llog \ -lz #7 加入静态库,加了LOCAL_WHOLE_STATIC_LIBRARIES代表编译器会将静态库完整链接而不会进行删减优化 LOCAL_WHOLE_STATIC_LIBRARIES := cocos_libpng_static LOCAL_WHOLE_STATIC_LIBRARIES += cocos_jpeg_static LOCAL_WHOLE_STATIC_LIBRARIES += cocos_libxml2_static LOCAL_WHOLE_STATIC_LIBRARIES += cocos_libtiff_static #8 预编译宏 # define the macro to compile through support/zip_support/ioapi.c LOCAL_CFLAGS := -DUSE_FILE32API LOCAL_EXPORT_CFLAGS := -DUSE_FILE32API #9 声明生成静态库 include $(BUILD_STATIC_LIBRARY) #10 添加外部导入库目录 $(call import-add-path,$(LOCAL_PATH)) #11 添加导入库(基于上一行添加的导入库目录) $(call import-module,platform/third_party/android/prebuilt/libjpeg) $(call import-module,platform/third_party/android/prebuilt/libpng) $(call import-module,platform/third_party/android/prebuilt/libxml2) $(call import-module,platform/third_party/android/prebuilt/libtiff)
首先是自动遍历整个工程文件的方法,当解决这个问题,我们的目的也就实现了一大半。我先介绍makefile里面的三个常用命令:
1、wildcard : 扩展通配符,用于查找一个目录下的所有符合条件的文件
2、notdir : 去除路径,仅保留文件名
3、patsubst :替换通配符,也可以是任意文本替换
#1 #2这两处我定义了两个宏,用于遍历整个工程的cpp和c文件。
$(patsubst ./%,%, $(shell find $(LOCAL_PATH) -name "platform" -prune -o -name "*.c" -and -not -name ".*"))
如上文所 说,patsubst是文本替换,也就是把$(shell....)所返回的查找到的所有文件名去除掉开头的"./",具体替换原因我们下文会说明。 $(shell...)会执行任意的shell命令(如果是在windows下,就一定要用cygwin来执行ndk-build)。
find可以查找指定目录下的所有符合条件的文件。find后面跟查找目录,这里就是Android.mk所在目录。需要特别注意后面的 -name "platform" -prune这个参数,它可以过滤掉特定文件夹("platform"),使用时要先通过-name把文件夹名字传递过去,然后添加-prune,如果没 有需要过滤的文件夹,就不需要这个参数了。 -o可以指定输出,如果使用了-prune就一定要有这个参数,表示在过滤结果的基础上进行查找。 最后跟查找的判定式,示例中的判定式代表查找所有.c文件但是忽略以.开头的文件(通常是系统文件或隐藏文件)。 find命令是实现文件遍历的基础。
#3处我们定义了一个宏代表文件列表
CPP_FILE_LIST := $(call all-subdir-cpp-files) \
$(wildcard $(LOCAL_PATH)/platform/*.cpp) \
$(wildcard $(LOCAL_PATH)/platform/android/*.cpp) \
$(wildcard $(LOCAL_PATH)/platform/android/jni/*.cpp)
C_FILE_LIST := $(call all-subdir-c-files)
调用 $(call all-subdir-cpp-files) 返回了所有cpp文件列表,但是由于我们有指定忽略platform目录的文件,所以需要在后面把这个目录下android平台相关的文件加进来。
$(wildcard $(LOCAL_PATH)/platform/*.cpp)
这个我们一开始有介绍,就是遍历某一目录下的所有特定扩展名的文件。这个命令的缺点是不支持递归目录遍历(否则也就不需要我们前面写一大堆东西了),不过如果你的工程没有太多目录的话,可以直接使用这个。
#4处正式把文件加入到android工程中
LOCAL_SRC_FILES := $(CPP_FILE_LIST:$(LOCAL_PATH)/%=%)
LOCAL_SRC_FILES += $(C_FILE_LIST:$(LOCAL_PATH)/%=%)
$(CPP_FILE_LIST:$(LOCAL_PATH)/%=%) 这个又是一个文本替换技巧。意思是,把CPP_FILE_LIST里面的所有$(LOCAL_PATH)/去掉。之所以有这样的替换是因为LOCAL_SRC_FILES已经包含了LOCAL_PATH,其文件名应该是相对于Android.mk的相对路径。如果SRC_FILES里面还包含LOCAL_PATH的路径那就出错了。同样这也是我们在#1 #2处需要将查找到的文件名中的"./"去除掉的原因。
#5~#9可以直接看注释,没有什么需要特别说明的。
我们再重点说下#10 #11,也就是导入库的使用。
LOCAL_WHOLE_STATIC_LIBRARIES += cocos_jpeg_static
#LOCAL_STATIC_LIBRARIES += cocos_jpeg_static
$(call import-module,platform/third_party/android/prebuilt/libjpeg)
加入静态 库(第一行和第二行看实际情况使用,差别不大),然后使用call import-module引入导入库。在编译我们这个Android.mk模块时编译器会自动查找并编译libjpeg模块。通过使用导入库可以更加清 晰和方便的复用现有库,或者像我一样把一个大的工程拆分成多个子模块,然后再链接到一起。这样还有一个附带的好处是,当我们修改Android.mk文件 时,本文件的代码都会重新编译,但是导入库引入的模块不会。如果我们的工程很大,那么使用导入库后就可以避免修改一下配置就重新编译整个工程的情况。
prebuilt库的使用:
目的:复用没有源代码的静态库或动态库。
方式:新建一个文件夹如libpng,将头文件放到里面的include目录,.a静态库放到里面的libs目录,新建一个Android.mk其内容如下
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := cocos_libpng_static
LOCAL_MODULE_FILENAME := png
LOCAL_SRC_FILES := libs/$(TARGET_ARCH_ABI)/libpng.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_STATIC_LIBRARY)
重点有两个LOCAL_MODULE是prebuilt库的名字,外部使用时添加的就是这个名字, LOCAL_EXPORT_C_INCLUDES是导出头文件,方便外部头文件包含。
外部使用时像普通的导入库一样使用,再次强调名字一定要一致,否则prebuilt库无法正确导入,就会出现头文件找不到或者是链接错误:
LOCAL_WHOLE_STATIC_LIBRARIES := cocos_libpng_static
$(call import-module,cocos2dx/platform/third_party/android/prebuilt/libpng)