7

I am trying to cross-compile a project for embedded ARM Cortex builds, but I am unable to get the linker working. I want to use armlink, but no files are passed to armlink and hence no .elf file is produced.

My CMakeLists.txt is pretty simple and given below. The failure is shown after that which shows that armlink was invoked by the makefile without any arguments.

Any pointers will help - I searched and read many posts, but they all seem to have more involved requirements.

cmake_minimum_required(VERSION 2.8)

project(test_arm)
enable_language(C ASM)

# Cross-compilation for ARM
SET(CMAKE_C_COMPILER armcc)
SET(CMAKE_LINKER armlink)
SET(CMAKE_C_LINK_EXECUTABLE armlink)

SET(CMAKE_C_FLAGS "--cpu=Cortex-M3")
SET(LINK_FLAGS "--map --ro-base=0x0 --rw-base=0x0008000 --first='boot.o(RESET)' --datacompressor=off")
SET(CMAKE_EXE_LINKER_FLAGS "--map --ro-base=0x0 --rw-base=0x0008000 --first='boot.o(RESET)' --datacompressor=off")

include_directories(../include)

add_executable(blinky blinky.c)
set_target_properties(blinky PROPERTIES LINKER_LANGUAGE C)

The failure is as follows, but I guess it would be obvious to someone given that I have some stupid issue in my CMakeLists:

$ make VERBOSE=1
[100%] Building C object CMakeFiles/blinky.dir/blinky.c.o
/usr/bin/cmake -E cmake_link_script CMakeFiles/blinky.dir/link.txt --verbose=1
armlink
Linking C executable blinky
Product: DS-5 Professional 5.21.0 [5210017]
Component: ARM Compiler 5.05 update 1 (build 106)
Tool: armlink [4d0efa]
For support see http://www.arm.com/support/
Software supplied by: ARM Limited
Usage: armlink option-list input-file-list
where
....

I was expecting the CMake generated Makefile to invoke armlink with something like:

armlink --map --ro-base=0x0 --rw-base=0x0008000 \
  --first='boot.o(RESET)' --datacompressor=off \
  CMakeFiles/blinky.dir/blinky.c.o -o blinky.elf
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
guraaf
  • 163
  • 4
  • 12

3 Answers3

1

From my experience, you cannot set CMAKE_EXE_LINKER_FLAGS in a CMakeLists.txt file. It has to be passed via a CMAKE_TOOLCHAIN_FILE when CMake is invoked the very first time in a build directory.

I don't find any documentation regarding this problem, but there is the cross-compilation with CMake page which you should use it if you do cross-compilation.

For a start, just put your set-calls in a toolchain file and run

cmake -DCMAKE_TOOLCHAIN_FILE=<yourfile.toolchain>

in a clean build directory.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Patrick B.
  • 11,773
  • 8
  • 58
  • 101
  • 1
    Thanks Patrick. I tried this but didn't make progress. I went through the page and created a toolchainfile and passed on command-line as you wrote above. I still couldn't get the linker to work. I did start from a clean build directory. Any more ideas? – guraaf May 19 '15 at 19:42
1

A toolchain file may be a good idea. Here is what I've came up the last time I tried CMake 2.8.10 with the DS-5 toolchain (it could still be optimized, but it should give you a starting point):

INCLUDE(CMakeForceCompiler)

# This one is important
SET(CMAKE_SYSTEM_NAME Generic)
SET(CMAKE_SYSTEM_PROCESSOR arm)

# Specify the cross compiler
SET(CMAKE_C_COMPILER "C:/Program Files (x86)/DS-5/bin/armcc.exe")
SET(CMAKE_CXX_COMPILER "C:/Program Files (x86)/DS-5/bin/armcc.exe")
SET(CMAKE_AR "C:/Program Files (x86)/DS-5/bin/armar.exe" CACHE FILEPATH "Archiver")

#CMAKE_FORCE_C_COMPILER("C:/Program Files (x86)/DS-5/sw/gcc/bin/arm-linux-gnueabihf-gcc.exe" GNU)
#CMAKE_FORCE_CXX_COMPILER("C:/Program Files (x86)/DS-5/sw/gcc/bin/arm-linux-gnueabihf-g++.exe" GNU)

UNSET(CMAKE_C_FLAGS CACHE)
SET(CMAKE_C_FLAGS "--cpu=Cortex-A9 --thumb -Ospace" CACHE STRING "" FORCE)
UNSET(CMAKE_CXX_FLAGS CACHE)
SET(CMAKE_CXX_FLAGS ${CMAKE_C_FLAGS} CACHE STRING "" FORCE)
UNSET(CMAKE_EXE_LINKER_FLAGS CACHE)
SET(CMAKE_EXE_LINKER_FLAGS "" CACHE STRING "" FORCE)
UNSET(CMAKE_AR_FLAGS CACHE)
SET(CMAKE_AR_FLAGS "-p -armcc,-Ospace" CACHE STRING "" FORCE)

