Android构建系统:Android.mk(3)条件控制详解

简介: Android构建系统:Android.mk(3)条件控制详解

在上一篇博客中,我介绍了Android.mk文件赋值操作、变量引用和函数的使用方法,静态库和可执行文件的示例。在本文中,将介绍Android.mk文件中条件控制的使用方法和一些常用的控制语句。通过使用条件控制,可以根据不同的情况执行不同的操作,可以实现一些复杂的逻辑。我会附上详细的测试代码和测试结果。

Android构建系统:Android.mk(1)基础概念赋值变量引用详解

Android构建系统:Android.mk(2)函数详解

Android构建系统:Android.mk(3)条件控制详解

测试代码和结果

完整的测试结果:

完整的测试代码:

$(warning ====== 开始测试 ======)
# 测试ifeq
# 比较两个相同的字符串
ifeq (apple,apple)
$(info [IFEQ] apple 和 apple 是相同的)
endif
# 比较两个不同的字符串
ifeq "banana" "orange"
$(info [IFEQ] banana 和 orange 是相同的)
else
$(info [IFEQ] banana 和 orange 是不同的)
endif
# 使用ifeq 结构检查环境变量
ifeq ($(OS),Linux)
$(info [IFEQ] 操作系统是 Linux)
endif
ifeq "Linux" "Android"
# 如果Linux 和Android 相等,则执行这里的代码块(不会执行)
$(info Linux 和 Android 是相同的)
endif
# 测试ifneq
# 比较两个不同的字符串
ifneq (grape,lemon)
$(info [IFNEQ] grape 和 lemon 是不同的)
endif
# 使用ifneq 结构检查环境变量
ifneq ($(OS),Windows)
$(info [IFNEQ] 操作系统不是 Windows)
endif
# 测试ifdef
# 定义一个变量
FRUIT := apple
ifdef FRUIT
$(info [IFDEF] FRUIT 已定义,其值是 $(FRUIT))
endif
# 测试ifndef
# 检查一个未定义的变量
ifndef VEGETABLE
$(info [IFNDEF] VEGETABLE 未定义)
endif
# 使用else 结构
# 检查环境变量
ifeq ($(OS),Linux)
$(info [ELSE] 操作系统是 Linux)
else
$(info [ELSE] 操作系统不是 Linux)
endif
# 使用嵌套的ifeq 和 else 来模拟elif
# 检查多个可能的操作系统值
ifeq ($(OS),Linux)
$(info [ELIF] 操作系统是 Linux)
else
ifeq ($(OS),Windows)
$(info [ELIF] 操作系统是 Windows)
else
ifeq ($(OS),Mac)
$(info [ELIF] 操作系统是 Mac)
else
$(info [ELIF] 操作系统是未知的)
endif
endif
endif
# 使用include 包含另一个makefile
include common.mk
$(info [INCLUDE] 已包含 common.mk)
# include common_a.mk
# Android.mk:80: common_a.mk: 文件或目录不存在
# make: *** 没有规则可制作目标“common_a.mk”。 停止。
-include common_b.mk
# 没有common_b.mk
# 使用sinclude 安静地包含另一个makefile
sinclude optional.mk
$(info [SINCLUDE] 尝试包含 optional.mk)
# 展示 override 和 :=,= 的区别
MY_VAR := original_value
MY_LAZY_VAR = $(MY_VAR)
override MY_VAR = new_value
$(info [OVERRIDE] MY_VAR 原来设置为 original_value,但现在是 $(MY_VAR))
$(info [LAZY EVALUATION] MY_LAZY_VAR 的值是 $(MY_LAZY_VAR))
# 使用export 导出变量到子进程
export MY_VAR
$(shell echo [SHELL EXPORT] shell中的 MY_VAR 的值是 $(MY_VAR) > shell_output.txt)
$(info [EXPORT] 检查 shell_output.txt 查看在 shell 中 MY_VAR 的值)
# 使用unexport 阻止之前导出的变量在子进程中被访问
unexport MY_VAR
$(shell echo [SHELL UNEXPORT] shell中的 MY_VAR 的值是 $(MY_VAR) > shell_unexport_output.txt)
$(info [UNEXPORT] 检查 shell_unexport_output.txt 查看在 shell 中 MY_VAR 的值在取消导出后)
# 使用vpath 设置搜索路径
vpath %.cpp src
FOUND_FILES := $(wildcard src/*.cpp)
$(info [VPATH] 找到的 .cpp 文件: $(FOUND_FILES))
TARGET_ARCH := arm
LOCAL_MODULE_TAGS :=lrt
# 使用ifeq 结构
ifeq ($(TARGET_ARCH),arm)
# 如果目标架构为arm,则执行这里的代码块
LOCAL_CFLAGS += -DARM
LOCAL_SRC_FILES := src/arm_specific.cpp
endif
ifeq ($(HOST_OS),linux)
# 如果主机操作系统为linux,则执行这里的代码块
LOCAL_LDLIBS += -lrt
endif
# 使用ifdef 结构
ifdef LOCAL_MODULE_TAGS 
# 如果LOCAL_MODULE_TAGS 已经定义,则执行这里的代码块
LOCAL_CFLAGS += -D$(LOCAL_MODULE_TAGS)
endif
ifdef LOCAL_JNI_SHARED_LIBRARIES 
# 如果LOCAL_JNI_SHARED_LIBRARIES 已经定义,则执行这里的代码块
include $(CLEAR_VARS)
LOCAL_MODULE := $(LOCAL_JNI_SHARED_LIBRARIES)-copy
LOCAL_SRC_FILES := $(TARGET_OUT_INTERMEDIATE_LIBRARIES)/$(LOCAL_JNI_SHARED_LIBRARIES).so
include $(BUILD_PREBUILT)
endif
# 使用else 结构
ifeq ($(TARGET_ARCH),arm)
# 如果目标架构为arm,则执行这里的代码块
LOCAL_CFLAGS += -DARM
else # 添加一个分支
# 如果目标架构不为arm,则执行这里的代码块
LOCAL_CFLAGS += -DOTHER_ARCH
endif
ifdef LOCAL_MODULE_TAGS 
# 如果LOCAL_MODULE_TAGS 已经定义,则执行这里的代码块
LOCAL_CFLAGS += -D$(LOCAL_MODULE_TAGS)
else # 添加一个分支
# 如果LOCAL_MODULE_TAGS 未定义,则执行这里的代码块
$(error LOCAL_MODULE_TAGS is not defined)
endif
# 使用elif 结构
ifeq ($(TARGET_ARCH),arm)
# 如果目标架构为arm,则执行这里的代码块
LOCAL_CFLAGS += -DARM
LOCAL_SRC_FILES := src/arm_specific.cpp
else ifeq ($(TARGET_ARCH),x86)
# 如果目标架构为x86,则执行这里的代码块
LOCAL_CFLAGS += -DX86
LOCAL_SRC_FILES := src/x86_specific.cpp
else ifeq ($(TARGET_ARCH),mips)
# 如果目标架构为mips,则执行这里的代码块
LOCAL_CFLAGS += -DMIPS
LOCAL_SRC_FILES := src/mips_specific.cpp
else # 可选,添加一个分支
# 如果目标架构都不是arm、x86、mips,则执行这里的代码块
$(error Unknown target arch: $(TARGET_ARCH))
endif 
$(warning ====== 测试结束 ======)
all:
  @echo "测试example03结束!"

1. 条件语句

条件语句用于根据条件执行不同的操作,它有以下几种结构:

  • ifeq:这个结构用于判断两个字符串是否相等,如果相等则执行后面的代码块,否则跳过。它的一般形式为:
# 测试ifeq
# 比较两个相同的字符串
ifeq (apple,apple)
$(info [IFEQ] apple 和 apple 是相同的)
endif

或者

# 比较两个不同的字符串
ifeq "banana" "orange"
$(info [IFEQ] banana 和 orange 是相同的)
else
$(info [IFEQ] banana 和 orange 是不同的)
endif

例如:

# 使用ifeq 结构检查环境变量
ifeq ($(OS),Linux)
$(info [IFEQ] 操作系统是 Linux)
endif
ifeq "Linux" "Android"
# 如果Linux 和Android 相等,则执行这里的代码块(不会执行)
$(info Linux 和 Android 是相同的)
endif
  • ifneq:这个结构用于判断两个字符串是否不相等,如果不相等则执行后面的代码块,否则跳过。它的一般形式与ifeq 相同,只是将ifeq 替换为ifneq。例如:
# 测试ifneq
# 比较两个不同的字符串
ifneq (grape,lemon)
$(info [IFNEQ] grape 和 lemon 是不同的)
endif
# 使用ifneq 结构检查环境变量
ifneq ($(OS),Windows)
$(info [IFNEQ] 操作系统不是 Windows)
endif
  • ifdef:这个结构用于判断一个变量是否已经定义,如果已经定义则执行后面的代码块,否则跳过。它的一般形式为:
ifdef 变量名
# 如果变量名已经定义,则执行这里的代码块
endif

例如:

# 测试ifdef
# 定义一个变量
FRUIT := apple
ifdef FRUIT
$(info [IFDEF] FRUIT 已定义,其值是 $(FRUIT))
endif
  • ifndef:这个结构用于判断一个变量是否未定义,如果未定义,则执行后面的代码块,否则跳过。它的一般形式与ifdef 相同,只是将ifdef 替换为ifndef。例如:
# 测试ifndef
# 检查一个未定义的变量
ifndef VEGETABLE
$(info [IFNDEF] VEGETABLE 未定义)
endif
  • else:这个结构用于在条件语句中添加一个分支,如果前面的条件不成立,则执行后面的代码块。例如:
# 使用else 结构
# 检查环境变量
ifeq ($(OS),Linux)
$(info [ELSE] 操作系统是 Linux)
else
$(info [ELSE] 操作系统不是 Linux)
endif
  • elif:这个结构用于在条件语句中添加一个或多个分支,如果前面的条件不成立,但后面的条件成立,则执行后面的代码块。它的一般形式为:
if... # 条件语句的开始
# 如果条件成立,则执行这里的代码块
elif... # 添加一个或多个分支
# 如果条件成立,则执行这里的代码块
else # 可选,添加一个分支
# 如果条件不成立,则执行这里的代码块
endif # 条件语句的结束

例如:

# 使用嵌套的ifeq 和 else 来模拟elif
# 检查多个可能的操作系统值
ifeq ($(OS),Linux)
$(info [ELIF] 操作系统是 Linux)
else
ifeq ($(OS),Windows)
$(info [ELIF] 操作系统是 Windows)
else
ifeq ($(OS),Mac)
$(info [ELIF] 操作系统是 Mac)
else
$(info [ELIF] 操作系统是未知的)
endif
endif
endif

2. 控制语句

控制语句用于控制代码块的执行流程,有以下几种控制关键字可供选择:

  • include:这个关键字用于包含其他的Makefile 文件,它接受一个或多个文件名作为参数,它会将这些文件中的内容插入到当前位置。例如:
# 使用include 关键字
include $(CLEAR_VARS) # 包含CLEAR_VARS 文件,用于清空一些变量
include $(BUILD_EXECUTABLE) # 包含BUILD_EXECUTABLE 文件,用于构建可执行模块
include $(BUILD_PACKAGE) # 包含BUILD_PACKAGE 文件,用于构建APK模块
# 使用include 包含另一个makefile
include common.mk
$(info [INCLUDE] 已包含 common.mk)

以下是一些常见的include变量:

  1. BUILD_STATIC_LIBRARY: 用于构建静态库(.a文件)。
  2. BUILD_SHARED_LIBRARY: 用于构建动态库(.so文件)。
  3. BUILD_HOST_EXECUTABLE: 用于构建在构建机上运行的可执行文件,而不是在目标设备上。
  4. BUILD_HOST_STATIC_LIBRARY: 用于构建在构建机上使用的静态库。
  5. BUILD_HOST_SHARED_LIBRARY: 用于构建在构建机上使用的动态库。
  6. BUILD_JAVA_LIBRARY: 用于构建Java库。
  7. BUILD_PREBUILT: 用于包含预先构建的二进制或其他文件。
  • -include:这个关键字与include 相同,只是在包含文件时不会报错,即使文件不存在或无法读取。例如:
# 使用-include 关键字
-include common_b.mk # 包含common_b.mk 文件,如果不存在或无法读取,不会报错
  • sinclude:这个关键字与-include 相同,只是使用了不同的拼写。例如:
# 使用sinclude 安静地包含另一个makefile
sinclude optional.mk
$(info [SINCLUDE] Tried to include optional.mk) # 包含optional.mk 文件,如果不存在或无法读取,不会报错
  • override:这个关键字用于覆盖命令行中指定的变量值,它接受一个赋值操作作为参数,它会将赋值操作中的变量值覆盖命令行中的变量值。例如:
# 展示 override 和 :=,= 的区别
MY_VAR := original_value
MY_LAZY_VAR = $(MY_VAR)
override MY_VAR = new_value
$(info [OVERRIDE] MY_VAR 原来设置为 original_value,但现在是 $(MY_VAR))
$(info [LAZY EVALUATION] MY_LAZY_VAR 的值是 $(MY_LAZY_VAR))
  • export:这个关键字用于导出变量到子进程中,它接受一个或多个变量名作为参数,它会将这些变量作为环境变量传递给子进程。例如:
# 使用export 关键字
export NDK_PROJECT_PATH := $(LOCAL_PATH) # 导出NDK_PROJECT_PATH 到子进程中,用于指定项目路径
export APP_ABI := armeabi-v7a # 导出APP_ABI 到子进程中,用于指定目标架构
# 使用export 导出变量到子进程
export MY_VAR
$(shell echo [SHELL EXPORT] shell中的 MY_VAR 的值是 $(MY_VAR) > shell_output.txt)
$(info [EXPORT] 检查 shell_output.txt 查看在 shell 中 MY_VAR 的值)
  • unexport:这个关键字用于取消导出变量到子进程中,它接受一个或多个变量名作为参数,它会将这些变量从环境变量中移除。例如:
# 使用unexport 阻止之前导出的变量在子进程中被访问
unexport MY_VAR
$(shell echo [SHELL UNEXPORT] shell中的 MY_VAR 的值是 $(MY_VAR) > shell_unexport_output.txt)
$(info [UNEXPORT] 检查 shell_unexport_output.txt 查看在 shell 中 MY_VAR 的值在取消导出后)
$(shell echo $$MY_VAR) # 在子进程中打印MY_VAR的值(为空)
  • vpath:这个关键字用于指定搜索路径,它接受一个或两个参数,第一个参数是匹配模式,第二个参数是搜索路径(可选),它会在搜索路径中查找匹配模式的文件。例如:
# 使用vpath 关键字
vpath %.c src # 指定在src 目录下查找以.c 结尾的文件
vpath %.java java # 指定在java 目录下查找以.java 结尾的文件
vpath AndroidManifest.xml manifest # 指定在manifest 目录下查找AndroidManifest.xml 文件
# 使用vpath 设置搜索路径
vpath %.cpp src
FOUND_FILES := $(wildcard src/*.cpp)
$(info [VPATH] 找到的 .cpp 文件: $(FOUND_FILES))

3. 条件控制的应用

条件控制在Android.mk 文件中有很多实际应用,例如:

  • ifeq:可以使用ifeq 结构来根据不同的平台或架构执行不同的操作,例如:
TARGET_ARCH := arm
LOCAL_MODULE_TAGS :=lrt
# 使用ifeq 结构
ifeq ($(TARGET_ARCH),arm)
# 如果目标架构为arm,则执行这里的代码块
LOCAL_CFLAGS += -DARM
LOCAL_SRC_FILES := src/arm_specific.cpp
endif
ifeq ($(HOST_OS),linux)
# 如果主机操作系统为linux,则执行这里的代码块
LOCAL_LDLIBS += -lrt
endif
  • ifdef:可以使用ifdef 结构来判断一些特定的变量是否已经定义,例如:
# 使用ifdef 结构
ifdef LOCAL_MODULE_TAGS 
# 如果LOCAL_MODULE_TAGS 已经定义,则执行这里的代码块
LOCAL_CFLAGS += -D$(LOCAL_MODULE_TAGS)
endif
ifdef LOCAL_JNI_SHARED_LIBRARIES 
# 如果LOCAL_JNI_SHARED_LIBRARIES 已经定义,则执行这里的代码块
include $(CLEAR_VARS)
LOCAL_MODULE := $(LOCAL_JNI_SHARED_LIBRARIES)-copy
LOCAL_SRC_FILES := $(TARGET_OUT_INTERMEDIATE_LIBRARIES)/$(LOCAL_JNI_SHARED_LIBRARIES).so
include $(BUILD_PREBUILT)
endif
  • else:可以使用else 结构来添加一个分支,以处理条件不成立的情况,例如:
# 使用else 结构
ifeq ($(TARGET_ARCH),arm)
# 如果目标架构为arm,则执行这里的代码块
LOCAL_CFLAGS += -DARM
else # 添加一个分支
# 如果目标架构不为arm,则执行这里的代码块
LOCAL_CFLAGS += -DOTHER_ARCH
endif
ifdef LOCAL_MODULE_TAGS 
# 如果LOCAL_MODULE_TAGS 已经定义,则执行这里的代码块
LOCAL_CFLAGS += -D$(LOCAL_MODULE_TAGS)
else # 添加一个分支
# 如果LOCAL_MODULE_TAGS 未定义,则执行这里的代码块
$(error LOCAL_MODULE_TAGS is not defined)
endif
  • elif:可以使用elif 结构来添加一个或多个分支,以处理多种条件情况,例如:
# 使用elif 结构
ifeq ($(TARGET_ARCH),arm)
# 如果目标架构为arm,则执行这里的代码块
LOCAL_CFLAGS += -DARM
LOCAL_SRC_FILES := src/arm_specific.cpp
else ifeq ($(TARGET_ARCH),x86)
# 如果目标架构为x86,则执行这里的代码块
LOCAL_CFLAGS += -DX86
LOCAL_SRC_FILES := src/x86_specific.cpp
else ifeq ($(TARGET_ARCH),mips)
# 如果目标架构为mips,则执行这里的代码块
LOCAL_CFLAGS += -DMIPS
LOCAL_SRC_FILES := src/mips_specific.cpp
else # 可选,添加一个分支
# 如果目标架构都不是arm、x86、mips,则执行这里的代码块
$(error Unknown target arch: $(TARGET_ARCH))
endif

根据本文的代码例子,涉及的关键字进行总结成详细的表格:

关键字/结构 描述 示例
ifeq 比较两个字符串是否相等 ifeq ($(TARGET_ARCH),arm) 检查 TARGET_ARCH 是否为 arm
ifneq 比较两个字符串是否不相等 ifneq ($(OS),Windows) 检查 OS 是否不为 Windows
ifdef 检查一个变量是否已定义 ifdef LOCAL_MODULE_TAGS 检查 LOCAL_MODULE_TAGS 是否已定义
ifndef 检查一个变量是否未定义 ifndef VEGETABLE 检查 VEGETABLE 是否未定义
else ifeq, ifneq, ifdef, ifndef 提供一个分支 ifeq ($(OS),Linux) ... else ... endif
include 包含另一个 makefile 文件 include common.mk 包含 common.mk 文件
-include 安静地包含另一个 makefile 文件,即使它不存在也不会报错 -include common_b.mk 尝试包含 common_b.mk 文件
sinclude -include sinclude optional.mk 尝试包含 optional.mk 文件
override 覆盖之前定义的变量 override MY_VAR = new_value 覆盖 MY_VAR 的值
export 导出变量到子进程 export MY_VAR 导出 MY_VAR 以供子进程使用
unexport 阻止之前导出的变量在子进程中被访问 unexport MY_VAR 取消导出 MY_VAR
vpath 设置目录搜索路径 vpath %.cpp src 设置 .cpp 文件的搜索路径为 src 目录
$(shell ...) 执行 shell 命令 $(shell echo [SHELL EXPORT] MY_VAR in shell is $(MY_VAR) > shell_output.txt) 执行 shell 命令并将输出重定向到 shell_output.txt
$(wildcard ...) 查找匹配模式的文件 FOUND_FILES := $(wildcard src/*.cpp) 查找 src 目录下的所有 .cpp 文件
$(error ...) 抛出一个错误 $(error LOCAL_MODULE_TAGS is not defined) 如果 LOCAL_MODULE_TAGS 未定义,则抛出错误

4. 总结

本文介绍了Android.mk文件中条件控制的使用方法和一些常用的控制语句。通过使用条件控制,可以根据不同的情况执行不同的操作,可以实现复杂的逻辑。

相关文章
|
12天前
|
算法 JavaScript Android开发
|
14天前
|
安全 搜索推荐 Android开发
揭秘安卓与iOS系统的差异:技术深度对比
【10月更文挑战第27天】 本文深入探讨了安卓(Android)与iOS两大移动操作系统的技术特点和用户体验差异。通过对比两者的系统架构、应用生态、用户界面、安全性等方面,揭示了为何这两种系统能够在市场中各占一席之地,并为用户提供不同的选择。文章旨在为读者提供一个全面的视角,理解两种系统的优势与局限,从而更好地根据自己的需求做出选择。
36 2
|
13天前
|
安全 搜索推荐 程序员
深入探索Android系统的碎片化问题及其解决方案
在移动操作系统的世界中,Android以其开放性和灵活性赢得了广泛的市场份额。然而,这种开放性也带来了一个众所周知的问题——系统碎片化。本文旨在探讨Android系统碎片化的现状、成因以及可能的解决方案,为开发者和用户提供一种全新的视角来理解这一现象。通过分析不同版本的Android系统分布、硬件多样性以及更新机制的影响,我们提出了一系列针对性的策略,旨在减少碎片化带来的影响,提升用户体验。
|
13天前
|
安全 Android开发 iOS开发
深入探索iOS与Android系统的差异性及优化策略
在当今数字化时代,移动操作系统的竞争尤为激烈,其中iOS和Android作为市场上的两大巨头,各自拥有庞大的用户基础和独特的技术特点。本文旨在通过对比分析iOS与Android的核心差异,探讨各自的优势与局限,并提出针对性的优化策略,以期为用户提供更优质的使用体验和为开发者提供有价值的参考。
|
15天前
|
安全 Android开发 iOS开发
安卓系统与iOS系统的比较####
【10月更文挑战第26天】 本文将深入探讨安卓(Android)和iOS这两大主流移动操作系统的各自特点、优势与不足。通过对比分析,帮助读者更好地理解两者在用户体验、应用生态、系统安全等方面的差异,从而为消费者在选择智能手机时提供参考依据。无论你是技术爱好者还是普通用户,这篇文章都将为你揭示两大系统背后的故事和技术细节。 ####
36 0
|
Java 调度 Android开发
android体系课-系统启动流程-之zygote进程启动过程源码分析
笔者刚开始学习Android的时候也和大部分同学一样,只会使用一些应用层面的知识,对于一些比较常见的开源框架如<mark>RxJava</mark>,<mark>OkHttp</mark>,<mark>Retrofit</mark>,以及后来谷歌推出的<mark>协程</mark>等,都只在使用层面,对于他们<mark>内部原理</mark>,基本没有去了解觉得够用就可以了,又比如Activity,Service等四大组件的使用原理,系统开机过程,Launcher启动过程等知之甚少,知其然而不知其所以然,结果就是出现某些问题,不知道从哪里找原因,只能依赖万能的百度,但是百度看多了,你会发现自己
|
Java 调度 Android开发
android体系课-系统启动流程-之SystemServer启动过程源码分析
笔者刚开始学习Android的时候也和大部分同学一样,只会使用一些应用层面的知识,对于一些比较常见的开源框架如<mark>RxJava</mark>,<mark>OkHttp</mark>,<mark>Retrofit</mark>,以及后来谷歌推出的<mark>协程</mark>等,都只在使用层面,对于他们<mark>内部原理</mark>,基本没有去了解觉得够用就可以了,又比如Activity,Service等四大组件的使用原理,系统开机过程,Launcher启动过程等知之甚少,知其然而不知其所以然,结果就是出现某些问题,不知道从哪里找原因,只能依赖万能的百度,但是百度看多了,你会发现自己