How to set up CMake to cross compile with clang for ARM embedded on Windows?

16,368

Solution 1

My question was answered through the reply to my bug report, but I add the answer here to have all information in one place for future reference.

In short: CMake currently does not support to use the clang/clang++ command line interface if you install Clang from llvm.org. If you want to use the clang/clang++ interface (which is necessary to cross-compile for ARM) you have to install Clang via msys2.

In detail

Clang on Windows has two different command line interfaces:

  • clang/clang++ the default interface that attempts to be compatible with GCCs gcc/g++ and targets the GNU ABI
  • clang-cl that attempts to be compatible with Microsofts Visual C++ compiler cl.exe and targets the MSVC ABI

In order to cross-compile for ARM you need the clang/clang++ interface. The Problem is CMake supports different interfaces depending on how you installed Clang (see the bug in the CMake issue tracker for more details):

  • If you install Clang from llvm.org CMake only supports the clang-cl interface.
  • If you install Clang via msys2 CMake supports the clang/clang++ interface.

So here is what I did:

  1. Install msys2
  2. Install Clang and CMake with pacman. There are two clang packages in msys2, a mingw32 and a mingw64 version. I used the mingw64 package (mingw-w64-x86_64-clang).
  3. Launch the mingw64 shell and run CMake and build from there.

Toolchain file

There were two problems with my original toolchain file that took my a long time to fix. So I hope this will save others some time:

  1. The target triple (e.g. arm-none-eabi) needs to match the prefix of the GCC binutils exactly. The prefix of my binutils was arm-none-eabi (e.g. arm-none-eabi-ar) so I had to change the target triple accordingly.
  2. CMAKE_TRY_COMPILE_TARGET_TYPE needs to be changed to STATIC_LIBRARY in order to prevent CMake from running the linker during the compile check.

Here is the final toolchain file I used (you can also find a good example for a toolchain file in this GitHub repo):

cmake_minimum_required(VERSION 3.13)

set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR ARM)

if(DEFINED ENV{GCC_ARM_TOOLCHAIN})
    set(GCC_ARM_TOOLCHAIN $ENV{GCC_ARM_TOOLCHAIN})
else()
    set(GCC_ARM_TOOLCHAIN "C:/Users/user/tools/gcc-arm-none-eabi-7-2018-q2-update-win32")
endif()

LIST(APPEND CMAKE_PROGRAM_PATH ${GCC_ARM_TOOLCHAIN})

# Specify the cross compiler
# The target triple needs to match the prefix of the binutils exactly
# (e.g. CMake looks for arm-none-eabi-ar)
set(CLANG_TARGET_TRIPLE arm-none-eabi)
set(GCC_ARM_TOOLCHAIN_PREFIX ${CLANG_CLANG_TARGET_TRIPLE})
set(CMAKE_C_COMPILER clang)
set(CMAKE_C_COMPILER_TARGET ${CLANG_TARGET_TRIPLE})
set(CMAKE_CXX_COMPILER clang++)
set(CMAKE_CXX_COMPILER_TARGET ${CLANG_TARGET_TRIPLE})
set(CMAKE_ASM_COMPILER clang)
set(CMAKE_ASM_COMPILER_TARGET ${CLANG_TARGET_TRIPLE})

# Don't run the linker on compiler check
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)

# Specify compiler flags
set(ARCH_FLAGS "-mcpu=cortex-a5 -mthumb -mfpu=neon-vfpv4 -mfloat-abi=hard -mno-unaligned-access")
set(CMAKE_C_FLAGS "-Wall ${ARCH_FLAGS}" CACHE STRING "Common flags for C compiler")
set(CMAKE_CXX_FLAGS "-Wall -std=c++17 -fno-exceptions -fno-rtti -fno-threadsafe-statics ${ARCH_FLAGS}" CACHE STRING "Common flags for C++ compiler")
set(CMAKE_ASM_FLAGS "-Wall ${ARCH_FLAGS} -x assembler-with-cpp" CACHE STRING "Common flags for assembler")
set(CMAKE_EXE_LINKER_FLAGS "-nostartfiles -Wl,-Map,kernel.map,--gc-sections -fuse-linker-plugin -Wl,--use-blx --specs=nano.specs --specs=nosys.specs" CACHE STRING "")

