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文件中条件控制的使用方法和一些常用的控制语句。通过使用条件控制,可以根据不同的情况执行不同的操作,可以实现复杂的逻辑。

相关文章
|
1月前
|
人工智能 搜索推荐 物联网
Android系统版本演进与未来展望####
本文深入探讨了Android操作系统从诞生至今的发展历程,详细阐述了其关键版本迭代带来的创新特性、用户体验提升及对全球移动生态系统的影响。通过对Android历史版本的回顾与分析,本文旨在揭示其成功背后的驱动力,并展望未来Android可能的发展趋势与面临的挑战,为读者呈现一个既全面又具深度的技术视角。 ####
|
29天前
|
IDE Java 开发工具
移动应用与系统:探索Android开发之旅
在这篇文章中,我们将深入探讨Android开发的各个方面,从基础知识到高级技术。我们将通过代码示例和案例分析,帮助读者更好地理解和掌握Android开发。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和技巧。让我们一起开启Android开发的旅程吧!
|
16天前
|
监控 Java Android开发
深入探索Android系统的内存管理机制
本文旨在全面解析Android系统的内存管理机制,包括其工作原理、常见问题及其解决方案。通过对Android内存模型的深入分析,本文将帮助开发者更好地理解内存分配、回收以及优化策略,从而提高应用性能和用户体验。
|
18天前
|
存储 安全 Android开发
探索Android系统的最新安全特性
在数字时代,智能手机已成为我们生活中不可或缺的一部分。随着技术的不断进步,手机操作系统的安全性也越来越受到重视。本文将深入探讨Android系统最新的安全特性,包括其设计理念、实施方式以及对用户的影响。通过分析这些安全措施如何保护用户免受恶意软件和网络攻击的威胁,我们希望为读者提供对Android安全性的全面了解。
|
23天前
|
安全 Android开发 iOS开发
深入探讨Android与iOS系统的差异及未来发展趋势
本文旨在深入分析Android和iOS两大移动操作系统的核心技术差异、用户体验以及各自的市场表现,进一步探讨它们在未来技术革新中可能的发展方向。通过对比两者的开放性、安全性、生态系统等方面,本文揭示了两大系统在移动设备市场中的竞争态势和潜在变革。
|
17天前
|
Java Android开发 开发者
探索安卓开发:构建你的第一个“Hello World”应用
在安卓开发的浩瀚海洋中,每个新手都渴望扬帆起航。本文将作为你的指南针,引领你通过创建一个简单的“Hello World”应用,迈出安卓开发的第一步。我们将一起搭建开发环境、了解基本概念,并编写第一行代码。就像印度圣雄甘地所说:“你必须成为你希望在世界上看到的改变。”让我们一起开始这段旅程,成为我们想要见到的开发者吧!
24 0
|
API 开发工具 Android开发
4.0、Android Studio配置你的构建
Android构建系统编译你的app资源和源码并且打包到APK中,你可以用来测试,部署,签名和发布。
1170 0
|
29天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
16天前
|
搜索推荐 前端开发 API
探索安卓开发中的自定义视图:打造个性化用户界面
在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
40 19
|
16天前
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
41 14