67

I've done a lot of research and been unable to find an answer to this... how can I reliably find the target architecture I'm compiling for, using CMake? Basically, the equivalent to QMAKE_TARGET.arch in qmake.

Most sources seem to suggest CMAKE_SYSTEM_PROCESSOR, but that's a bad solution because that will always return i386 on OS X for example, no matter whether you're compiling for i386, x86_64, ppc or ppc64.

Similarly, CMAKE_SIZEOF_VOID_P gives the pointer size of the system, not the target.

I understand there is CMAKE_OSX_ARCHITECTURES, but this can be empty if not set, in which case it seems to default to whatever the system is capable of. So how can I find the target architecture information?

And specifically for OS X, how can I differentiate between 32, 64, and Intel Universal?

Jake Petroules
  • 23,472
  • 35
  • 144
  • 225
  • 9
    The CMAKE_SIZEOF_VOID_P gives different sizes for me depending whether I generate with -G "Visual Studio 11 Win64", or just with -G "Visual Studio 11" - which I am using to determine the bitness of the target. Does this not contradict your affirmation that "CMAKE_SIZEOF_VOID_P gives the pointer size of the system, not the target"? I have only tested this on Visual Studio... – Tarc Jul 03 '14 at 18:49
  • 6
    According to the documentation, [`CMAKE_SYSTEM_PROCESSOR`](https://cmake.org/cmake/help/v3.13/variable/CMAKE_SYSTEM_PROCESSOR.html) returns the architecture of the CPU you're compiling for, while [`CMAKE_HOST_SYSTEM_PROCESSOR`](https://cmake.org/cmake/help/v3.13/variable/CMAKE_HOST_SYSTEM_PROCESSOR.html) returns the achitecture of the CPU CMake is running on. So it should be safe to use `CMAKE_SYSTEM_PROCESSOR` even if you're cross-compiling. Do you find that this is not the case? It does return inconsistent results though (e.g. "AMD64" on Windows, "x86_64" on Linux). – Arthur Tacca Feb 26 '19 at 11:27

8 Answers8

46

So I devised a rather creative solution to my problem... it appears that CMake has no functionality to detect the target architecture whatsoever.

Now, we know we can easily do this in C because symbols like __i386__, __x86_64__, etc., will be defined depending on your environment. Fortunately CMake has a try_run function which will compile and run an arbitrary C source code file during the configure stage.

We could then write a small program which uses a bunch of ifdefs and writes the architecture name to the console as a string. The only problem is that this only works if the host and target system are the same... it can't work during cross compilation because while you can compile the binary, you can't run it to see its output.

Here's where things get interesting. We can exploit the C preprocessor to get the necessary information by deliberately writing a broken C program... we use the original concept of writing the architecture name to the console based on ifdefs but instead of doing that, we'll simply place an #error preprocessor directive in place of a printf call.

When CMake's try_run function compiles the C file, compilation will always fail, but whatever message we placed in the #error directive will show up in the compiler's error output, which try_run returns to us.

Therefore, all we have to do is parse the architecture name from the compiler's error output using some CMake string commands, and we can retrieve the target architecture... even when cross compiling.

The OS X specific part of the code mostly uses CMAKE_OSX_ARCHITECTURES to determine the target architecture, but in the case it's unspecified it will use the same code as other systems and correctly return x86_64 (for modern systems on which that is the compiler's default) or i386 (for older OS X systems such as Leopard).

I've tested and verified this works on Windows, OS X and Linux using Visual Studio 9 and 10 generators (x86, x86_64, ia64), Xcode, NMake, MSYS Makefiles and Unix Makefiles. The correct result is returned every time.

Notice: This solution can fail if you deliberately do things like pass -m32 or -m64 to your compiler, or other flags that may affect the target architecture (is there a way to pass all environment settings through to try_run?); this is not something I've tested. As long as you're using the default settings for your generator and all targets are being compiled for the same architecture you should be OK.

The full source code for my solution can be found at GitHub: https://github.com/petroules/solar-cmake/blob/master/TargetArch.cmake

