想知道“魔笛手”在这里能发挥什么作用吗?想象一下,把 CMake 当做法力高强的魔笛手,C++ 的项目则是故事中的那些被魔笛手拯救的孩子。
父母要抚养一个孩子并非易事,营养需要面面俱到,保证身体健康,关心事无巨细,确保快乐成长。其难度不亚于 C++ 项目的交叉编译,但一个保姆就能让事情变得更简单。类似地,我们也有 CMake 来简化交叉编译中的烦琐事。
交叉编译到底是什么?
以一个著名的应用程序为例,比如 Microsoft Paint。在它成为可执行文件之前,是一组源代码文件。编译源代码并将目标代码链接到单个可执行文件,这是编译器的工作。在正常情况下:
Microsoft Paint 的源代码是一组C++文件,在 Microsoft windows 中使用 Visual Studio 编译器进行编译。现在来看交叉编译场景:
因此,当源代码编译器对应的操作系统与其当前所在的操作系统不同时,就会发生交叉编译。这张图应该更清楚一点:
现在,交叉编译这个术语从何而来已经很明显了。跨平台软件开发并不容易,因为每个操作系统都有自己的特点。为 Windows 编写的源代码通常不能为 Linux 编译,反之亦然。这就是 Qt 等框架和 POSIX 等标准发挥作用的地方。
我不能忍受简单编译吗?
不总是这样。举个例子,一个开发者正在为 Android 开发的应用程序,他会利用开发机器(Windows、Linux 或 Mac)的计算能力为 Android 交叉编译应用程序。如果开发人员将目标锁定为 Raspberry Pi 这样的平台,情况也是如此。比起要在一台低性能机器上耗费大量时间运行编译,在功能强大的开发机器上设置交叉编译环境并将二进制文件复制到 Raspberry Pi,则容易得多。
交叉编译的另一个用例,是使用目标机器的本机编译器生成依赖项的复杂性。例如,以 Chromium(Microsoft Edge 浏览器和 Google Chrome 浏览器的基础)这样一个在多个平台上都受支持的开源项目为例。由于存在多个依赖项,因此不建议尝试以本机方式构建 Chromium。更确切地说,本地镜像是一个 Linux 容器,承载 GNU GCC 来交叉编译源代码。
能举个交叉编译的例子吗?
Windows 10 桌面操作系统有两种不同的风格:
- Windows 10, Intel CPUs ( 32 Bit 和 64 Bit 版本)
- Windows 10,ARM CPUs (ARM32 和ARM64 Bit 版本)
为 Intel CPU 编译的程序与 ARM CPU 不兼容,反之亦然。Microsoft Visual Studio 附带以下命令工具,其中一些用于交叉编译:
- VS2015 x64 ARM Cross Tools Command
- VS2015 x64 Native Tools Command
- VS2015 x64 x86 Cross Tools Command
- VS2015 x86 ARM Cross Tools Command
- VS2015 x86 Native Tools Command
- VS2015 x86 x64 Cross Tools Command
我们对交叉编译工具很感兴趣。让我们试着创建一个简单的 “Hello, World!” C++ 程序:
#include int main(int argc, char** argv){std::cout << “Hello, World!” << std::endl;return 0;}
在 x64 本机命令行上,编译上述程序:
在 x64 ARM cross tools 命令提示符下,编译相同的程序:
特别注意 /machine:arm 输出。编译成功并生成了可执行文件,但从运行可执行文件可以看出,它不适用于当前计算机:
恭喜!你已经在不同的机器上成功交叉编译了最简单 C++ 程序。
为什么需要交叉编译?
为同一操作系统支持的不同 CPU 进行编译是交叉编译的最简单形式。如果你想使用 Linux 并希望生成支持 Windows 的可执行文件,该怎么办?如果可能的话,最好使用相同的代码库来生成 Linux 和 Windows 可执行文件。
我们来试一下。我有一个 Windows 10 的机器,运行了一个适用于 Linux 的Windows 子系统,并且在这个系统上安装了 Ubuntu。简单输入:
sudo apt-get install mingw-w64
将为 Windows 安装 mingw 交叉编译器工具链。我们创建 CMakeLists.txt 文件:
#set minimum cmake versioncmake_minimum_required(VERSION 3.5 FATAL_ERROR)
#project name and languageproject(HelloWorld LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)set(CMAKE_CXX_EXTENSIONS OFF)set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(GNUInstallDirs)set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY
C
M
A
K
E
B
I
N
A
R
Y
D
I
R
/
{CMAKE_BINARY_DIR}/
CMAKEBINARYDIR/{CMAKE_INSTALL_LIBDIR})set(CMAKE_LIBRARY_OUTPUT_DIRECTORY
C
M
A
K
E
B
I
N
A
R
Y
D
I
R
/
{CMAKE_BINARY_DIR}/
CMAKEBINARYDIR/{CMAKE_INSTALL_LIBDIR})set(CMAKE_RUNTIME_OUTPUT_DIRECTORY
C
M
A
K
E
B
I
N
A
R
Y
D
I
R
/
{CMAKE_BINARY_DIR}/
CMAKEBINARYDIR/{CMAKE_INSTALL_BINDIR})
#define executable and its source fileadd_executable(HelloWorld main.cpp)
为CMake创建一个工具链文件,它告诉我们一些关于交叉编译工具链的信息。像这样:
#the name of the target operating systemset(CMAKE_SYSTEM_NAME Windows)
#which compilers to useset(CMAKE_C_COMPILER i686-w64-mingw32-gcc)set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)
#adjust the default behavior of the find commands:# search headers and libraries in the target environmentset(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
#search programs in the host environmentset(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
将此文件另存为 cross-compilation.cmake,并将其保存在 CMakeLists.txt 文件(见上文)和 Main.cpp. 创建一个名为“build”的新文件夹并更改到该目录。现在发出命令:
cmake … -DCMAKE_TOOLCHAIN_FILE=…/cross-compilation.cmakecmake –build .
这将会创建一个 HelloWorld.exe,就像 build/bin 文件夹中的 CMakeLists.txt 中指定的那样。我们取得了什么成就?我们为 Windows 创建了一个完全在 Linux 下运行的可执行文件。不同的工具链文件和编译器应允许相同的 CMakeLists.txt 文件为多个平台创建的目标。
使用 CMake 进行跨平台软件开发
CMake 是一个很好的跨平台软件开发工具。它使用一组称为工具链的实用程序来驱动构建。在构建中使用 CMake 有两种主要场景:
- CMake 负责选择工具链的普通构建
- 用户指定工具链文件的跨平台构建
上面的简单示例演示了如何创建工具链文件,并通知 CMake 使用该工具链文件来驱动构建。在实际的跨平台软件开发中,相关工作人员会仔细选择有助于这种开发的框架,例如 Qt。
另外,如果你想了解更多关于 CMake 的信息,请阅读我们的博客 CMake vs Make。
结论
魔笛手的故事有一个圆满的结局吗?一些版本说魔笛手把孩子们带到了一个美丽的地方。CMake 作为跨平台软件开发的一个选择,也应该带来这样一个美好的结局。
点击了解 Incredibuild 加速 C/C++ 构建编译的解决方案,并获取试用 License!