Android.mk 极简入门

  • 适合对象
  • Android.mk 简介
  • Android.mk 符号
  • 自定义变量
  • GNU Make 系统变量
  • 模块描述变量(LOCAL_XXX)
  • 编译module模板
  • 参考资料

一、适合对象

   本博客仅适合刚接触Android.mk的新人,简单上手所作。另外本人只想做一个纯粹的仅适合嵌入式C,不涉及 Java 的Android.mk粗略讲解,适合建立一个初步的概念框架,绝不涉及过多更为细节的内容,小伙伴也可以直接看编译module模板一节,做到快速上手。追求更详细内容的讲解,可以看CSDN 其他大佬博主的Android.mk详解,非常详细全面。

二、Android.mk 简介

  Android.mk文件用来告知NDK Build 系统关于Source 的信息,个人理解即是链接头文件、源文件、各类库来编译模块,其中无需额外添加依赖关系。由于Android.mk中所有的变量都是全局的,所以请尽量减少在Android.mk声明的变量,也不要认为某些变量在解析过程中不会被定义。

  对于C/C++ 可编译的模块类型:

module 说明
C/C++应用程序 可执行的 C/C++应用程序
C/C++静态库 编译生成C/C++静态库,并打包生成 .a文件
C/C++共享库 编译生成共享库,也可以是动态链接库, 两者只是调用库的方式不同,并打包成 .so文件

  编译指令:

  1. 在编译模块前, 在android 目录下, 执行 $. ./build/envsetup.sh
  2. 在Android.mk所在目录下,执行 $mm , 开始编译模块

  我们可以在每一个Android.mk中定义一个或多个模块, 也可以在几个模块中使用同一个源代码文件。编译系统会为你处理许多细节问题,比如你无需在Android.mk中列出头文件和生成的文件之间的显式依赖关系,编译系统将会为你自动处理这些问题。

三、Android.mk 符号

符号 作用
:= 赋值
+= 追加
$ 引用某变量的值
\ ‘\’和 Enter键配合来换行,可以连接同一变量上下行的文件

  ‘\’ 使用实例

LOCAL_SRC_FILES:= \
            main.c \
            yan.c \
            pwm.c 

ps: 注意一行中’\‘后不能有空格,否则会编译出错;最后一个文件后尽量避免添加不必要的’\’,以免误将非空格的无关的下一行添加入变量。另外若一行足够,同一行的文件之间可以用空格键或Tab键进行分割。

四、自定义变量

  在Android.mk中允许自定义变量,但是需要避开NDK编译系统已保留下来的变量名成分。
  以下是要避开的变量名成分:
   1. 以LOCAL_开头的名字,例如LOCAL_MODULE
   2. 以PRIVATE_, NDK_, 或APP_开头的名字(内部使用)
   3. 小写名字(内部使用),如" my_dir"

  为了方便在Android.mk中定义自己的变量,建议使用"MY_ "作为前缀。

五、GNU Make 系统变量

  这些GNU Make变量在你的Android.mk文件解析之前,就由编译系统定义好了。注意在某些情况下, NDK可能分析Android.mk几次, 每一次某些变量的定义会有不同。
 1) CLEAR_VARS
  指向一个编译脚本,几乎所有未定义的LOCAL_XXX都在"Module-description"节中列出。必须在开始一个新模块之前定义这个脚本: include $(CLEAR_VARS),用于重置除LOCAL_PATH变量外的所有LOCAL_XXX系列变量。

 2) BUILD_SHARED_LIBRARY
  指向编译脚本,把在include $(CLEAR_VARS)之后,include $(BUILD_SHARED_LIBRARY)之前所有的LOCAL_XXX变量列出的源代码文件编译成一个共享库, 即生成一个libxxx.so文件。

 3) BUILD_STATIC_LIBRARY
  用于编译一个静态库,编译的源文件方法同BUILD_SHARED_LIBRARY。静态库不会复制到APK中, 但是能够用静态库编译共享库。

 4) BUILD_EXECUTABLE
   用于编译一个可执行程序,如include $(BUILD_EXECUTABLE),生成可执行的 C/C++应用程序。

 5) TARGET_ARCH
  目标CPU架构的名字,和android开放源码中指定的那样。如果是arm,表示要生成ARM兼容的指令,与CPU架构的修订版无关。

 6) TARGET_PLATFORM:
  目标Android.mk平台的名字,详情可参考/development/ndk/docs/stable- apis.txt。
  android-3 -> Official Android 1.5 system images
  android-4 -> Official Android 1.6 system images
  android-5 -> Official Android 2.0 system images

