CMake Cross Compiling: Difference between revisions
No edit summary |
|||
Line 63: | Line 63: | ||
# specify the cross compiler | # specify the cross compiler | ||
SET(CMAKE_C_COMPILER /opt/eldk-2007-01-19/usr/bin/ppc_74xx-gcc) | 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 | # where is the target environment | ||
SET(CMAKE_FIND_ROOT_PATH /opt/eldk-2007-01-19/ppc_74xx) | 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 | # search for programs in the build host directories |
Revision as of 19:58, 17 July 2007
Cross compiling is supported by CMake starting with version 2.6.0 (not yet released as of July 2007).
Cross compiling means that the software is built for a different system than the one which does the build. This means
- CMake cannot autodetect the target system
- usually the executables don't run on the build host
- the build process has to use a different set of include files and libraries for building, i.e. not the native ones
Setting up the system and toolchain
Since CMake cannot guess the target system, you have to preset the following cmake variables:
- CMAKE_SYSTEM_NAME
- this one is mandatory, it is the name of the target system, i.e. the same as CMAKE_SYSTEM_NAME would have if CMake would run on the target system. Typical examples are "Linux" and "Windows". This variable is used for constructing the file names of the platform files like Linux.cmake or Windows-gcc.cmake. If your target is an embedded system without OS set CMAKE_SYSTEM_NAME to "Generic". If CMAKE_SYSTEM_NAME is preset, the CMake variable CMAKE_CROSSCOMPILING is automatically set to TRUE, so this can be used for testing in the CMake files.
- CMAKE_SYSTEM_VERSION
- optional, version of your target system, not used very much.
- CMAKE_SYSTEM_PROCESSOR
- optional, processor (or hardware) of the target system. This variable is not used very much except for one purpose, it is used to load a CMAKE_SYSTEM_NAME-compiler-CMAKE_SYSTEM_PROCESSOR.cmake file, which can be used to modify settings like compiler flags etc. for the target. You probably only have to set this one if you are using a cross compiler where every target hardware needs special build settings.
Since CMake cannot guess the target system, it also cannot guess which compiler it should use, so you have to preset this too:
- CMAKE_C_COMPILER
- the C compiler executable, may be the full path or just the filename. If it is specified with full path, then this path will be prefered when searching the C++ compiler and the other tools (binutils, linker, etc.). If this compiler is a gcc-cross compiler with a prefixed name (e.g. "arm-elf-gcc") CMake will detect this and automatically find the corresponding C++ compiler (i.e. "arm-elf-c++"). (this may also work for MS cross compilers). The compiler can also be preset via the CC environment variables.
- CMAKE_CXX_COMPILER
- the C++ compiler executable, may be the full path or just the filename. It is handled the same way as CMAKE_C_COMPILER. If the toolchain is a GNU toolchain, you only need to set one of both.
Once the system and the compiler are determined by CMake, it loads the corresponding files in the following order:
- Platform/${CMAKE_SYSTEM_NAME}.cmake (mandatory)
- Platform/${CMAKE_SYSTEM_NAME}-<compiler>.cmake (optional)
- Platform/${CMAKE_SYSTEM_NAME}-<compiler>-${CMAKE_SYSTEM_PROCESSOR}.cmake (optional)
<compiler> is either the basename of the compiler executable, e.g. "gcc" (this is also used if gcc has a different name) or "cl", or by a compiler id, which is detected by compiling a test source file.
For testing the host system, there is a corresponding set of variables, which is set automatically by CMake:
- CMAKE_HOST_SYSTEM_NAME
- CMAKE_HOST_SYSTEM_VERSION
- CMAKE_HOST_SYSTEM_PROCESSOR
- CMAKE_HOST_SYSTEM
Without cross compiling the variables for the host system and the target system are identical. In most cases you will want to test for the target system, then the same way as without cross compiling use the CMAKE_SYSTEM_xxx variables, this will work both for cross compiling and for native building.
With these variables correctly set, CMake will now use the cross compiling toolchain for building and in the CMakeLists.txt you can still use the CMAKE_SYSTEM_XXX variables for testing for which system you are building. This is already enough to use CMake for cross compiling simple (buildsystem-wise) projects.
Searching and finding external software
Most non-trivial projects will depend on external libraries or tools. CMake offers the FIND_PROGRAM(), FIND_LIBRARY(), FIND_FILE(), FIND_PATH() and FIND_PACKAGE() commands for this purpose. They search the file system in common places for files and return the results. FIND_PACKAGE() is a bit different in that it actually doesn't search itself, but "only" executes FindXXX.cmake modules, which usually call the FIND_PROGRAM(), FIND_LIBRARY(), FIND_FILE() and FIND_PATH() commands then.
When cross compiling e.g. for a target with an ARM processor getting /usr/lib/libjpeg.so as the result of a FIND_PACKAGE(JPEG) wouldn't be much of a help, since this would be the JPEG library for the host system, e.g. an x86 Linux box. So you need to tell CMake to search in other locations. This can be done by setting the following variables:
- CMAKE_FIND_ROOT_PATH
- this is a list of directories, each of the directories listed there will be prepended to each of the search directories of every FIND_XXX() command. So e.g. if your target environment is installed under /opt/eldk/ppc_74xx, set CMAKE_FIND_ROOT_PATH to this directory. Then e.g. FIND_LIBRARY(BZ2_LIB bz2) will search in /opt/eldk/ppc_74xx/lib, /opt/eldk/ppc_74xx/usr/lib, /lib, /usr/lib and so give /opt/eldk/ppc_74xx/usr/lib/libbz2.so as result. By default CMAKE_FIND_ROOT_PATH is empty. If set, at first the directories prefixed with the directories given in CMAKE_FIND_ROOT_PATH will be searched and after that the unprefixed versions of the search directories will be searched. This behaviour can be modified individually for every FIND_XXX() call with the NO_CMAKE_FIND_ROOT_PATH, ONLY_CMAKE_FIND_ROOT_PATH and CMAKE_FIND_ROOT_PATH_BOTH options.
- CMAKE_FIND_ROOT_PATH_MODE_PROGRAM
- This sets the default behaviour for the FIND_PROGRAM() command. It can be set to NEVER, ONLY or BOTH (default). If set set NEVER, CMAKE_FIND_ROOT_PATH will not be used for FIND_PROGRAM() calls (except where it is enabled explicitely). If set to ONLY, only the search directories with the prefixes coming from CMAKE_FIND_ROOT_PATH will be used in FIND_PROGRAM(). The default is BOTH, which means that at first the prefixed directories and after that the unprefixed directories will be searched. In most cases FIND_PROGRAM() is used to search for an executable which will then be executed e.g. using EXECUTE_PROCESS() or ADD_CUSTOM_COMMAND(). So in most cases an executable from the build host is required, so usually set CMAKE_FIND_ROOT_PATH_MODE_PROGRAM to NEVER.
- CMAKE_FIND_ROOT_PATH_MODE_LIBRARY
- This is the same as above, but for the FIND_LIBRARY() command. In most cases this is used to find a library which will then be used for linking, so a library for the target is required. So in the common case, set it to ONLY.
- CMAKE_FIND_ROOT_PATH_MODE_INCLUDE
- This is the same as above and used for both FIND_PATH() and FIND_FILE(). In many cases this is used for finding include directories, so the target environment should be searched. So in the common case, set it to ONLY. You may have to adjust this behaviour for some of the FIND_PATH() or FIND_FILE() calls using the NO_CMAKE_FIND_ROOT_PATH, ONLY_CMAKE_FIND_ROOT_PATH and CMAKE_FIND_ROOT_PATH_BOTH options.
The toolchain file
Defining all the variables mentioned above using -DCMAKE_SYSTEM_NAME etc. would be quite tedious and error prone. To make things easier, there is another cmake variable you can set:
- CMAKE_TOOLCHAIN_FILE
- absolute or relative path to a cmake script which sets up all the toolchain related variables mentioned above
For instance for crosscompiling from Linux to Embedded Linux on PowerPC this file could look like this:
# 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)
If this file is named Toolchain-eldk-ppc74xx.cmake and is located in your home directory and you are building in the subdirectory build then you can do:
~/src$ cd build ~/src/build$ cmake -DCMAKE_TOOLCHAIN_FILE=~/Toolchain-eldk-ppc74xx.cmake .. ...
You don't have to write a toolchain file for every piece of software you want to build, the toolchain files are per target platform, i.e. if you are building several software packages all for the same target platform, you have to write only one toolchain file and you can use this for all packages. platform.
If your compiler is not able to build a simple program by default without special flags or files (e.g. linker scripts or memory layout files), the toolchain file as shown above doesn't work. Then you have to force the compiler:
INCLUDE(CMakeForceCompiler) # this one is important SET(CMAKE_SYSTEM_NAME eCos) # specify the cross compiler CMAKE_FORCE_C_COMPILER(arm-elf-gcc GNU 4) 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)
This is done using the CMAKE_FORCE_XXX_COMPILER() macros. The second argument is the compiler id, which is used by CMake to recognize the compiler, and CMAKE_FORCE_C_COMPILER requires a third argument which specifies sizeof(void*) in bytes.
System introspection
Many non-trivial software projects will have a set of system introspection tests for finding out properties of the (target) system. In CMake there are e.g. macros provided like CHECK_INCLUDE_FILES() or CHECK_C_SOURCE_RUNS(). Most of these tests will internally use either the TRY_COMPILE() or the TRY_RUN() CMake commands. The TRY_COMPILE() commands still work as expected when cross compiling, they will try to compile the piece of software with the cross compiling toolchain, which will give the expected result. All tests using TRY_RUN() internally cannot work, since the executables produced cannot run on the build host system. At first TRY_RUN() tries to compile the software, which will work the same way when cross compiling. If this succeeded, it will check the variable CMAKE_CROSSCOMPILING whether the resulting executable is runnable or not. If not, it will create two cache variables, which then have to be set by the user or via the CMake cache. Let's say the command looks like this:
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")
The variable SHARED_LIBRARY_PATH_INFO_COMPILED will be set to the result of the build (i.e. TRUE or FALSE). CMake will create a cache variable SHARED_LIBRARY_PATH_TYPE and preset it to PLEASE_FILL_OUT-NOTFOUND. This one has to be set to the exit code of the executable if it would have been executed on the target. It will also create a cache variable SHARED_LIBRARY_PATH_TYPE__SHARED_LIBRARY_PATH_INFO_COMPILED __TRYRUN_OUTPUT and preset it to PLEASE_FILL_OUT-NOTFOUND. This one has to be set to the output the executable would have printed to stdout and stderr if it would have been executed on the target. This variable is only created if the TRY_RUN() command was used with the RUN_OUTPUT_VARIABLE or the OUTPUT_VARIABLE argument. You have to fill in appropriate values for these variables. To help you with this CMake tries its best to give you useful error messages, it will tell you which file was compiled, from which cmake file and what the arguments for running the executable would have been. This is given in the error message when the configure step of CMake ends AND in the help (or comment) for the cache variable (try pressing H in ccmake). You then have to take a look at the file and figure out what it would have done.
To make this easier you can also "guard" the TRY_RUN() commands so that they are not executed if the results are already preset. This means modifying your cmake files.
Using executables in the build created during the build
In some cases during a build executables are created which are then used in ADD_CUSTOM_COMMAND() or ADD_CUSTOM_TARGET() during the same build process.
When cross compiling this won't work without modifications because the executables cannot run on the build host. Starting with CMake 2.6 it is possible to "import" executable targets into a CMake project. When cross compiling this has to be used to import executables built in a native build into the cross-build. This can be done like this:
# 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)
So during the native build the target "mygenerator" will be built and used in ADD_CUSTOM_COMMAND(). As command only the target name is used. CMake >= 2.6.0 recognizes this and creates the dependencies and will use the path to the created executable when executing the command. At the end the EXPORT() function (since CMake 2.6.0) is called, which "exports" the listed targets to the file ${CMAKE_BINARY_DIR}/ImportExecutables.cmake, which will look like this:
ADD_EXECUTABLE(mygenerator IMPORT) SET_TARGET_PROPERTIES(mygenerator PROPERTIES LOCATION /home/alex/build-native/bin/mygenerator )
This file is then included when cross compiling, it either has to be specified using -D or via the cmake GUI. Then later on the command for actually building mygenerator is excluded. In ADD_CUSTOM_COMMAND() mygenerator will be recognized as an imported target and it will be used when executing the command.
If the executable mygenerator also has to be built when cross compiling, then some more logic needs to be added, e.g. like this:
# 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 PREFIX 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 PREFIX native- ) ENDIF(NOT CMAKE_CROSSCOMPILING)