17

When using CMake for cross compiling, one generally specifies a toolchain file via the CMAKE_TOOLCHAIN_FILE option. In GNU terminology, one can specify the host architecture toolset using this file. However, one can generally not expect to be able to execute anything built with this toolchain. So often enough, some build tools need to be compiled for the build architecture.

Consider the following setup. I have two source files genfoo.c and bar.c. During build, genfoo.c needs to be compiled and run. Its output needs to be written to foo.h. Then I can compile bar.c, which #include "foo.h". Since CMake defaults to using the host architecture toolchain, the instructions for bar.c are easy. But how do I tell it to use the build architecture toolchain for compiling genfoo.c? Simply saying add_executable(genfoo genfoo.c) will result in using the wrong compiler.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Helmut Grohne
  • 6,578
  • 2
  • 31
  • 67
  • As you have stated, in GNU terminology you would use the `configure` command to specify the host architecture. And you would have to run `configure` twice if you want to build for two different host environments. So I see two possibilities with CMake: use a build script that creates and executes the two environments needed (see [here](http://stackoverflow.com/questions/30999130/cmake-build-multiple-targets-in-different-build-directories)) or use [ExternalProject_Add()](https://cmake.org/cmake/help/v3.2/module/ExternalProject.html) and split your `CMakeLists.txt` into dedicated subprojects. – Florian Mar 23 '16 at 13:27
  • No, with autotools you simply use `CC_FOR_BUILD`. See `ax_cc_for_build`. – Helmut Grohne Mar 23 '16 at 13:39
  • Ok, didn't know that one. If I understand correctly we are talking about the use case described in [CMake Cross Compiling - Using executables in the build created during the build](https://cmake.org/Wiki/CMake_Cross_Compiling#Using_executables_in_the_build_created_during_the_build)? – Florian Mar 23 '16 at 13:52
  • @Florian I believe that you should make an answer with that link as it answers a huge part of my question. I don't like the approach taken, but it certainly solves the problem at hand. I was originally blind to recognizing this as a solution as it puts the burdon (of building twice) on the user. – Helmut Grohne Mar 24 '16 at 07:01
  • If you know the other compiler's path (like you probably need to with `CC_FOR_BUILD`) and the parameters you like to call it with, there are other possibilities (all in the area of setting it up [as new language](https://github.com/Kitware/CMake/blob/master/Modules/CMakeAddNewLanguage.txt)). I will certainly add the `ExternalProject_Add()` approach where you won't have to call the configuration twice manually (but where it would be - from my point of view - preferable to split your `CMakeLists.txt` file). – Florian Mar 24 '16 at 07:15
  • 1
    Perhaps also consider the approach described [here](http://stackoverflow.com/q/36084785/1938798) where an executable needs to be built within the CMake run so it can be used to generate sources and even other CMakeLists.txt files. I have it working to generate files on OS X for an iOS build, so it works in practice for a situation like yours. – Craig Scott Apr 28 '16 at 12:09

2 Answers2

16

CMake can only handle one compiler at a time. So - if you don't go the long way to set up the other compiler as a new language - you will end up with two configuration cycles.

I see the following approaches to automate this process:

  1. Taking the example "CMake Cross Compiling - Using executables in the build created during the build?" from the CMake pages as a starting point I'll get:

    CMakeLists.txt

    cmake_minimum_required(VERSION 3.0)
    project(FooBarTest)
    
    # When crosscompiling import the executable targets
    if (CMAKE_CROSSCOMPILING)
        set(IMPORT_PATH "IMPORTFILE-NOTFOUND" CACHE FILEPATH "Point it to the export file path from a native build")
        file(TO_CMAKE_PATH "${IMPORT_PATH}" IMPORT_PATH_CMAKE)
        include(${IMPORT_PATH_CMAKE}/genfooTargets.cmake)
    
        # Then use the target name as COMMAND, CMake >= 2.6 knows how to handle this
        add_custom_command(
            OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/foo.h
            COMMAND genfoo
        )
    
        add_executable(bar bar.cpp ${CMAKE_CURRENT_BINARY_DIR}/foo.h)
        target_include_directories(bar PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
    endif()
    
    # Only build the generator if not crosscompiling
    if (NOT CMAKE_CROSSCOMPILING)
        add_executable(genfoo genfoo.cpp)
        export(TARGETS genfoo FILE "${CMAKE_CURRENT_BINARY_DIR}/genfooTargets.cmake")
    endif()
    

    Then using a script like:

    build.sh

    #!/bin/bash
    
    if [ ! -d hostBuild ]; then
        cmake -E make_directory hostBuild
        cmake -E chdir hostBuild cmake ..
    fi
    cmake --build hostBuild
    
    if [ ! -d crossBuild ]; then
        cmake -E make_directory crossBuild
        cmake -E chdir crossBuild cmake .. -DIMPORT_PATH=${PWD}/hostBuild -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake
    fi
    cmake --build crossBuild
    

    I'll get the desired results by calling ./build.sh.

  2. Splitting the CMakeLists.txt and maybe even replace the export()/include() with something where I know the output path of my build tools e.g. by using CMAKE_RUNTIME_OUTPUT_DIRECTORY would simplify things:

    CMakeLists.txt

    cmake_minimum_required(VERSION 3.0)
    project(FooBarTest)
    
    # Then use the target name as COMMAND. CMake >= 2.6 knows how to handle this
    add_custom_command(
        OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/foo.h
        COMMAND genfoo
    )
    
    add_executable(bar bar.cpp ${CMAKE_CURRENT_BINARY_DIR}/foo.h)
    target_include_directories(bar PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
    

    buildTools/CMakeLists.txt

    cmake_minimum_required(VERSION 3.0)
    project(BuildTools)
    
    add_executable(genfoo genfoo.cpp)
    

    build.sh

    #!/bin/bash
    
    if [ ! -d crossBuild ]; then
        cmake -E make_directory crossBuild
        cmake -E chdir crossBuild cmake .. -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake
    fi
    if [ ! -d hostBuild ]; then
        cmake -E make_directory hostBuild
        cmake -E chdir hostBuild cmake ../buildTools -DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${PWD}/crossBuild
    fi
    cmake --build hostBuild
    cmake --build crossBuild
    

References

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Florian
  • 39,996
  • 9
  • 133
  • 149
5

It is possible to do that completely within CMake.

The trick is to run a separate CMake configuring stage within its own space, silently dismissing every crosscompiling setting and using the host's default toolchain, then import the generated outputs into it's parent, crosscompiling build.

First part:

set(host_tools_list wxrc generate_foo)

if(CMAKE_CROSSCOMPILING)
    # Pawn off the creation of the host utilities into its own dedicated space
    file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/host_tools)
    file(TO_NATIVE_PATH ${CMAKE_COMMAND} native_cmake_command)
    file(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR} native_cmake_current_source_dir)
    execute_process(
        COMMAND "${native_cmake_command}" "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}" "${native_cmake_current_source_dir}"
        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/host_tools
    )

    add_custom_target(host_tools
        COMMAND ${CMAKE_COMMAND} --build . --target host_tools --config $<CONFIG>
        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/host_tools
    )
    include(${CMAKE_CURRENT_BINARY_DIR}/host_tools/host_tools.cmake)

    foreach(tgt IN ITEMS ${host_tools_list})
        add_dependencies(host${tgt} host_tools)
    endforeach()

else()
    # Add an empty target, host tools are built inplace
    add_custom_target(host_tools
        DEPENDS ${host_tools_list}
    )
endif()

... then you add the usual add_executable and whatever ...

At the end:

if(NOT CMAKE_CROSSCOMPILING)
    foreach(tgt IN ITEMS ${host_tools_list})
        add_executable(host${tgt} ALIAS ${tgt})
    endforeach()

    export(TARGETS ${host_tools_list} NAMESPACE host FILE host_tools.cmake)
endif()

When it crosscompiles, it pawns off the creation of the host-run tools into its own dedicated space, and imports the targets as "hostwxrc" and "hostgenerate_foo", with a dependency on generating the host_tools themselves .

When it doesn't crosscompile, it builds wxrc and generate_foo as-is, and aliases them to hostwxrc and hostgenerate_foo.

After this, when you use $<TARGET_FILE:wxrc>, you refer to the wxrc built for the target platform, and $<TARGET_FILE:hostwxrc> refers to the wxrc built for the host platform, regardless whether they are the same or not.

Anonymous
  • 151
  • 1
  • 2
  • It looks like with this approach you may need to clear CXX/CC environment vars, before calling host cmake with exec_process. i.e. `set(ENV{CXX} "")` and `set(ENV{CC} "")` – kert Aug 29 '20 at 18:05