# set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> cr <TARGET> <LINK_FLAGS> <OBJECTS>")
SET(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> ${CMAKE_AR_FLAGS} -o <TARGET> <OBJECTS>" CACHE STRING "C Archive Create")
# set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> cr <TARGET> <LINK_FLAGS> <OBJECTS>")
SET(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> ${CMAKE_AR_FLAGS} -o <TARGET> <OBJECTS>" CACHE STRING "CXX Archive Create")

include_directories("C:/Program Files (x86)/DS-5/include")
#include_directories("C:/Program Files (x86)/DS-5/sw/gcc/arm-linux-gnueabihf/libc/usr/include/arm-linux-gnueabi")

# Where is the target environment
SET(CMAKE_FIND_ROOT_PATH "C:/Program Files (x86)/DS-5")

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

Regarding your question

Some failure analysis

What you have tried should work (see also e.g. How do I add a linker or compile flag in a CMake file?). But it seems something goes wrong during the configuration step.

I don't know your command line call for CMake's configuration/generation steps, so some general tips to find the root cause:

You could try calling

cmake.exe --trace ...

to see what went wrong with your CMAKE_EXE_LINKER_FLAGS variable. This will generate a lot of output, so here are some basics on what CMake does:

  • The project() command will trigger the compiler evaluation
  • This will write CMAKE_EXE_LINKER_FLAGS into your CMakeCache.txt
  • You are overwriting it with a local variable (see variable scope docu here)

If you look into share\cmake-2.8\Modules\CMakeCommonLanguageInclude.cmake:

set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS_INIT} $ENV{LDFLAGS}"
     CACHE STRING "Flags used by the linker.")

You could use CMAKE_EXE_LINKER_FLAGS_INIT, but you have to set it before the project() command or in the toolchain file.

Because you set the link language to C take a look into share\cmake-2.8\Modules\CMakeCInformation.cmake:

if(NOT CMAKE_C_LINK_EXECUTABLE)
  set(CMAKE_C_LINK_EXECUTABLE
    "<CMAKE_C_COMPILER> <FLAGS> <CMAKE_C_LINK_FLAGS> <LINK_FLAGS> <OBJECTS>  -o <TARGET> <LINK_LIBRARIES>")
endif()

So you can use CMAKE_C_LINK_EXECUTABLE to overwrite the complete linker call or you could use CMAKE_C_LINK_FLAGS to set additional flags.

The "official" way

The official way to set the target's linker and compiler flags would be (before CMake 2.8.12):

set_property(TARGET blinky APPEND_STRING PROPERTY COMPILE_FLAGS "--cpu=Cortex-M3")
set_property(TARGET blinky APPEND_STRING PROPERTIES LINK_FLAGS "--map --ro-base=0x0 --rw-base=0x0008000 --first='boot.o(RESET)' --datacompressor=off")

Starting with CMake 2.8.12 it would be something like:

add_compile_options("--cpu=Cortex-M3")
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Florian
  • 39,996
  • 9
  • 133
  • 149
  • Thanks Florian for the detailed reply. I have spent hours on this but no progress yet. I can't even get the armcc to work. Complains that it is unable to compile even a simple program. I started using a toolchain file and added SET(CMAKE_C_LINK_EXECUTABLE=" ${CMAKE_C_LINK_FLAGS} -o ") but that didn't help. – guraaf May 19 '15 at 19:55
  • OK, one more question. I realize that I need to build my SET(CMAKE_C_COMPILE_OBJECT " ${....) to be able to compile with the forced compiler. But how do I make it utilize the include_directories?? – guraaf May 19 '15 at 23:35
  • I'm sorry to hear it didn't work. But I'm confident we could find a solution that will work. – Florian May 20 '15 at 07:37
  • If you have problems with the "unable to compile a simple program" error, you should add `--debug-trycompile` to your cmake.exe command line options. This will keep CMakeFiles\CMakeTmp directory to check the compiler runs outputs (including a log file). And I was using [Ninja](https://martine.github.io/ninja/) as an replacement for ARM's make, because it gives a detailed output of what it had tried (full command line call I can copy/paste on the command line for tests; was very helpful; also check it's `rules.ninja` file). And please add some more details on the errors you get. – Florian May 20 '15 at 07:45
  • 1
    And for your first tests - not recommended as the final solution - you could add the following two lines to your toolchain file to bypass the CMake's own compiler tests: `SET(CMAKE_C_COMPILER_WORKS 1 CACHE INTERNAL "")` and `SET(CMAKE_CXX_COMPILER_WORKS 1 CACHE INTERNAL "")`. Additionally - to make sure that the variables set in toolchain file are what you end up in your compiler call - you can/should add `CACHE ... FORCE` to the `set()` commands. For more details see http://stackoverflow.com/questions/11423313/cmake-cross-compiling-c-flags-from-toolchain-file-ignored – Florian May 20 '15 at 07:58
  • The include directories are part of the `` [expansion rule](http://www.cmake.org/Wiki/CMake_Useful_Variables#Expansion_Rules). I forgot that in my toolchain file and updated my answer accordingly. As reference see CMake's source code cmNinjaTargetGenerator ::WriteObjectBuildStatement() and cmNinjaTargetGenerator::ComputeFlagsForObject(). – Florian May 20 '15 at 10:47
  • I've found also some interesting comments here: [CMake, armcc and Ninja workarounds and problems](http://www.cmake.org/pipermail/cmake/2014-October/058963.html) by Gerhard Olsson – Florian May 21 '15 at 12:42
1

Starting with CMake v3.5 you don't need a toolchain anymore for Keil ARM C/C++ compilation tools:

Support was added for the ARM Compiler (arm.com) with compiler id ARMCC.

Just set your C/CXX compiler variables accordingly

cmake -DCMAKE_C_COMPILER:PATH="C:\Program Files (x86)\DS-5\bin\armcc.exe"
      -DCMAKE_CXX_COMPILER:PATH="C:\Program Files (x86)\DS-5\bin\armcc.exe"
      ...

References

Community
  • 1
  • 1
Florian
  • 39,996
  • 9
  • 133
  • 149