Jake Petroules
  • 23,472
  • 35
  • 144
  • 225
  • How does the variable CMAKE_HOST_SYSTEM_PROCESSOR relate to this? Not the same thing? – joscarsson Aug 27 '12 at 11:41
  • 4
    That's the processor type of the HOST system, i.e. the system you're using to do the build. That may not always be the same as the TARGET, the system you are building the software for. For example, when you're writing an iPhone app, your HOST system is OS X, and your TARGET is iOS. If you're writing a Mac app, the host and target would both be OS X. CMAKE_HOST_SYSTEM_PROCESSOR is generally completely useless - for example it returns "i386" on a 64-bit OS X system and probably other less than useful values on other systems as well. – Jake Petroules Aug 27 '12 at 11:49
  • 3
    Does CMAKE_SYSTEM_PROCESSOR not work for this purpose? – Catskul Jul 10 '14 at 22:17
  • 2
    Forgive my ignorance... Why doesn't Cmake do what you describe and make a variable available to us? What good is generation without configuration? Have you `set(CMAKE_VERBOSE_MAKEFILE on)` and looked at how awful their flags are on *every* platform, from i686 and x86_64 to AMR and MIPS? No wonder they hide output by default... – jww Sep 14 '16 at 04:25
  • 3
    Great post and thanks for sharing. I am adapting you approach but use the preprocessor directly using CMake's execute_process function. `${CMAKE_C_COMPILER} -E -P platformtest.c` with my crosscompiler ${CMAKE_C_COMPILER}. This has less noisy output, uses STDOUT, can differentiate between success and failure, does not require any parsing, does not rely on how compiler displays errors. See https://gist.github.com/webmaster128/e08067641df1dd784eb195282fd0912f – Simon Warta Dec 23 '16 at 01:19
  • I thought ARCHITECTURE_ID and CMAKE_SYSTEM_NAME is used to determine the target architecture and system. – fdk1342 May 03 '17 at 19:33
  • Cmake's official FAQ says specifically to not use try_run for Universal binaries, and Universal is in the question https://gitlab.kitware.com/cmake/community/-/wikis/FAQ#how-do-i-build-universal-binaries-on-mac-os-x – G Huxley Aug 27 '20 at 23:05
  • Qt is LGPL/GPLv2 licensed. For an Apache-licensed alternative, we can also use https://github.com/openssl/openssl/blob/master/crypto/arm_arch.h – Yixing Lao Sep 05 '20 at 10:59
29

I've a Solution for the Case where the Host and Target System are the same.

First you need to call "uname -m" to get the "machine hardware name". Afterwards you need to cut off the trailing "Carriage Return" to get the actual Value back into the provided Variable.

EXECUTE_PROCESS( COMMAND uname -m COMMAND tr -d '\n' OUTPUT_VARIABLE ARCHITECTURE )

Now you can print out the Variable ${ARCHITECTURE}:

message( STATUS "Architecture: ${ARCHITECTURE}" )

or do some Canonisation to map, e.g. "x86_64", "amd64", ... to e.g. "64Bit". Same goes for 32Bit. With this you can execute archtecture dependend Compilation like:

if( ${ARCHITECTURE} STREQUAL "64Bit" )
    set( BLA_LIBRARY "/opt/lib/libBla.so" )
else()
    set( BLA_LIBRARY "/opt/lib32/libBla.so" )
endif()
Angstgeist
  • 555
  • 1
  • 7
  • 13
  • 2
    CMAKE_SYSTEM_PROCESSOR contains the output of uname -s or, on Windows, the environment variable PROCESSOR_ARCHITECTURE. – user2746401 Jan 29 '15 at 11:11
  • Wrong. uname -s is [CMAKE_SYSTEM_NAME](https://cmake.org/cmake/help/v3.0/variable/CMAKE_SYSTEM_NAME.html) – tobilocker Aug 25 '17 at 13:24
  • In addition to the above: CMAKE_SYSTEM_PROCESSOR is the same as uname -p on target system. – tobilocker Aug 25 '17 at 13:52
  • @user2746401 The environment variable `PROCESSOR_ARCHITECTURE` on windows depends on running console process. Even more, the cmake itself can be either 32-bit or 64-bit process. And even more, some 64-bit compilers can generate both 32-bit and 64-bit modules, which in turn depends on command line keys. So there is no way to portably detect the architecture. – Andry Feb 11 '19 at 20:51
  • The `tr` can be avoided by using `OUTPUT_STRIP_TRAILING_WHITESPACE` per: https://stackoverflow.com/a/42452019/3196753 – tresf Apr 05 '21 at 17:54
14

Android ${ANDROID_ABI}

The ${ANDROID_ABI} variable is the way to go in Android, where it assumes values like arm64-v8a, x86_64 and so on.

It is used on the official NDK library example: https://github.com/googlesamples/android-ndk/blob/840858984e1bb8a7fab37c1b7c571efbe7d6eb75/hello-libs/app/src/main/cpp/CMakeLists.txt#L25

I have further commented on that example at: NDK: How to include Prebuilt Shared Library Regardless of Architecture

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
6

I case your build process involved more than 1 target, I this it is better to let CMake know what ARCH/toolchain it is building against. You can follow the instructions for CMake cross-compilation, that encourages you to create a toolchain CMake file, which lets you pick the toolchain/compiler being used.

I've created one for building my C++ Linux application for the arm processor, and named it toolchain-arm.cmake.

It includes set(CMAKE_SYSTEM_PROCESSOR arm).

I then executed CMake like so:

cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE={my toolchain cmake path}/toolchain-arm.cmake {my source path}

within my project's CMakeList.txt I can refer to CMAKE_SYSTEM_PROCESSOR any way I wish.

When building for X86, I don't include the reference to -DCMAKE_TOOLCHAIN_FILE, leaving CMAKE_SYSTEM_PROCESSOR undefined, or at least not defined as arm.

Here's my toolchain-arm.cmake

SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_SYSTEM_VERSION 1)
set(CMAKE_SYSTEM_PROCESSOR arm)

