4、Android.mk详解(上):LOCAL_PATH、include $(CLEAR_VARS)、LOCAL_MODULE、LOCAL_SRC_FILES、LOCAL_C_INCLUDES
各位同学,今天我们正式进入 Android.mk 的细节世界。
说实话,Android.mk 是 AOSP 构建系统里最基础、也最绕不开的一块。我见过不少新人,一上来就被各种 LOCAL_ 变量搞晕了。其实没那么复杂,你把它想象成「给编译器下订单」——告诉系统:我要编译什么、从哪里找代码、头文件在哪、最终生成什么名字。
今天我们先讲最核心的五个变量。掌握了它们,你就掌握了 Android.mk 的骨架。
4.1 LOCAL_PATH:你的代码在哪里?
LOCAL_PATH 是 Android.mk 里第一个要定义的变量。它告诉构建系统:当前这个模块的源代码在哪个目录。
写法很简单,几乎每个 Android.mk 开头都是这一行:
LOCAL_PATH := $(call my-dir)
my-dir 是构建系统提供的一个宏,它会返回当前 Android.mk 文件所在的目录路径。说白了,就是「我就在这儿」。
LOCAL_PATH := $(call my-dir)
# 蓝牙音频 HAL 模块
这样半年后回来看,一眼就知道这个 mk 是干嘛的。
有一点要注意:LOCAL_PATH 是相对路径,但构建系统会自动把它解析成绝对路径。你不需要自己拼路径。
4.2 include $(CLEAR_VARS):清空上一家的痕迹
这个写法,我刚开始学的时候觉得挺奇怪的——为什么要「清空」?
原因很简单:Android 构建系统是全局的。所有 Android.mk 文件会被解析到同一个 Make 上下文中。如果你不清空,上一个模块设置的 LOCAL_SRC_FILES 会污染当前模块。
include $(CLEAR_VARS)
这一行会清除所有 LOCAL_ 开头的变量,但保留 LOCAL_PATH。嗯,这里要注意:LOCAL_PATH 不会被清掉,因为它是你刚设好的。
include $(CLEAR_VARS),结果编译出来的 so 文件莫名其妙多了几个源文件。查了半天,发现是上一个模块的 LOCAL_SRC_FILES 残留下来了。从那以后,我写每个 Android.mk 的第一件事就是先写这一行。
4.3 LOCAL_MODULE:给你的模块起个名字
LOCAL_MODULE 定义的是模块的名字。这个名字会直接影响最终生成的文件名。
LOCAL_MODULE := libhello
如果你编译的是动态库,最终生成的是 libhello.so;静态库是 libhello.a;可执行文件就是 hello(不加前缀后缀)。
命名上我建议遵循几个原则:
- 库文件以
lib开头,比如libaudio、libcamera - 可执行文件直接用功能名,比如
mediaserver、logd - 不要用中文或特殊字符,构建系统不认
libbt_audio 表示蓝牙音频。
4.4 LOCAL_SRC_FILES:告诉编译器要编译哪些源文件
这个变量列出所有需要编译的源文件。可以是 .c、.cpp、.S(汇编)等。
LOCAL_SRC_FILES := \
src/main.c \
src/utils.c \
src/network.c
这里有几个实用技巧:
- 文件路径是相对于
LOCAL_PATH的,不要写绝对路径 - 用反斜杠
\换行,方便阅读 - 如果源文件很多,可以用
LOCAL_SRC_FILES +=分多次添加
我个人习惯把源文件按功能模块分组,注释写明每组的作用:
LOCAL_SRC_FILES := \
# 核心逻辑
core/engine.c \
core/parser.c \
# 网络相关
net/socket.c \
net/protocol.c \
# 工具函数
util/string.c \
util/memory.c
你想想看,这样写是不是清晰多了?半年后回来改代码,一眼就知道每个文件是干嘛的。
4.5 LOCAL_C_INCLUDES:头文件搜索路径
编译器在编译 .c 文件时,需要知道头文件在哪里。LOCAL_C_INCLUDES 就是干这个的。
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/include \
$(LOCAL_PATH)/../common/include
这里要注意几点:
- 路径可以是相对路径(相对于
LOCAL_PATH),也可以是绝对路径 - 多个路径用空格或换行分隔
- 系统头文件路径(如
system/core/include)不需要你加,构建系统会自动包含
./。Android 的构建系统对路径格式很敏感,建议统一用 $(LOCAL_PATH)/xxx 的写法,不要自己拼路径。
另外,如果你用到了第三方库的头文件,也要加进来:
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/include \
external/openssl/include \
external/protobuf/src
4.6 一个完整的例子
把今天学的五个变量串起来,就是一个完整的 Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libhello
LOCAL_SRC_FILES := \
src/hello.c \
src/world.c
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/include
include $(BUILD_SHARED_LIBRARY)
最后一行 include $(BUILD_SHARED_LIBRARY) 告诉系统:我要编译成动态库。这个我们后面会详细讲。
LOCAL_PATH:定位当前模块目录include $(CLEAR_VARS):清空上一模块的残留变量LOCAL_MODULE:定义模块名,影响输出文件名LOCAL_SRC_FILES:列出所有源文件LOCAL_C_INCLUDES:指定头文件搜索路径
好了,今天先到这里。记住,写 Android.mk 就像写菜谱——把原料(源文件)、调料(头文件路径)、菜名(模块名)交代清楚,构建系统就能帮你做出成品。