六、模块描述变量(LOCAL_XXX)

  下面的变量用于向编译系统描述你的模块,应该定义在 “include $(CLEAR_VARS) " 和"include $(BUILD_XXX)” 之间。正如前面描述的一样, include $(CLEAR_VARS)用于重置除LOCAL_PATH变量外的所有LOCAL_XXX系列变量。

 1) LOCAL_PATH
   用于给出当前需编译文件所在的路径,必须在 Android.mk的开头定义,使用方法如 LOCAL_PATH := $(call_my_dir),则$(LOCAL_PATH) 为当前 Android.mk所在的路径。这个变量不会被include $(CLEAR_VARS)清除,因此在每个Android.mk文件开头定义一次即可,即使该文件中已经定义了几个模块(LOCAL_MOUDEL)。

 2)LOCAL_MOUDEL
  给定模块的名字,必须是唯一的,且不含空格,必须在任一包含 $(BUILD_XXX) 的脚本前定义它。模块的名字决定了生成文件的名字。例如,一个共享库的模块的名字为libvp_vpu ,则生成的文件为libvp_vpu.so。另外此时共享库模块对应的LOCAL_MODULE的名字最好能有lib前缀,因为有的 Android.mk并不会自动生成前缀lib ,以致不能正确调用共享库编译可执行程序,编译出错。

 3)LOCAL_MODULE_PATH
  指定生成模块的路径,可和LOCAL_MODULE搭配使用。 若未设置,则生成的模块在编译系统默认的生成路径。用法如 LOCAL_MODULE_PATH := $(LOCAL_PATH)。

 4)LOCAL_SRC_FILES
  给定要编译的源代码文件列表,文件也可以是静态库,或共享库。只要列出要传递给编译器的文件,则编译系统就能自动计算文件之间的依赖关系。注意给定的源代码文件都相对于 $(LOCAL_PATH),在该路径下。也可以选择添加路径部分,例如 LOCAL_SRC_FILES := taobao/foo.c ,使用的是$(LOCAL_PATH)相对路径, 绝对路径为$(LOCAL_PATH)/taobao/。

 5)LOCAL_STATIC_LIBRARIES
  表示该模块需要使用哪些静态库,以便在编译时进行链接。

 6)LOCAL_SHARED_LIBRARIES
  表示该模块在编译时要依赖的共享库,在链接时就需要,以便在生成文件时嵌入其相应的信息,但不像静态库那样把程序代码加入。所以程序执行的开始,也要有相应的共享库和对应的库路径。 简单的库路径添加命令: $export LD_LIBRARY_PATH=/…/…/…/,也可以选择放在/system/lib/等lib目录下方便加载。

 7)LOCAL_C_INCLUDES
  这是一个可选变量,很常用,基本都会用到,表示头文件的搜索路径。默认的头文件搜索路径是 $(LOCAL_PATH)目录。如果要添加其他路径的头文件,添加路径的方法和 LOCAL_SRC_FILES一样。

 8)LOCAL_CFLAGS
  可选的编译器选项,在编译 C 代码文件的时候使用。这可能是有用的,指定一个附加的包含路径(相对于NDK的顶层目录),宏定义,或者编译选项。
  注意:不要在 Android.mk中改变 optimization/debugging 级别,只要在 Application.mk中指定合适的信息,就会自动地为你处理这个问题,在调试期间,会让 NDK自动生成有用的数据文件。

ps: 以下是C++的内容,不需要的可以跳过
 9)LOCAL_CPP_EXTENSION
  这同样是一个可选变量,用来指定C++代码文件的扩展名,默认是".cpp", 但是可以改变扩展名,比如LOCAL_CPP_ENTENSION := .cxx ,则可以用.cxx的扩展名,本人尚未尝试使用。

 10)LOCAL_CXXFLAGS
  与LOCAL_CFLAGS同理,针对C++源文件。

 11)LOCAL_CPPFLAGS
  与LOCAL_CFLAGS同理,对C和C++ 源文件都适用。

七、编译module模板

  其实编译各模块的模板都很相似,光看前面是不易看出究竟是编译什么模块的,然而在模块编译的最后一句可以明确区分是编译哪种类型的模块。

模块 模块类型确定
C/C++应用程序 include $(BUILD_EXECUTABLE)
C/C++静态库 include $(BUILD_STATIC_LIBRARY)
C/C++共享库 include $(BUILD_SHARED_LIBRARY)

 1. 编译C/C++ 应用程序模板
  备注: 带#的变量是可选项

#Android.mk 开头初始化,
#确定当前编译文件路径以及重置除LOCAL_PATH以外的 LOCAL_XXX变量
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
	
#生成的模块名,可选是否设置模块存放的路径,我选择放在当前路径下
LOCAL_MODULE :=   XXX
#LOCAL_MODULE_PATH :=  $(LOCAL_PATH)
          