# C/C++ toolchain
set(GCC_ARM_SYSROOT "${GCC_ARM_TOOLCHAIN}/${GCC_ARM_TOOLCHAIN_PREFIX}")
# set(CMAKE_SYSROOT ${GCC_ARM_SYSROOT})
set(CMAKE_FIND_ROOT_PATH ${GCC_ARM_SYSROOT})

# 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)

Solution 2

Here is an alternative answer that uses CMake to cross compile for embedded ARM using the GCC toolchain, rather than clang, and also uses plain old make rather than Ninja. I know this does not directly answer the question, but it is a very reasonable alternative for embedded ARM, and many of the Eclipse-based vendor toolchains we are using now are based on GCC rather than LLVM/clang.

First install the latest "GNU MCU Eclipse ARM Embedded GCC" toolchain from https://github.com/gnu-mcu-eclipse/arm-none-eabi-gcc/releases (NOTE: no installer, just unzip to where you want it).

Next, install latest MSYS2/MinGW using the installer available at https://www.msys2.org/.

For some reason the default install of MSYS2 does not include make. So, after using pacman to update the default packages per the install instructions, execute 'pacman -S make' to install make.

There is a cmake build available within MSYS2. However, it does not include generators for MSYS or MinGW (no idea why), and also (or because of) seems to have problems finding the external GCC toolchain, even when full paths are explicitly provided on the command line or in a toolchain file. So, this solution uses the native Window build of CMake, available at https://cmake.org/download/, and NOT the MSYS2 build of cmake.

By default the MSYS shell does NOT inherit the Windows environment, so the native Windows CMake will not be on the MSYS shell path. The simple solution is to specify the full path to cmake on the command line. In my case, I have the GCC cross compiler toolchain at C:/GNU MCU Eclipse/ARM Embedded GCC/8.2.1-1.4-20190214-0604. So, a full command to cmake looks like this.

/c/Program\ Files/CMake/bin/cmake.exe -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-arm-none-eabi.cmake "-DTOOLCHAIN_PREFIX=C:/GNU MCU Eclipse/ARM Embedded GCC/8.2.1-1.4-20190214-0604" -G "MSYS Makefiles" ../

Here the command is being executed from a build directory one level down from the project root, and a sibling cmake directory contains the specified toolchain file. Note, backslashes are necessary to escape any spaces in the path to cmake.exe, but are NOT used in the quoted paths passed as argument to cmake.

You can also run cmake and make right from a plain old Windows command line (cmd.exe) as long as both the cmake and msys bin directories on on the Windows path. In that case there is no need to specify the full path to cmake. No success from PowerShell, however.

Share:
16,368

Related videos on Youtube

S. Marti
Author by

S. Marti

Updated on July 19, 2022