# specify the cross compiler
SET(ENV{TOOLCHAIN_ROOT} /home/user/toolchain/tools-master/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin )
SET(CMAKE_C_COMPILER   $ENV{TOOLCHAIN_ROOT}/arm-linux-gnueabihf-gcc)
SET(CMAKE_CXX_COMPILER $ENV{TOOLCHAIN_ROOT}/arm-linux-gnueabihf-gcc)

# 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)
Parallel Universe
  • 1,626
  • 2
  • 13
  • 5
  • Given that most systems set `CMAKE_SYSTEM_VERSION` by default, then overriding it is a special case reserved for cross-compiling. – MarkHu Apr 18 '16 at 20:56
  • 4
    *"...it is better to let CMake know what ARCH/toolchain it is building against. ..."* - CMake is the build system. It is supposed to detect the platform and tell us what it is; not vice-versa. – jww Aug 23 '17 at 09:29
4

This post is old, so sorry if resurrecting the dead here but I just thought I'd share the solution I made.

I didn't want to use any external application and unfortunately the toolchain.cmake file we're using doesn't have the arch set in another variable so I detect it by looking at the CMAKE_C_FLAGS and CMAKE_CXX_FLAGS variables looking for the -march argument to GCC. If there isn't one, it falls back to CMAKE_HOST_SYSTEM_PROCESSOR.

A quick look at the Clang documentation seems to indicate that this wouldn't work for that one but it would just need a second regex step to match its expected argument.

set(TARGET_ARCH_REGEX "^.*-march[= ]([^ ]+).*$")
string(REGEX MATCH "${TARGET_ARCH_REGEX}" TARGET_ARCH_MATCH ${CMAKE_C_FLAGS} ${CMAKE_CXX_FLAGS})
if (TARGET_ARCH_MATCH)
    string(REGEX REPLACE "${TARGET_ARCH_REGEX}" "\\1" TARGET_ARCH ${CMAKE_C_FLAGS} ${CMAKE_CXX_FLAGS})
else()
    set(TARGET_ARCH ${CMAKE_HOST_SYSTEM_PROCESSOR})
endif()
  • Is there a chance that you expanded and perfected your script to detect target architecture for all the major compilers since then?) – Sergey Kolesnik Jul 26 '22 at 13:29
0

For now, you don't need any hacks for determining target architecture: per-target variable OSX_ARCHITECTURES was added to cmake and can be used for your purpose: http://public.kitware.com/Bug/view.php?id=8725

slashdot
  • 630
  • 5
  • 14
0

You can use the CMAKE_GENERATOR_PLATFORM variable. If you want to build a project under win32, you must use -A Win32 to specify the platform. Then the platform will be stored in the CMAKE_GENERATOR_PLATFORM variable.
сmake -A x64 .. or cmake -A Win32 ..
It is important that if you do not use -A, the variable will be empty

Mrognor
  • 42
  • 4
-4

This is a well tested way of knowing the host architecture:

# Store in CMAKE_DEB_HOST_ARCH var the current build architecture
execute_process(COMMAND
  dpkg-architecture
    -qDEB_HOST_ARCH
  OUTPUT_VARIABLE
    CMAKE_DEB_HOST_ARCH
  OUTPUT_STRIP_TRAILING_WHITESPACE
)

And use that information later in CMakeLists as you need

if(${CMAKE_DEB_HOST_ARCH} MATCHES "armhf")
  ...
elseif(${CMAKE_DEB_HOST_ARCH} MATCHES "i386")
  ...
else()
  ...
endif()
Roberto Mier
  • 59
  • 1
  • 1