CMake交叉编译

CMake的使用,以及如何将一个项目移植到Android。

CMake的用法

先让我们简单学习回顾一下cmake的基本知识:

基本流程

以linux平台为例,使用 CMake 生成 Makefile 并编译的流程如下:

  • 编写 CMake 配置文件 CMakeLists.txt 。
  • 执行命令 cmake Path-to-Cmakelist/CMakeLists.txt 生成 Makefile。
  • 使用 make 命令进行编译。

语法

CMakeLists文件可以包含comments,commands,以及 空行。
注释以#开头
command:包含命令名字,括号,用空格分开的参数 comand(arg1 arg2 …)
所有的空白行都会被忽略。

基本的命令

  • project
    project:用来声明项目的名字,也可以指定项目的开发语言
    project(projectname [cxx] [c] [java] [none])
    如果没有指定语言,那么CMake默认指定为c和c++

在每一个project出现的CMakeLists.txt,CMake都会创建一个top level的IDE project文件。这个project会包括出现在该CMakeLists.txt中的所有的targets,以及它所有的subdirecotry, 用add_subdirectory命令来指定。

  • set
    set命令用来定义或者修改变量或者lists
    和set对应的remove命令

  • add_executable / add_library
    用来定义what executable/libs to build, 并且指定source 文件。

另外一些命令可以从这里找到:
https://cmake.org/cmake/help/v3.0/manual/cmake-variables.7.html
https://cmake.org/Wiki/CMake_Useful_Variables

关于cmake在普通项目中的用法,请参考这篇文章:cmake入门实践

交叉编译

现在移动开发越老越火,我们免不了会将一些项目porting到Android/iOS平台,这个时候就要用到交叉编译:即在你host宿主机上(例如你用的Ubuntu电脑)要生成target目标机(例如Android手机)的程序。在编译的过程中会涉及到相关头文件的切换和编译器的选择以及环境变量的改变等,今天就来看看CMake是如何来做交叉编译的。

首先需要明确以下几点:

  • CMake不能自动判断出目标机系统,需要我们指定。
  • 一般情况下Build出来的可执行文件是不能直接运行在宿主机上。
  • 编译过程中不能用宿主机上的原声头文件和库,而是需要用到一套不同的头文件和库。

CMAKE_TOOLCHIAIN_FILE

这个应该是CMake交叉编译中最重要的概念。正如前面提到过的,CMake不知道你的目标平台是什么、用什么编译起、如何编译等等,所以你需要提供预设一些变量到CMake,其中最为方便的一个方法就是将相关的变量设置都放进一个文件(cmake脚本)中去,然后将该文件通过CMAKE_TOOLCHIAIN_FILE传递给CMake, 例如:

cmake -D CMAKE_TOOLCHIAIN_FILE="/path/to/my-cmake-toolchain-file" ..

此处我们假设要执行的CMakeLists file在上一级目录中。

下面将要在这个文件中需要设置的几个重要的变量分两大类依次介绍一下。

设置目标系统以及Toolchain

  • CMAKE_SYSTEM_NAME:

在toolchain脚本中必须要设置的变量,只有当CMAKE_SYSTEM_NAME这个变量被设置了,CMake才认为此时正在交叉编译,它会额外设置一个变量CMAKE_CROSSCOMPILING为TRUE。

CMAKE_SYSTEM_NAME即目标机target所在的操作系统名称,比如ARM或者Linux你就需要写”Linux”,如果Android平台你就写”Android”,如果你的嵌入式平台没有相关OS你即需要写成”Generic”.

  • CMAKE_SYSTEM_PROCESSOR:

这个是可选项,但是在移动开发中很重要,代表目标系统的硬件或者CPU型号,例如ARM,X86 etc。

  • CMAKE_C_COMPILER:

即C语言编译器,这里可以将变量设置成完整路径或者文件名

  • CMAKE_CXX_COMPILER:

C++编译器。

搜索查找外部依赖

稍微大一点的项目都会用到一些外部依赖库或者tool,CMake提供了FIND_PROGRAM(), FIND_LIBRARY(), FIND_FILE(), FIND_PATH() and FIND_PACKAGE() 等命令来进行外部依赖的搜索查找。

但是有个问题,假如我们在给一个ARM处理器的移动设备做交叉编译,其中需要寻找libjpeg.so,假如FIND_PACKAGE(JPEG) 返回的是/usr/lib/libjpeg.so,那么这就会有问题,因为找到的这个so库只是给你的宿主机系统(例如一个x86的Ubuntu主机)服务的,不能用于Arm系统。所以你需要告诉CMake去其它地方去查找,这个时候你就咬配置以下的变量了:

  • CMAKE_FIND_ROOT_PATH:

代表了一系列的相关文件夹路径的根路径的变更,比如你设置了/opt/arm/,所有的Find_xxx.cmake都会优先根据这个路径下的/usr/lib,/lib等进行查找,然后才会去你自己的/usr/lib和/lib进行查找.

  • CMAKE_FIND_ROOT_PATH_MODE_PROGRAM:

对FIND_PROGRAM()起作用,有三种取值,NEVER,ONLY,BOTH,第一个表示不在你CMAKE_FIND_ROOT_PATH设置的目录下进行查找,第二个表示只在这个路径下查找,第三个表示先查找这个路径,再查找全局路径

  • CMAKE_FIND_ROOT_PATH_MODE_LIBRARY

对FIND_LIBRARY()起作用,表示在链接的时候的库的相关选项,因此这里需要设置成ONLY来保证我们的库是在交叉环境中找的.

一个小例子

附上一个CMake官方文档中的toolchian file的小例子,这样我们就会对如何写toolchain文件有了感性认识:

# this one is important
SET(CMAKE_SYSTEM_NAME Linux)
#this one not so much
SET(CMAKE_SYSTEM_VERSION 1)

# specify the cross compiler
SET(CMAKE_C_COMPILER   /opt/eldk-2007-01-19/usr/bin/ppc_74xx-gcc)
SET(CMAKE_CXX_COMPILER /opt/eldk-2007-01-19/usr/bin/ppc_74xx-g++)

# where is the target environment 
SET(CMAKE_FIND_ROOT_PATH  /opt/eldk-2007-01-19/ppc_74xx /home/alex/eldk-ppc74xx-inst)

# search for programs in the build host directories
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# for libraries and headers in the target directories
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

执行起来也很简单,如下:

~/src$ cd build
~/src/build$ cmake -DCMAKE_TOOLCHAIN_FILE=~/Toolchain-eldk-ppc74xx.cmake ..

实战:CMake项目的Android porting

接下来我们谈谈Androi平台上的交叉编译,google为我们提供了NDK用来做Native(C/C++)的build, 这其中包括编译器,debugger等。

所以我们如果想用CMake来交叉编译Android的native库或者应用的时候,需要做以下的事情:

  • 指明target平台为Android:

    set( CMAKE_SYSTEM_NAME Android )
    
  • 从ndk安装目录搜寻依赖的库和头文件。
  • 用ndk提供的编译器。
  • 指明ndk level version
  • 指明目标硬件是ARM还是X86.

可以看到,一大堆的东西需要去设置,幸好,我们有Android-cmake项目可以帮忙,它帮我们设置了大部分的事情,我们需要做的就是指定cmake的toolchain file为这个项目里面的android.toolchain.cmake文件,然后设置好NDK路径即可:

#  Usage Linux:
#   $ export ANDROID_NDK=/absolute/path/to/the/android-ndk
#   $ mkdir build && cd build
#   $ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/the/android.toolchain.cmake ..
#   $ make -j8

如果有时间的话,我会写一个实际项目中的例子来看看如何将一个项目通过cmake迁移到Android。

Reference