Comments

  • S. Marti
    S. Marti over 1 year

    I'm trying to generate Ninja makefiles to cross compile a C++ project with Clang for an ARM Cortex A5 CPU. I created a toolchain file for CMake but it seems like there is an error or something missing which I'm unable to find. When invoke CMake with the toolchain file below I get the following error.

    CMake command:

    cmake -DCMAKE_TOOLCHAIN_FILE="..\Src\Build\Toolchain-clang-arm.cmake" -GNinja ..\Src\

    Output:

    -- The C compiler identification is Clang 7.0.0 CMake Error at C:/Users/user/scoop/apps/cmake/3.13.4/share/cmake-3.13/Modules/CMakeDetermineCompilerId.cmake:802 (message): The Clang compiler tool

    "C:/Program Files/LLVM/bin/clang.exe"

    targets the MSVC ABI but has a GNU-like command-line interface. This is not supported. Use 'clang-cl' instead, e.g. by setting 'CC=clang-cl' in the environment. Furthermore, use the MSVC command-line environment. Call Stack (most recent call first):
    C:/Users/user/scoop/apps/cmake/3.13.4/share/cmake-3.13/Modules/CMakeDetermineCCompiler.cmake:113 (CMAKE_DIAGNOSE_UNSUPPORTED_CLANG) CMakeLists.txt:2 (PROJECT)

    -- Configuring incomplete, errors occurred!

    CMake toolchain file (Toolchain-clang-arm.cmake):

    set(CMAKE_CROSSCOMPILING TRUE)
    SET(CMAKE_SYSTEM_NAME Generic)
    set(CMAKE_SYSTEM_PROCESSOR arm)
    
    # Clang target triple
    SET(TARGET armv7-none-eabi)
    
    # specify the cross compiler
    SET(CMAKE_C_COMPILER_TARGET ${TARGET})
    SET(CMAKE_C_COMPILER clang)
    SET(CMAKE_CXX_COMPILER_TARGET ${TARGET})
    SET(CMAKE_CXX_COMPILER clang++)
    SET(CMAKE_ASM_COMPILER_TARGET ${TARGET})
    SET(CMAKE_ASM_COMPILER clang)
    
    # C/C++ toolchain
    SET(TOOLCHAIN "C:/Program Files (x86)/GNU Tools ARM Embedded/7 2018-q2-update")
    SET(CMAKE_C_COMPILER_EXTERNAL_TOOLCHAIN ${TOOLCHAIN})
    SET(CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN ${TOOLCHAIN})
    
    # specify compiler flags
    SET(ARCH_FLAGS "-target armv7-none-eabi -mcpu=cortex-a5")
    SET(CMAKE_C_FLAGS "-Wall -Wextra ${ARCH_FLAGS}" CACHE STRING "Common flags for C compiler")
    SET(CMAKE_CXX_FLAGS "-Wall -Wextra -std=c++11 -fno-exceptions -fno-threadsafe-statics ${ARCH_FLAGS}" CACHE STRING "Common flags for C++ compiler")
    

    I used the CMake and Clang documentation and some random link from the net to create the toolchain file. The whole project compiles fine with ARM GCC for Windows, so the toolchain file seems to be the only missing piece of the puzzle.

    EDIT

    I tried to work around the CMake compiler checks by forcing the compiler. I replace the lines with SET(CMAKE_C_COMPILER clang), SET(CMAKE_CXX_COMPILER clang++) etc. with:

    CMAKE_FORCE_C_COMPILER(clang Clang)
    CMAKE_FORCE_CXX_COMPILER(clang++ Clang)
    

    The error stays the same.

    EDIT

    I can successfully compile a hello world example with clang -target arm-none-eabi. So the issue seems to be in CMake. I created a bug in the CMake issue tracker.

    Tool versions:

    • clang version 7.0.0 (tags/RELEASE_700/final)
    • cmake version 3.13.4
    • AngCaruso
      AngCaruso
      You left a comment on the CMake GitLab issue that indicates you may have been successful using msys2 with the clang package. If so, would you kindly post an answer/comment here with details on how you got it to work? I am still having no success, and I believe I am trying to do essentially the same thing.
    • S. Marti
      S. Marti
      @AngCaruso Thanks for the reminder to write an answer. I hope this answers your question.
  • AngCaruso
    AngCaruso over 4 years
    Thanks for posting this. My original problem was actually cross compile using the GGC for ARM toolchain from here gnu-mcu-eclipse.github.io/toolchain/arm/install, which I have since figured out how to do successfully. I will probably post an answer here for that, even though it doesn't answer your specific question. I also tried installing cmake in msys2, but it curiously doesn't support the MSYS of MinGW generators, which means that I must continue using the separately installed native Windows cmake.
  • S. Marti
    S. Marti over 4 years
    You are right, if you want to set up a toolchain for an embedded ARM device going with GCC is the more reasonable option. In fact that is what is used in this project right now. The main reason I want to be able to compile the project as well with Clang is to use the great tools Clang provides like clang-format and clang-tidy. For the binary that is deployed on the target we will still use GCC.