1.Makefile & Android build
system
在进行讲述Android编译系统之前,应该先了解一下编译时所使用的Makefile,或者说复习下这方面的知识,这样才能更好的了解Android build system的原理。
1.1.Makefile
1.1.1.Makefile的规则
首先介绍Makefile的规则:
target ... : prerequisites
...
command
...
target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label)。prerequisites就是要生成那个target所需要(依赖)的文件或是目标。
command也就是make需要执行的命令(任意的Shell命令)。这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。
这就是Makefile的规则。也就是Makefile中最核心的内容,Android编译系统符合GNU
make的标准,当然这也是Android编译系统最核心的内容。
1.1.2.编译helloworld
下面用一个最简单的例子来说明这个规则,当然就是我们最喜欢写的helloworld,创建一个简单的helloworld.c,下面就是编译helloworld的makefile:
helloworld : helloworld.o
cc -o helloworld helloworld .o
helloworld.o : helloworld.c
cc -c main.c
clean:
rm helloworld helloworl.o
执行make就可以编译helloworld.c了,执行make clean就可以清除编译结果了(其实就是删除helloworld helloworl.o)。
1.2.Android build
ystem
Makefile文件用来告诉make命令需要怎么样的去编译和链接程序。在编译时,需要根据编译环境和编译目标选择编译工具,编译参数,以及选择编译安装哪些模块。同时Makefile指定了构建目标所需的依赖性以及生成规则。
在Android中,主要的Makefile文件存在于build/core/目录下,它的表现形式为多个后缀为*.mk的文件组成,也称为build
system。Android build
system主要有两大部分构成:配置部分和目标构建部分。
Build
system的主流程文件为build/core/main.mk文件,有它以及所需要的其它*.mk文件共同完成一次Build的任务。
下面以表格的形式概要描述几个重要的*.mk的文件的作用:
Android.mk
module/package的设置文件,每个module/package的目录下都会有一个Android.mk
AndroidProducts.mk
即为Android
build system提供给厂商的接口文件,通过此文件即可定义所需编译和安装的packages,默认为generic
base_rules.mk
对一些Makefile的变量规则化
BoardConfig.mk
是为product主板做设定,例如driver选择设定,选择CPU架构等等
Binary.mk
控制如何生成目标文件
buildspec.mk
位于根目录下,可在此选择要产生的product 、平台、额外的module/package等。build/buildspec.mk.default是样板
Clear_vars.mk
清除编译系统中用到的临时变量
Copy_headers.mk
将头文件拷贝到指定目录
config.mk
定义了编译目标程序所需的工具链及编译参数
definations.mk
定义了很多编译系统中用到的宏,相当于函数库
envsetup.mk
初始化编译环境,定义一些实用的shell函数,方便编译
main.mk
实际的主控Makefile,例如找到TOP目录下所有Android.mk文件
Makefile
辅助main.mk
主要控制生成 system.img,ramdisk.img,userdata.img等
build/envsetup.sh
提供了几个有用的命令,如:执行.
build/envsetup.sh
Combo/linux-arm.mk
控制如何生成linux-arm二进制文件,包括ARM相关的编译器,编译参数等的设置
2.Android.mk
Android.mk是Android编译系统中最重要的一个文件,下面将详细介绍:
2.1.概述
Android.mk在编译中起着至关重要的作用,这其实就是Android编译环境中的makefile。Android.mk文件是为了向生成系统描述你的源代码。更明确的说:这个文件实际上是GNU Make文件的一小片段,它会被生成系统解析一次或多次。因此,你应该在Android.mk里尽量少地声明变量。
下面是在NDK中Android.mk网页中引用的一段:
An Android.mk file is
written to describe your sources to thebuild system. More
specifically:
1. The file is really a tiny
GNU Makefile fragment that will be parsed one or more times by the
build system. As such, you should try to minimize the variables you
declare there and do not assume that anything is not defined during
parsing.
2. The file syntax is
designed to allow you to group your sources into 'modules'. A
module is one of the following:
- a static library
- a shared library
Only shared libraries will
be installed/copied to your application package. Static libraries
can be used to generate shared libraries though.You can define one
or more modules in each Android.mk file, and you can use the same
source file in several modules.
3. The build system handles
many details for you. For example, you don't need to list header
files or explicit dependencies between generated files in your
Android.mk. The NDK build system will compute these automatically
for you. This also means that, when updating to newer releases of
the NDK, you should be able to benefit from new toolchain/platform
support without having to touch your Android.mk files.
2.2.详细说明
首先,对这些变量的命名做一说明:
LOCAL_XXX变量:在每个module中都要设置以LOCAL_开头的变量。它们会被include $(CLEAR_VARS)命令来清除,你会在你的很多module中使用这些LOCAL_开头的变量。
PRIVATE_XXX变量:这些变量是make-target-specific(编译具体目标)的变量。
INTERNAL_XXX变量:这些变量是编译系统所使用的变量,所以你最好不要将你的变量用此来命名,而且最好也不要在你的makefile中见到这些变量。
HOST_XXX变量和TARGET_XXX变量:这些变量包含或者定义了一些特定的host或者target编译。所以不要在你的makefile中设置以开头HOST_和TARGET_的变量。
BUILD_XXX变量和CLEAR_VARS:这些变量都包含在那些清晰的makefile模板中了,比如CLEAR_VARS和BUILD_HOST_PACKAGE。
当然,你还可以在你的Android.mk文件中使用其它你所需要的命名变量。但是,要记住的是Android是一个非递归的编译系统,所以很有可能,你的变量可能会被其它的Android.mk改变,当你的module执行命令是,这些变量可能已经不同了。
说明:为保证可参考性,也将英语原文记录如下。
But first, a note on
variable naming:
LOCAL_
These variables are set
per-module. They are cleared by the include $(CLEAR_VARS) line, so
you can rely on them being empty after including that file. Most of
the variables you'll use in most modules are LOCAL_
variables.
PRIVATE_
These variables are
make-target-specific variables. That means they're only usable
within the commands for that module. It also means that they're
unlikely to change behind your back from modules that are included
after yours. This link to the make documentation describes more about
target-specific variables. Please note that there are a couple of
these laying around the tree that aren't prefixed with PRIVATE_. It
is safe, and they will be fixed as they are discovered. Sorry for
the confusion.
INTERNAL_
These variables are critical to
functioning of the build system, so you shouldn't create variables
named like this, and you probably shouldn't be messing with these
variables in your makefiles.
HOST_
and TARGET_ These
contain the directories and definitions that are specific to either
the host or the target builds. Do not set variables that start with
HOST_ or TARGET_ in your makefiles.
BUILD_
and CLEAR_VARS These
contain the names of well-defined template makefiles to include.
Some examples are CLEAR_VARS and BUILD_HOST_PACKAGE.
Any other name is fair-game
for you to use in your Android.mk. However, remember that this is a
non-recursive build system, so it is possible that your variable
will be changed by another Android.mk included later, and be
different when the commands for your rule / module are
executed
下面对Android.mk文件中涉及到的变量做详细说明:
2.2.1.LOCAL_XXX变量
LOCAL_XXX变量用于向编译系统描述你的模块中所使用的变量,你应该在include $(CLEAR_VARS)和include $(BUILD_XXXXX)语句之间来定义你想要使用的变量。
下面来详细说明以LOCAL_开头的变量:
LOCAL_ASSET_FILES
LOCAL_ASSET_FILES在Android.mk文件中编译应用程序(BUILD_PACKAGE)时设置此变量,表示引用资源文件,通常会定义成:
LOCAL_ASSET_FILES += $(call
find-subdir-assets)
说明:为保证可参考性,也将英语原文记录如下(下同,不再说明)。
In Android.mk files that
include $(BUILD_PACKAGE) set this to the set of files you want
built into your app. Usually:
LOCAL_ASSET_FILES += $(call
find-subdir-assets)
This will probably change
when we switch to ant for the apps' build system.
LOCAL_CC
如果你想在你的module中使用不同的C编译器,可以设置这个变量。如果LOCAL_CC是空的,它就使用默认的编译器。
If you want to use a
different C compiler for this module, set LOCAL_CC to the path to
the compiler. If LOCAL_CC is blank, the appropriate default
compiler is used.
LOCAL_CXX
如果你想在你的module中使用不同的C++编译器,可以设置这个变量。如果LOCAL_CXX是空的,它就使用默认的编译器。
If you want to use a
different C++ compiler for this module, set LOCAL_CXX to the path
to the compiler. If LOCAL_CXX is blank, the appropriate default
compiler is used.
LOCAL_CFLAGS
LOCAL_CFLAGS变量为C/C++编译器定义额外的标志,当编译C/C++源文件时传递一个可选的编译器标志,这对于指定额外的宏定义或编译选项很有用。例如:
LOCAL_CFLAGS +=
-DLIBUTILS_NATIVE=1
------分隔符,方便下次编辑修改------
If you have additional flags
to pass into the C or C++ compiler, add them here. For
example:
LOCAL_CFLAGS +=
-DLIBUTILS_NATIVE=1
重要提示:尽量不要改变Android.mk中的优化/调试级别,这个可以通过在Application.mk中设置相应的信息来自动为你处理,并且会会让NDK生成在调试过程中使用的有用的数据文件。注意:在Android-ndk-1.5_r1中,只使用于C源文件,而不适用于C++源文件。在匹配所有Android build system的行为已经得到了纠正。(现在你可以为C++源文件使用LOCAL_CPPFLAGS来指定标志)它可以用LOCAL_CFLAGS +=
-I来指定额外的包含路径,然而,如果使用LOCAL_C_INCLUDES会更好,因为用ndk-gdk进行本地调试的时候,那些路径依然是需要使用的。
LOCAL_CPPFLAGS
LOCAL_CPPFLAGS变量和LOCAL_CFLAGS变量类似,如果你只想要增加一些标记(flag)在你的C++编译器中,使用LOCAL_CPPFLAGS变量来增加它们,例如:
LOCAL_CPPFLAGS +=
-ffriend-injection
LOCAL_CPPFLAGS必须在LOCAL_CFLAGS变量命令行的后面使用,所以你可以用它来重写你在LOCAL_CFLAGS定义的标记。
If you have additional flags
to pass into only the C++ compiler, add them here. For
example:
LOCAL_CPPFLAGS +=
-ffriend-injection
LOCAL_CPPFLAGS is guaranteed
to be after LOCAL_CFLAGS on the compile line, so you can use it to
override flags listed in LOCAL_CFLAGS.
注意:在Android
NDK-1.5_r1版本中,相应的标志可以应用于C或C++源文件上。在配合完整的Android build system的时候,这已经得到了纠正(你可以使用LOCAL_CFLAGS去指定C或C++源文件)。
LOCAL_CPP_EXTENSION
如果你的C++文件不是以cpp为文件后缀,通过LOCAL_CPP_EXTENSION指定C++文件后缀名例如:
LOCAL_CPP_EXTENSION :=
.cc
需要注意的是在module中给出的所有的C++文件必须具有相同的扩展名,它是不允许混合使用不同扩展名的。
If your C++ files end in
something other than ".cpp", you can specify the custom extension
here. For example:
LOCAL_CPP_EXTENSION :=
.cc
Note that all C++ files for
a given module must have the same extension; it is not currently
possible to mix different extensions.
注意:统一模块中C++文件后缀必须保持一致。
LOCAL_C_INCLUDES
LOCAL_C_INCLUDES变量可以指定额外的目录来指引C/C++编译器来寻找头文件。这些路径必须是最顶端的目录路径,使用LOCAL_PATH来包含你的子目录路径下的文件,例如:
LOCAL_C_INCLUDES +=
extlibs/zlib-1.2.3
LOCAL_C_INCLUDES +=
$(LOCAL_PATH)/src
注意:
如果你在代码(main.c)中引用了一些头文件,而在编译时如果找不到这些头文件,就会报如下图的错误:
所以要确保你所包含的路径目录下,有你所需要的头文件,以避免编译时出现错误。
Additional directories to
instruct the C/C++ compilers to look for header files in. These
paths are rooted at the top of the tree. Use LOCAL_PATH if you have
subdirectories of your own that you want in the include paths. For
example:
LOCAL_C_INCLUDES +=
extlibs/zlib-1.2.3
LOCAL_C_INCLUDES +=
$(LOCAL_PATH)/src
You should not add
subdirectories of include to LOCAL_C_INCLUDES, instead you should
reference those files in the #include statement with their
subdirectories. For example:
#include
not #include
There are some components
that are doing this wrong, and should be cleaned up.
补充1:
如果在你的头文件目录include下有以下*.h文件:
/include/
utils/ KeyedVector.h
log.h
你不应该在LOCAL_C_INCLUDES变量中包含子目录,相反,你应该在使用#include来声明这些文件的引用,以及它们的子目录路径。例如:
#include
而不是:
#include
如果你想引用log.h文件,那么你应该这么写:
#include
补充2:
如果你在编译JNI时,在你的JNI代码中要引用jni.h时,既当你的代码(helloneon.c)中写道:
#include
如果你没有在该目录下的Android.mk中定义:
# Also need the JNI
headers
LOCAL_C_INCLUDES +=
$(JNI_H_INCLUDE)
那么就会在编译时报如下图所示的错误:
如果你能预先定义该变量的值,那么就不会出现上述的错误。
LOCAL_MODULE_TAGS
可以用空白的空格来分开一些标签,可以设置LOCAL_MODULE_TAGS,如果这个标签列表是空的或者包含有droid,这个module就会当作droid来编译,否则,它只能用make来编译和安装你的module,或者使用make all
Set LOCAL_MODULE_TAGS to any
number of whitespace-separated tags. If the tag list is empty or
contains droid, the module will get installed as part of a make
droid. Otherwise, it will only get installed by running make
or with the make all
pseudotarget.
补充:LOCAL_MODULE_TAGS:模块标记,一般的取值范围为debug、eng、test、optional,如果不定义则默认为optional。对这几个模式的解释为:user:指该模块只在user版本下才编译;eng:指该模块只在eng版本下才编译;tests:指该模块只在tests版本下才编译;optional:指该模块在所有版本下都编译。
LOCAL_REQUIRED_MODULES
可以用空格来分开不同的module的名字,以用来设置LOCAL_REQUIRED_
MODULES,像"libblah"或者"Email"。如果安装了这个module,那么同时也会安装这个module所必需的模块。确保所需要的共享库和必须已经安装好,当所给的程序安装时。
Set LOCAL_REQUIRED_MODULES
to any number of whitespace-separated module names, like "libblah"
or "Email". If this module is installed, all of the modules that it
requires will be installed as well. This can be used to, e.g.,
ensure that necessary shared libraries or providers are installed
when a given app is installed.
LOCAL_FORCE_STATIC_EXECUTABLE
如果编译的可执行程序要进行静态链接(执行时不依赖于任何动态库),则设置:
LOCAL_FORCE_STATIC_EXECUTABLE:=true
目前只有libc有静态库形式,这个只有文件系统中/sbin目录下的应用程序会用到,这个目录下的应用程序在运行时通常文件系统的其它部分还没有加载,所以必须进行静态链接。
If your executable should be
linked statically, set
LOCAL_FORCE_STATIC_EXECUTABLE:=true.
There is a very short list
of libraries that we have in static form (currently only libc).
This is really only used for executables in /sbin on the root
filesystem.
LOCAL_JAVA_LIBRARIES
LOCAL_JAVA_LIBRARIES编译java应用程序和库的时候指定包含的java类库,目前有core和framework两种情况下定义成:
LOCAL_JAVA_LIBRARIES := core
framework
注意:LOCAL_JAVA_LIBRARIES不是必须的,而且编译APK时不允许定义(系统会自动添加)
When linking Java apps and
libraries, LOCAL_JAVA_LIBRARIES specifies which sets of java
classes to include. Currently there are two of these: core and
framework. In most cases, it will look like this:
LOCAL_JAVA_LIBRARIES := core
framework
Note that setting
LOCAL_JAVA_LIBRARIES is not necessary (and is not allowed) when
building an APK with "include $(BUILD_PACKAGE)". The appropriate
libraries will be included automatically.
LOCAL_LDFLAGS
你可以通过设置LOCAL_LDFLAGS来增加额外的标记传递给连接器。不过要记住,这些参数对ld来说是非常重要的,所以你最好在所有的平台都测试下。
You can pass additional
flags to the linker by setting LOCAL_LDFLAGS. Keep in mind that the
order of parameters is very important to ld, so test whatever you
do on all platforms.
LOCAL_LDLIBS
LOCAL_LDLIBS允许你在你编译你的可执行程序或者库的时候,添加一些指定的额外的库。用lxxx的格式来指定你要引用的库,它们会被连接命令行直接解析。然而,要知道它不会为这些库生成附属。这在使用模拟器编译而又想使用库预编译在主机上时是非常有用的。例如:
LOCAL_LDLIBS += -lcurses
-lpthread
LOCAL_LDLIBS +=
-Wl,-z,origin
------分隔符,方便下次编辑修改------
LOCAL_LDLIBS allows you to
specify additional libraries that are not part of the build for
your executable or library. Specify the libraries you want in -lxxx
format; they're passed directly to the link line. However, keep in
mind that there will be no dependency generated for these
libraries. It's most useful in simulator builds where you want to
use a library preinstalled on the host. The linker (ld) is a
particularly fussy beast, so it's sometimes necessary to pass other
flags here if you're doing something sneaky. Some
examples:
LOCAL_LDLIBS += -lcurses
-lpthread
LOCAL_LDLIBS +=
-Wl,-z,origin
补充1:
LOCAL_LDLIBS:生成你的模块时用到的额外的连接器标记(linkerflags)的名单,在传递有“-l”前缀的特殊系统库的名称时很有用。例如:
LOCAL_LDLIBS :=
-labc
上面的语句会告诉连接器在load
time时生成连接到/system/lib/目录下名字叫做libabc.so的动态库。
补充2:
如果在你的Android.mk代码中定义了LOCAL_LDLIBS变量,例如:
LOCAL_LDLIBS := -lGLESv1_CM
-ldl -llog
但是,如果在编译的过程中找不到这个库,或者说这个库并没有存在与你编译环境下,那么在编译的时候就会出现如下图所示的错误:
上图的错误表示在编译到SHARED_LIBRARIES这一步(既要生成libsanangeles.so共享库时),在链接的过程中(既在LINKED这一步时)遇到了错误。
所以,在对LOCAL_LDLIBS变量赋值时,要确保其正确性以及存在性,避免出现上图的编译错误。