交叉编译
最近由Kitware Robot编辑6个月前
页面历史记录
从版本2.6.0开始,CMake支持交叉编译。
交叉编译意味着该软件是为不同于构建的系统构建的。 这意味着
- CMake无法自动检测目标系统
- 通常,可执行文件不在构建主机上运行
- 构建过程必须使用一组不同的包含文件和库来构建,即不是本机的
设置系统和工具链
交叉编译时,CMake无法猜测目标系统,因此您必须预设一些CMake变量,例如: 用一个
toolchain file
. 必须预设以下变量:
- CMAKE_SYSTEM_NAME : 这是必需的,它是目标系统的名称,即与CMake将在目标系统上运行时的CMAKE_SYSTEM_NAME相同。典型的例子是“Linux”和“Windows”。此变量用于构造平台文件的文件名,如Linux.cmake或Windows-gcc.cmake。如果您的目标是没有OS的嵌入式系统,则将CMAKE_SYSTEM_NAME设置为“Generic”。如果预设了CMAKE_SYSTEM_NAME,则CMake变量CMAKE_CROSSCOMPILING将自动设置为TRUE,因此可用于在CMake文件中进行测试。 CMAKE_SYSTEM_VERSION:目标系统的可选版本,不常用。 CMAKE_SYSTEM_PROCESSOR:目标系统的可选,处理器(或硬件)。除了一个目的之外,此变量的使用不多,它用于加载CMAKE_SYSTEM_NAME-compiler-CMAKE_SYSTEM_PROCESSOR.cmake文件,该文件可用于修改目标的编译器标志等设置。如果您使用的是交叉编译器,每个目标硬件都需要特殊的构建设置,您可能只需要设置此项。
由于CMake无法猜测目标系统,因此无法猜测它应该使用哪个编译器,因此您也必须预设它:
- CMAKE_C_COMPILER : C编译器可执行文件,可以是完整路径,也可以只是文件名。 如果使用完整路径指定,则在搜索C ++编译器和其他工具(binutils,链接器等)时,首选此路径。 如果这个编译器是带有前缀名称的gcc-cross编译器(例如“arm-elf-gcc”),CMake将检测到这一点并自动找到相应的C ++编译器(即“arm-elf-c ++”)。 也可以通过CC环境变量预设编译器。 CMAKE_CXX_COMPILER:C ++编译器可执行文件,可以是完整路径,也可以只是文件名。 它的处理方式与CMAKE_C_COMPILER相同。 如果工具链是GNU工具链,您只需要设置其中一个。
一旦系统和编译器由CMake确定,它将按以下顺序加载相应的文件:
- Platform/${CMAKE_SYSTEM_NAME}.cmake (optional, but issues a stern warning)
- Platform/${CMAKE_SYSTEM_NAME}-.cmake (optional)
- Platform/${CMAKE_SYSTEM_NAME}–${CMAKE_SYSTEM_PROCESSOR}.cmake (optional)
是编译器可执行文件的基本名称,例如 “gcc”(如果gcc具有不同的名称也使用此选项)或“cl”,或者通过编译器ID来使用,编译器ID通过编译测试源文件来检测。
为了测试主机系统,有一组相应的变量,由CMake自动设置:
- CMAKE_HOST_SYSTEM_NAME
- CMAKE_HOST_SYSTEM_VERSION
- CMAKE_HOST_SYSTEM_PROCESSOR
- CMAKE_HOST_SYSTEM
没有交叉编译主机系统和目标系统的变量是相同的。 在大多数情况下,您将需要测试目标系统,然后与没有交叉编译的方式一样使用CMAKE_SYSTEM_xxx变量,这将适用于交叉编译和本机构建。
正确设置这些变量后,CMake现在将使用交叉编译工具链进行构建,在CMakeLists.txt中,您仍然可以使用CMAKE_SYSTEM_XXX变量来测试您正在构建的系统。 这已经足够使用CMake来交叉编译简单(构建系统)项目。
搜索和查找外部软件
大多数非平凡的项目将依赖于外部库或工具。 为此,CMake提供了FIND_PROGRAM(),FIND_LIBRARY(),FIND_FILE(),FIND_PATH()和FIND_PACKAGE()命令。 他们在常见位置搜索文件系统中的文件并返回结果。 FIND_PACKAGE()有点不同,它实际上不会搜索自身,但“仅”执行FindXXX.cmake模块,这些模块通常会调用FIND_PROGRAM(),FIND_LIBRARY(),FIND_FILE()和FIND_PATH()命令。
交叉编译时,例如 对于具有ARM处理器的目标获得/usr/lib/libjpeg.so作为FIND_PACKAGE(JPEG)的结果将没有多大帮助,因为这将是主机系统的JPEG库,例如, 一个x86 Linux盒子。 所以你需要告诉CMake在其他地方搜索。 这可以通过设置以下变量来完成:
- CMAKE_FIND_ROOT_PATH:这是一个目录列表,其中列出的每个目录都将被添加到每个FIND_XXX()命令的每个搜索目录中。所以例如如果目标环境安装在/ opt / eldk / ppc_74xx下,请将CMAKE_FIND_ROOT_PATH设置为此目录。然后例如FIND_LIBRARY(BZ2_LIB bz2)将在/ opt / eldk / ppc_74xx / lib,/ opt / eldk / ppc_74xx / usr / lib,/ lib,/ usr / lib中搜索,所以给/ opt / eldk / ppc_74xx / usr / lib / libbz2结果。默认情况下,CMAKE_FIND_ROOT_PATH为空。如果设置,首先将搜索以CMAKE_FIND_ROOT_PATH中给出的目录为前缀的目录,然后搜索搜索目录的未加前缀版本。这种行为可以单独为每个FIND_XXX被修改()与NO_CMAKE_FIND_ROOT_PATH,ONLY_CMAKE_FIND_ROOT_PATH和CMAKE_FIND_ROOT_PATH_BOTH选项或所有FIND_XXX()命令默认调用可以与CMAKE_FIND_ROOT_PATH_MODE_PROGRAM,CMAKE_FIND_ROOT_PATH_MODE_LIBRARY和CMAKE_FIND_ROOT_PATH_MODE_INCLUDE变量进行调整。如果您不想仅使用工具链附带的库,而且还要为目标平台构建和使用其他库,则应为这些包创建安装目录,例如: $ HOME / eldk-ppc_74xx-inst /并将其添加到CMAKE_FIND_ROOT_PATH,因此FIND_XXX()命令也会在那里搜索。如果您随后为目标平台构建软件包,则应将它们安装到此目录中。 CMAKE_FIND_ROOT_PATH_MODE_PROGRAM:这将设置FIND_PROGRAM()命令的默认行为。它可以设置为NEVER,ONLY或BOTH(默认)。如果设置为NEVER,则CMAKE_FIND_ROOT_PATH将不会用于FIND_PROGRAM()调用(除非明确启用它)。如果设置为ONLY,则只在FIND_PROGRAM()中使用前缀为CMAKE_FIND_ROOT_PATH的搜索目录。默认值为BOTH,这意味着首先是前缀目录,之后将搜索未加前缀的目录。在大多数情况下,FIND_PROGRAM()用于搜索可执行文件,然后执行该例程。使用EXECUTE_PROCESS()或ADD_CUSTOM_COMMAND()。因此,在大多数情况下,需要构建主机的可执行文件,因此通常将CMAKE_FIND_ROOT_PATH_MODE_PROGRAM设置为NEVER。 CMAKE_FIND_ROOT_PATH_MODE_LIBRARY:这与上面相同,但是对于FIND_LIBRARY()命令。在大多数情况下,这用于查找将用于链接的库,因此需要用于目标的库。因此,在常见情况下,将其设置为ONLY。 CMAKE_FIND_ROOT_PATH_MODE_INCLUDE:这与上面相同,用于FIND_PATH()和FIND_FILE()。在许多情况下,这用于查找包含目录,因此应搜索目标环境。因此,在常见情况下,将其设置为ONLY。您可能必须使用NO_CMAKE_FIND_ROOT_PATH,ONLY_CMAKE_FIND_ROOT_PATH和CMAKE_FIND_ROOT_PATH_BOTH选项为某些FIND_PATH()或FIND_FILE()调用调整此行为。
工具链文件
使用-DCMAKE_SYSTEM_NAME等定义上面提到的所有变量将非常繁琐且容易出错。 为了简化操作,您可以设置另一个cmake变量:
- CMAKE_TOOLCHAIN_FILE:cmake脚本的绝对或相对路径,它设置上面提到的所有工具链相关变量
例如,对于从PowerPC上的Linux到嵌入式Linux的交叉编译,此文件可能如下所示:
# 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)
如果此文件名为Toolchain-eldk-ppc74xx.cmake并且位于您的主目录中,并且您正在子目录内构建,那么您可以执行以下操作:
~/src$ cd build
~/src/build$ cmake -DCMAKE_TOOLCHAIN_FILE=~/Toolchain-eldk-ppc74xx.cmake ..
...
您不必为要构建的每个软件编写工具链文件,工具链文件是每个目标平台,即如果您为同一目标平台构建多个软件包,则只需编写一个工具链 文件,您可以将其用于所有包。
如果您的编译器默认情况下无法构建一个简单的程序而没有特殊的标志或文件(例如链接器脚本或内存布局文件),则上面显示的工具链文件不起作用。 然后你必须强制编译器:
INCLUDE(CMakeForceCompiler)
# this one is important
SET(CMAKE_SYSTEM_NAME eCos)
# specify the cross compiler
CMAKE_FORCE_C_COMPILER(arm-elf-gcc GNU)
CMAKE_FORCE_CXX_COMPILER(arm-elf-g++ GNU)
# where is the target environment
SET(CMAKE_FIND_ROOT_PATH /home/alex/src/ecos/install )
# 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)
这是使用CMAKE_FORCE_XXX_COMPILER()宏完成的。 第二个参数是编译器ID,CMake使用它来识别编译器。
使用mingw32为Win32进行交叉编译的工具链可能如下所示:
# the name of the target operating system
SET(CMAKE_SYSTEM_NAME Windows)
# which compilers to use for C and C++
SET(CMAKE_C_COMPILER i486-mingw32-gcc)
SET(CMAKE_CXX_COMPILER i486-mingw32-g++)
SET(CMAKE_RC_COMPILER i486-mingw32-windres)
# here is the target environment located
SET(CMAKE_FIND_ROOT_PATH /usr/i486-mingw32)
# adjust the default behaviour of the FIND_XXX() commands:
# search headers and libraries in the target environment, search
# programs in the host environment
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
系统内省
许多非平凡的软件项目都有一组系统内省测试,用于查找(目标)系统的属性。在CMake中,为此目的提供了宏,例如, CHECK_INCLUDE_FILES()或CHECK_C_SOURCE_RUNS()。大多数这些测试将在内部使用TRY_COMPILE()或TRY_RUN()CMake命令。 TRY_COMPILE()命令在交叉编译时也按预期工作,它们将尝试使用交叉编译工具链编译代码段,这将给出预期的结果。内部使用TRY_RUN()的所有测试都无法工作,因为创建的可执行文件无法在构建主机系统上运行。起初,TRY_RUN()尝试编译软件,这在交叉编译时将以相同的方式工作。如果成功,它将检查变量CMAKE_CROSSCOMPILING是否生成的可执行文件是否可运行。如果没有,它将创建两个缓存变量,然后必须由用户或通过CMake缓存设置。假设命令看起来像这样:
TRY_RUN(SHARED_LIBRARY_PATH_TYPE SHARED_LIBRARY_PATH_INFO_COMPILED
${PROJECT_BINARY_DIR}/CMakeTmp
${PROJECT_SOURCE_DIR}/CMake/SharedLibraryPathInfo.cxx
OUTPUT_VARIABLE OUTPUT
ARGS "LDPATH")
变量SHARED_LIBRARY_PATH_INFO_COMPILED将被设置为构建的结果(即TRUE或FALSE)。 CMake将创建一个缓存变量SHARED_LIBRARY_PATH_TYPE并将其预设为PLEASE_FILL_OUT-FAILED_TO_RUN。如果它已在目标上执行,则必须将此设置为可执行文件的退出代码。它还将创建一个缓存变量SHARED_LIBRARY_PATH_TYPE__TRYRUN_OUTPUT并将其预设为PLEASE_FILL_OUT-NOTFOUND。如果在目标上执行,则必须将此可执行文件设置为可执行文件打印到stdout和stderr的输出。仅当TRY_RUN()命令与RUN_OUTPUT_VARIABLE或OUTPUT_VARIABLE参数一起使用时,才会创建此变量。您必须为这些变量填写适当的值。为了帮助您,CMake尽力为您提供有用的信息。
为此,CMake创建了一个文件$ {CMAKE_BINARY_DIR} /TryRunResults.cmake。在那里,您将找到CMake无法确定的所有变量,从哪个CMake文件调用它们,源文件,可执行文件的参数以及可执行文件的路径。 CMake还将复制构建目录中的可执行文件,它们的名称为cmTryCompileExec-,例如: cmTryCompileExec-SHARED_LIBRARY_PATH_TYPE。然后,您可以尝试在实际目标平台上手动运行此可执行文件并检查结果。
获得这些结果后,他们必须进入CMake缓存。您可以使用ccmake / CMakeSetup /“make edit_cache”并直接在缓存中编辑变量。然后,您将无法在另一个构建目录中重用您的更改,或者如果您删除CMakeCache.txt。第二个选项是使用TryRunResults.cmake文件。将其复制到一个安全的位置(即如果删除构建目录,则不会删除它)并给它一个有用的名称,例如MyProjectTryRunResults-ELDK-ppc.cmake。然后编辑它,以便SET()命令设置所需的值。您可以使用此文件通过使用cmake的-C选项预加载CMake缓存:
src/build/ $ cmake -C ~/MyProjectTryRunResults-eldk-ppc.cmake .
您不必再次使用其他CMake选项,它们现在已经在缓存中。 这样,您可以在多个构建树中使用MyProjectTryRunResults-eldk-ppc.cmake,它也可以与您的项目一起分发,以便其他想要编译它的用户更容易。
This script
可能有助于自动将TRY_RUN结果与放置在目标上创建的CMakeCache.txt中的结果一起填充。
在构建期间创建的构建中使用可执行文件
在某些情况下,在构建期间会创建可执行文件,然后在同一构建过程中将其用于ADD_CUSTOM_COMMAND()或ADD_CUSTOM_TARGET()。
当交叉编译时,如果没有修改就无法工作,因为可执行文件无法在构建主机上运行。 从CMake 2.6开始,可以将可执行目标“导入”CMake项目。 当交叉编译时,必须使用将本机构建中构建的可执行文件导入到交叉构建中。 这可以这样做:
# when crosscompiling import the executable targets from a file
IF(CMAKE_CROSSCOMPILING)
SET(IMPORT_EXECUTABLES "IMPORTFILE-NOTFOUND" CACHE FILEPATH "Point it to the export file from a native build")
INCLUDE(${IMPORT_EXECUTABLES})
ENDIF(CMAKE_CROSSCOMPILING)
...
# only build the generator if not crosscompiling
IF(NOT CMAKE_CROSSCOMPILING)
ADD_EXECUTABLE(mygenerator mygen.cpp)
TARGET_LINK_LIBRARIES(mygenerator ${SOME_LIBS})
ENDIF(NOT CMAKE_CROSSCOMPILING)
# then use the target name as COMMAND, CMake >= 2.6 knows how to handle this
ADD_CUSTOM_COMMAND(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated.c
COMMAND mygenerator foo.dat -o ${CMAKE_CURRENT_BINARY_DIR}/generated.c
DEPENDS foo.dat )
...
# export the generator target to a file, so it can be imported (see above) by another build
# the IF() is not necessary, but makes the intention clearer
IF(NOT CMAKE_CROSSCOMPILING)
EXPORT(TARGETS mygenerator FILE ${CMAKE_BINARY_DIR}/ImportExecutables.cmake )
ENDIF(NOT CMAKE_CROSSCOMPILING)
因此,在本机构建期间,将在ADD_CUSTOM_COMMAND()中构建并使用目标“mygenerator”。 作为命令,仅使用目标名称。 CMake> = 2.6.0识别此并创建依赖项,并在执行命令时使用创建的可执行文件的路径。 最后调用EXPORT()函数(自CMake 2.6.0起),将列出的目标“导出”到文件$ {CMAKE_BINARY_DIR} /ImportExecutables.cmake,如下所示:
ADD_EXECUTABLE(mygenerator IMPORT)
SET_TARGET_PROPERTIES(mygenerator PROPERTIES
LOCATION /home/alex/build-native/bin/mygenerator )
然后在交叉编译时包含该文件,它必须使用-D或通过cmake GUI指定。 然后,排除了实际构建mygenerator的命令。 在ADD_CUSTOM_COMMAND()中,mygenerator将被识别为导入目标,并在执行命令时使用。
如果在交叉编译时也必须构建可执行的mygenerator,则需要添加更多逻辑,例如, 像这样:
# when crosscompiling import the executable targets from a file
IF(CMAKE_CROSSCOMPILING)
SET(IMPORT_EXECUTABLES "IMPORTFILE-NOTFOUND" CACHE FILEPATH "Point it to the export file from a native build")
INCLUDE(${IMPORT_EXECUTABLES})
ENDIF(CMAKE_CROSSCOMPILING)
...
# always build the executable
ADD_EXECUTABLE(mygenerator mygen.cpp)
TARGET_LINK_LIBRARIES(mygenerator ${SOME_LIBS})
# but use different names for the command
IF(CMAKE_CROSSCOMPILING)
SET(mygenerator_EXE native-mygenerator)
ELSE(CMAKE_CROSSCOMPILING)
SET(mygenerator_EXE mygenerator)
ENDIF(CMAKE_CROSSCOMPILING)
# then use the target name as COMMAND, CMake >= 2.6 knows how to handle this
ADD_CUSTOM_COMMAND(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated.c
COMMAND ${mygenerator_EXE} foo.dat -o ${CMAKE_CURRENT_BINARY_DIR}/generated.c
DEPENDS foo.dat )
...
# export the generator target to a file, so it can be imported (see above) by another build
# the IF() is not necessary, but makes the intention clearer
# use the NAMESPACE option of EXPORT() to get a different target name for mygenerator when exporting
IF(NOT CMAKE_CROSSCOMPILING)
EXPORT(TARGETS mygenerator FILE ${CMAKE_BINARY_DIR}/ImportExecutables.cmake NAMESPACE native- )
ENDIF(NOT CMAKE_CROSSCOMPILING)
Windows CE的交叉编译
构建Windows CE需要Visual Studio 2005或2008(No Express Edition!),并且至少安装了一个SDK。 如果您没有目标设备的特定安装文件,则可以使用Windows CE 5.0 Standard SDK
http://www.microsoft.com/downloads/details.aspx?familyid=fa1a3d66-3f61-4ddc-9510-ae450e2318c3
.
从版本2.8.10开始,当与NMake Makefiles生成器一起使用时,CMake支持Windows CE开箱即用。 要使用它,首先需要相应的环境变量集,在以下版本中添加了CMake命令env_vs8_wince。 如果手动设置环境,也可以使用2.8.10。 要到达那里,请启动命令提示符并键入以下命令:
"%VS80COMNTOOLS%vsvars32.bat"
cmake -E env_vs8_wince "STANDARDSDK_500 (ARMV4I)" > env.bat
env.bat
然后可以使用以下命令生成和构建Makefile:
cmake -G "NMake Makefiles" -DCMAKE_SYSTEM_NAME=WindowsCE -DCMAKE_SYSTEM_PROCESSOR=ARMV4I \path\to\source
cmake --build .
从CMake 2.8.11开始,还可以为Windows CE目标创建Visual Studio解决方案。 根据安装的SDK,CMake将接受其他生成器。 以下命令将为WinCE标准SDK创建Visual Studio 2005文件:
cmake -G "Visual Studio 8 2005 STANDARDSDK_500 (ARMV4I)" \path\to\source
要使用VS2008代替VS2005,请将“VS80COMNTOOLS”替换为“VS90COMNTOOLS”,将“vs8”替换为“vs9”,将“8 2005”替换为“9 2008”。
有关如何设置各种交叉编译工具链的信息
-
1
, 有关使用CMake for iPhone(外部,第三方网站)的详细说明。 -
eldk
,来自Denx的嵌入式Linux交叉编译工具链 -
mingw
– gcc用于从Linux到Windows的交叉编译 -
SDCC
-小型设备C编译器 -
eCos
– 嵌入式可配置操作系统 -
ADSP
– 用于DSP的ADI工具链 -
IBM BlueGene/L
-
Cray XT3 / Catamount
-
Crosstool NG
– 可用于轻松构建各种交叉编译工具链。 生产的工具链似乎与CMake交叉编译很好地协作。 -
MXE
– 构建编译器和库,以便从Linux到Windows进行交叉编译。 附带CMake工具链文件!
如何交叉编译特定项目
常见问题/潜在问题
在混合的32/64位Linux安装中,交叉编译不能仅用于构建32/64位。
- 依赖于执行像pkg-config这样的二进制工具的FindXXX.cmake模块可能会出现问题,因为目标平台的pkg-config无法在主机上执行。 像pkg-config这样的工具只能在FindXXX.cmake文件中使用。
- Scratchbox怎么样? CMake应该在scratchbox中没有问题,然后它将在纯模式下工作。
- 它可以为PS3构建软件吗? 如果您为PS3构建软件,则可以为PowerPC和单元构建两种体系结构的软件。 这是使用两个不同的工具链完成的。 目前,CMake不支持在一个构建树中使用多个工具链,也不支持在一个构建树中构建多个目标体系结构。 因此,为PS3构建并不是开箱即用的。 它应该使用ADD_CUSTOM_COMMAND()或使用两个构建树。