Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to instruct CMake to use the build architecture compiler

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.

like image 741
Helmut Grohne Avatar asked Mar 23 '16 09:03

Helmut Grohne


People also ask

How does CMake know which compiler to use?

As the linker is invoked by the compiler driver, CMake needs a way to determine which compiler to use to invoke the linker. This is calculated by the LANGUAGE of source files in the target, and in the case of static libraries, the language of the dependent libraries.

How do you build with CMake?

To build with just cmake change directory into where you want the binaries to be placed. For an in-place build you then run cmake and it will produce a CMakeCache. txt file that contains build options that you can adjust using any text editor.

Does CMake include compiler?

When CMake executes the project() call, it looks for a default compiler executable and determines the way for use it: default compiler flags, default linker flags, compile features, etc. And CMake stores path to that default compiler executable in the CMAKE_C_COMPILER variable.


2 Answers

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

  • Making a CMake library accessible by other CMake packages automatically
  • CMake build multiple targets in different build directories
  • How do I make CMake output into a 'bin' dir?
like image 119
Florian Avatar answered Oct 23 '22 11:10

Florian


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.

like image 39
Anonymous Avatar answered Oct 23 '22 10:10

Anonymous