#需要的材料,分别是源文件(必选)、静态库.a文件(可选)、共享库.so文件(可选)、头文件(可选)
#若未设置路径,则材料默认的搜索路径为 $(LOCAL_PATH),在该目录下
#可定义路径,绝对路径如 $(TOP)/hardware/rog.c 
#或相对于 $(LOCAL_PATH)的路径,hardware/rog.c 等效于 $(LOCAL_PATH)/hardware/rog.c 
        
LOCAL_SRC_FILES := main.c  \
			Xxx.c  \
			xxx.c
			
#LOCAL_STATIC_LIBRARIES :=
#LOCAL_SHARED_LIBRARIES :=
#LOCAL_C_INCLUDES := 
          
#生成可执行程序
include $(BUILD_EXECUTABLE)

 ps: C/C++执行模块的目标文件夹为 XXX_intermediates,XXX是模块名 。

 2. 编译C/C++静态库

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
	
#生成的静态库模块名,将生成xxx.a文件
LOCAL_MODULE := XXX
#LOCAL_MODULE_PATH := $(LOCAL_PATH)
          
#需要的材料,分别是源文件(必选)、静态库(可选)、共享库(可选)、头文件(可选)        
LOCAL_SRC_FILES :=    Xxx.c  \
                xxx.c
	
#LOCAL_STATIC_LIBRARIES :=
#LOCAL_SHARED_LIBRARIES :=
#LOCAL_C_INCLUDES := 
          
#生成C/C++静态库
include $(BUILD_STATIC_LIBRARY)

  ps: C/C++ 静态库模块的目标文件夹为 XXX_static_intermediates,或者XXX_intermediates,我的是后者,编译系统版本不同会有差异, XXX是模块名。这个在 Android.mk编译生成过程中会有显示。

 3. 编译C/C++ 共享库

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
	
#生成的模块名,xxx最好能有lib前缀,生成libxxx.so文件,以适应不同版本的Android.mk
LOCAL_MODULE :=   XXX
#LOCAL_MODULE_PATH :=  $(LOCAL_PATH)
          
#需要的材料,分别是源文件(必选)、静态库(可选)、共享库(可选)、头文件(可选) 
LOCAL_SRC_FILES := Xxx.c \
				   xxx.c 
	
#LOCAL_STATIC_LIBRARIES :=
#LOCAL_SHARED_LIBRARIES :=
#LOCAL_C_INCLUDES := 
	
#生成C/C++ 共享库
include $(BUILD_SHARED_LIBRARY)

 ps: 生成的目标文件夹名带 "_intermediates"结尾,在编译过程中有显示,版本不同则文件夹路径和文件夹名会有差异,根据实际的目录查找即可。同时静态库可用来编译共享库,则LOCAL_SRC_FILES := Xxx.a。

  当然也可以在一个Android.mk编译多个模块,模块之间Include $(CLEAR_VARS) 和Include $(BUILD_)相隔, 其中LOCAL_PATH 只需在Android.mk开头定义一次即可,后续不必重复定义。

附本人的Android.mk实例:

#是否要编译库, true 确定编译库, false 不编译库,选择编译执行程序
COMPILE_LIB := true     
LOCAL_PATH:= $(call my-dir)

ifeq ($(COMPILE_LIB),true)
include $(CLEAR_VARS)

LOCAL_MODULE:= libvp_yan
LOCAL_MODULE_PATH := $(LOCAL_PATH)
LOCAL_CFLAGS += -DVERSION=4.3

LOCAL_SRC_FILES:= \
          VP_Yan.c \
          VP_Pwm.c \
          VP_Hdu.c 

LOCAL_SHARED_LIBRARIES := \
    libhardware \
    liblog 

LOCAL_C_INCLUDES += \
    hardware/libhardware/include \
    $(TOP)/hardware/log \
    $(LOCAL_PATH)/hfile   #有头文件位于当前编译路径的hfile子文件夹下
    
include $(BUILD_SHARED_LIBRARY)

else
#编译可执行程序vp_yin
include $(CLEAR_VARS)

LOCAL_CFLAGS += -DVERSION=4.3
LOCAL_MODULE:= vp_yin
LOCAL_MODULE_PATH := $(LOCAL_PATH)

LOCAL_SRC_FILES:= \
			VP_Main.c
                     
LOCAL_SHARED_LIBRARIES := \
      libhardware \
      liblog \
      libvp_yan          #添加入true选项编译的libvp_yan.so 共享库

LOCAL_C_INCLUDES += \
     hardware/libhardware/include \
    $(TOP)/hardware/log \
    $(LOCAL_PATH)/hfile

include $(BUILD_EXECUTABLE)

endif

本博客大量参考了下列博主的文章
1. Android NDK学习 <二> Android.mk的制作
2. Android.mk的用法
3. Android.mk 中的静态库和共享库
4. android.mk 的理解和使用
5. android.mk 详解

猜你喜欢

转载自blog.csdn.net/Mark_minGE/article/details/82899224
今日推荐