Android FrameWork基础之Makefile
Makefile入门教程(结合Android源码编译用例)
一、引言
Makefile是用于自动化构建项目的工具,尤其在Android源码编译中扮演着至关重要的角色。Makefile文件包含了构建过程中所需的各种规则、依赖关系和命令。通过Makefile,我们可以方便地编译、链接和安装项目。
二、Makefile基础
1. 目标和依赖
Makefile的基本单位是规则,每条规则由目标、冒号和依赖列表组成。目标是要生成的文件,依赖是生成目标所需的文件或条件。
例如,一个简单的Makefile规则可能如下:
hello: hello.c | |
gcc hello.c -o hello |
在这个例子中,hello
是目标,hello.c
是依赖。当执行make hello
时,如果hello.c
存在且hello
不存在或hello
比hello.c
旧,则会执行gcc hello.c -o hello
命令来生成hello
。
2. 变量
Makefile支持变量,可以方便地替换命令中的重复部分。
CC = gcc | |
CFLAGS = -Wall | |
hello: hello.c | |
$(CC) $(CFLAGS) hello.c -o hello |
3. 通配符和模式规则
Makefile支持使用通配符和模式规则来匹配多个文件。
%.o: %.c | |
$(CC) $(CFLAGS) -c $< -o $@ |
这条规则表示将所有的.c
文件编译成对应的.o
文件。$<
代表依赖列表中的第一个依赖(即.c
文件),$@
代表目标(即.o
文件)。
三、Android源码编译中的Makefile
Android源码编译是一个复杂的构建过程,涉及大量的Makefile文件。下面以Android源码中的某个模块为例,介绍Makefile的用法。
假设我们有一个简单的Android模块,其目录结构如下:
my_module/ | |
Android.mk | |
src/ | |
main.c |
1. Android.mk文件
在Android源码中,每个模块通常包含一个Android.mk
文件,用于描述模块的构建规则。
# 定义LOCAL_PATH为当前目录路径 | |
LOCAL_PATH := $(call my-dir) | |
# 清除之前定义的变量 | |
include $(CLEAR_VARS) | |
# 定义模块信息 | |
LOCAL_MODULE := my_module | |
LOCAL_SRC_FILES := src/main.c | |
# 包含需要的库文件 | |
LOCAL_SHARED_LIBRARIES := \ | |
libutils \ | |
libcutils | |
# 定义编译类型 | |
include $(BUILD_SHARED_LIBRARY) |
LOCAL_PATH
:指定当前模块的路径。include $(CLEAR_VARS)
:清除之前定义的变量,确保每个模块都有自己的独立环境。LOCAL_MODULE
:定义模块的名称。LOCAL_SRC_FILES
:指定模块的源文件列表。LOCAL_SHARED_LIBRARIES
:指定模块依赖的共享库。include $(BUILD_SHARED_LIBRARY)
:指定编译类型,这里是生成共享库。
2. 构建模块
在Android源码根目录下执行以下命令来构建模块:
source build/envsetup.sh | |
lunch <target_product>-<target_build_variant> | |
mmm my_module/ |
source build/envsetup.sh
:初始化构建环境。lunch <target_product>-<target_build_variant>
:选择构建目标和变体。mmm my_module/
:只构建my_module
模块。
四、常用指令和函数
1. 条件判断
Makefile支持条件判断,可以根据不同的条件执行不同的命令。
ifeq ($(TARGET_OS),android) | |
# Android平台相关的设置 | |
else | |
# 其他平台相关的设置 | |
endif |
2. 函数
Makefile提供了一些内置函数,用于处理字符串、文件名等。
my_files := $(wildcard src/*.c) # 匹配src目录下的所有.c文件 |
3.静态库编译
假设我们有一个简单的C语言项目,需要编译成静态库供其他项目使用。
目录结构:
my_lib/ | |
Android.mk | |
src/ | |
file1.c | |
file2.c |
Android.mk
文件:
LOCAL_PATH := $(call my-dir) | |
include $(CLEAR_VARS) | |
LOCAL_MODULE := my_static_lib | |
LOCAL_SRC_FILES := src/file1.c src/file2.c | |
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include | |
include $(BUILD_STATIC_LIBRARY) |
在这个例子中,BUILD_STATIC_LIBRARY
宏告诉构建系统生成一个静态库。其他项目可以通过 LOCAL_STATIC_LIBRARIES
变量来链接这个静态库。
4.可执行文件编译
假设我们有一个C语言项目,需要编译成一个可执行文件。
目录结构:
my_tool/ | |
Android.mk | |
src/ | |
main.c |
Android.mk
文件:
LOCAL_PATH := $(call my-dir) | |
include $(CLEAR_VARS) | |
LOCAL_MODULE := my_tool | |
LOCAL_SRC_FILES := src/main.c | |
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include | |
# 如果需要链接其他库 | |
LOCAL_SHARED_LIBRARIES := \ | |
liblog \ | |
libcutils | |
include $(BUILD_EXECUTABLE) |
在这个例子中,BUILD_EXECUTABLE
宏告诉构建系统生成一个可执行文件。LOCAL_SHARED_LIBRARIES
变量列出了这个可执行文件所依赖的共享库。
5.条件编译
有时候,我们可能需要根据不同的条件来编译不同的源文件或设置不同的编译选项。
Android.mk
文件:
LOCAL_PATH := $(call my-dir) | |
include $(CLEAR_VARS) | |
LOCAL_MODULE := my_module | |
ifeq ($(TARGET_ARCH),arm) | |
LOCAL_SRC_FILES := src/arm/main.c | |
LOCAL_CFLAGS += -DARM_VERSION | |
else | |
LOCAL_SRC_FILES := src/x86/main.c | |
LOCAL_CFLAGS += -DX86_VERSION | |
endif | |
include $(BUILD_SHARED_LIBRARY) |
在这个例子中,我们根据目标架构(TARGET_ARCH
)来决定使用哪个源文件进行编译,并设置相应的编译选项。
6.使用通配符和模式规则
当项目中有大量的源文件时,使用通配符和模式规则可以简化Makefile的编写。
Android.mk
文件:
LOCAL_PATH := $(call my-dir) | |
include $(CLEAR_VARS) | |
LOCAL_MODULE := my_module | |
LOCAL_SRC_FILES := $(wildcard src/*.c) | |
include $(BUILD_SHARED_LIBRARY) |
在这个例子中,$(wildcard src/*.c)
会匹配src
目录下所有的.c
文件,并将其赋值给LOCAL_SRC_FILES
。这样,我们就无需手动列出每一个源文件了。
7.使用变量和函数
Makefile中的变量和函数可以帮助我们更加灵活地处理文件名、路径等。
Android.mk
文件:
LOCAL_PATH := $(call my-dir) | |
MY_SRC_DIR := $(LOCAL_PATH)/src | |
MY_INCLUDES := $(LOCAL_PATH)/include | |
include $(CLEAR_VARS) | |
LOCAL_MODULE := my_module | |
LOCAL_SRC_FILES := $(wildcard $(MY_SRC_DIR)/*.c) | |
LOCAL_C_INCLUDES := $(MY_INCLUDES) | |
include $(BUILD_SHARED_LIBRARY) |
当然可以。Android 源码编译是一个复杂的过程,涉及大量的 Makefile 文件和构建规则。下面我会给出更多结合 Android 源码编译的 Makefile 例子,以便你更好地理解其在实际项目中的应用。
8.编译 Android 应用
假设你有一个简单的 Android 应用,它包含一个 Activity 和一些资源文件。
目录结构:
MyApp/ | |
Android.mk | |
src/ | |
com/example/myapp/MainActivity.java | |
res/ | |
... (资源文件) |
Android.mk
文件:
LOCAL_PATH := $(call my-dir) | |
include $(CLEAR_VARS) | |
# 应用的基本信息 | |
LOCAL_PACKAGE_NAME := MyApp | |
LOCAL_SRC_FILES := $(call all-subdir-java-files) | |
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res | |
LOCAL_CERTIFICATE := platform | |
# 依赖的库(如果有的话) | |
LOCAL_STATIC_JAVA_LIBRARIES := \ | |
android-support-v4 \ | |
... (其他库) | |
include $(BUILD_PACKAGE) |
在这个例子中,BUILD_PACKAGE
宏告诉构建系统这是一个 Android 应用的构建规则。LOCAL_SRC_FILES
通过 all-subdir-java-files
函数自动包含 src
目录下的所有 Java 文件。LOCAL_RESOURCE_DIR
指定了资源文件的目录。LOCAL_CERTIFICATE := platform
表示这个应用使用平台签名。
9.编译 Android 服务
如果你正在开发一个 Android 服务,它的构建过程与应用的构建类似,但可能包含一些额外的配置。
目录结构:
MyService/ | |
Android.mk | |
src/ | |
com/example/myservice/MyService.java | |
res/ | |
... (资源文件,如果有的话) |
Android.mk
文件:
LOCAL_PATH := $(call my-dir) | |
include $(CLEAR_VARS) | |
# 服务的基本信息 | |
LOCAL_MODULE := MyService | |
LOCAL_SRC_FILES := $(call all-subdir-java-files) | |
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res | |
LOCAL_PRIVILEGED_MODULE := true # 如果服务需要特权 | |
LOCAL_CERTIFICATE := platform # 使用平台签名 | |
# 依赖的库 | |
LOCAL_STATIC_JAVA_LIBRARIES := \ | |
android-support-v4 \ | |
... (其他库) | |
include $(BUILD_JAVA_LIBRARY) # 构建 Java 库,通常用于服务 |
在这个例子中,我们使用 BUILD_JAVA_LIBRARY
来构建 Java 库,这通常用于 Android 服务。LOCAL_PRIVILEGED_MODULE := true
表示这个服务需要特权才能运行。
10.编译 Android 本地库(C/C++)
对于包含本地代码(C/C++)的 Android 模块,Makefile 的编写会有所不同。
目录结构:
MyNativeLib/ | |
Android.mk | |
src/ | |
main.cpp | |
include/ | |
my_native_lib.h |
Android.mk
文件:
LOCAL_PATH := $(call my-dir) | |
include $(CLEAR_VARS) | |
# 本地库的基本信息 | |
LOCAL_MODULE := my_native_lib | |
LOCAL_SRC_FILES := src/main.cpp | |
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include | |
LOCAL_SHARED_LIBRARIES := \ | |
liblog \ | |
libcutils \ | |
... (其他共享库) | |
# 如果需要编译为静态库,使用 BUILD_STATIC_LIBRARY | |
# include $(BUILD_STATIC_LIBRARY) | |
# 如果需要编译为动态库(共享库),使用 BUILD_SHARED_LIBRARY | |
include $(BUILD_SHARED_LIBRARY) |
在这个例子中,我们编译一个本地共享库。LOCAL_SRC_FILES
指定了源文件,LOCAL_C_INCLUDES
指定了头文件的搜索路径,LOCAL_SHARED_LIBRARIES
列出了这个库所依赖的其他共享库。根据需要,你可以选择编译为静态库还是动态库。
五、构建规则和属性
在 Android 构建系统中,Makefile 文件用于定义模块的构建规则和属性。这些 Makefile 文件通常包含一系列的变量和宏,用于指定模块的类型、源文件、依赖关系以及其他构建选项。以下是一些 Android 构建系统中常见的固定 Makefile 属性及其详细解释和示例:
1. LOCAL_PATH
LOCAL_PATH
变量用于定义当前 Makefile 文件所在的目录路径。它通常通过 $(call my-dir)
宏来设置,该宏返回当前 Makefile 文件的目录路径。
示例:
LOCAL_PATH := $(call my-dir) |
2. include $(CLEAR_VARS)
CLEAR_VARS
是一个预定义的宏,用于清除之前定义的局部变量,以确保当前模块的构建环境是干净的。
示例:
include $(CLEAR_VARS) |
3. LOCAL_MODULE
LOCAL_MODULE
变量用于定义当前模块的名称。这个名称在构建系统中必须是唯一的。
示例:
LOCAL_MODULE := my_module |
4. LOCAL_SRC_FILES
LOCAL_SRC_FILES
变量用于指定模块的源文件列表。对于 Java 模块,它通常包含 .java
文件;对于本地库(C/C++),它包含 .c
、.cpp
等源文件。
Java 模块示例:
LOCAL_SRC_FILES := $(call all-subdir-java-files) |
本地库示例:
LOCAL_SRC_FILES := main.cpp util.cpp |
5. LOCAL_C_INCLUDES
LOCAL_C_INCLUDES
变量用于指定 C/C++ 源文件搜索头文件的路径列表。
示例:
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include |
6. LOCAL_STATIC_JAVA_LIBRARIES
LOCAL_STATIC_JAVA_LIBRARIES
变量用于指定模块所依赖的静态 Java 库列表。
示例:
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 |
7. LOCAL_SHARED_LIBRARIES
LOCAL_SHARED_LIBRARIES
变量用于指定模块在运行时所依赖的共享库列表。
示例:
LOCAL_SHARED_LIBRARIES := liblog libcutils |
8. LOCAL_CERTIFICATE
LOCAL_CERTIFICATE
变量用于指定模块的签名证书。通常用于为系统应用或特权服务指定签名。
示例:
LOCAL_CERTIFICATE := platform |
9. include $(BUILD_XXX)
BUILD_XXX
是一系列预定义的宏,用于指定模块的构建类型。常见的 BUILD_XXX
宏有 BUILD_PACKAGE
(用于构建 APK)、BUILD_JAVA_LIBRARY
(用于构建 Java 库)、BUILD_SHARED_LIBRARY
(用于构建共享库)等。
构建 APK 示例:
include $(BUILD_PACKAGE) |
构建本地共享库示例:
include $(BUILD_SHARED_LIBRARY) |
这些只是 Android 构建系统中 Makefile 的一部分属性。实际上,构建系统提供了更多的变量和宏,以支持各种复杂的构建需求。为了编写有效的 Makefile,通常需要查阅 Android 的构建系统文档和相关的源代码,以了解所有可用的选项和宏。