How to detect if 64 bit MSVC with cmake?
Asked Answered
S

7

34

I have a project which uses cmake, one target is set to only build with MSVC:

 if (MSVC)
     add_library(test SHARED source.cpp) 
 endif()

Now the other issue is that this target is only designed for MSVC 32bit. So how can I detect that the generator is MSVC64 and skip this target?

Stotts answered 31/8, 2016 at 20:13 Comment(0)
P
30

There are several ways - also used by CMake itself - that will check for "not 64Bit":

if(NOT "${CMAKE_GENERATOR}" MATCHES "(Win64|IA64)")
    ...
endif()

if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
    ...
endif()

if(NOT CMAKE_CL_64)
   ...
endif()

References

Pinot answered 31/8, 2016 at 20:51 Comment(7)
if(NOT "${CMAKE_GENERATOR}" MATCHES "(Win64|IA64)") seems to be what I'm after, I didn't try the CMAKE_SIZEOF_VOID_P but I guess that should work tooStotts
The first method does not work for me in Qt Creator. CMAKE_GENERATOR equals NMake Makefiles. However, CMAKE_SIZEOF_VOID_P looks fine.Forebrain
Beware of the cmake 3.14 release, now the bitness in the generator name is dropped: cmake.org/cmake/help/v3.14/generator/…Syman
2019: The CMAKE_GENERATOR code above is very out of date. Visual Studio 2019's generator has no bitness suffix (and defaults to 64 bit) unless the user manually specifies a "generator target platform" in CMake. And the platforms have been renamed to ARM64, ARM, Win32 and x64. It's not a reliable method for finding the target generator's platform... Do NOT use the CMAKE_GENERATOR code above. Also, the CMAKE_SIZEOF_VOID_P does NOT work either. It's always 64-bit even when building 32-bit apps. CMAKE_CL_64 seems best (when understood): gitlab.kitware.com/cmake/cmake/issues/16504Packing
2019 PART 2: However, CMAKE_CL_64 has its own problems, with people here claiming it is not set AT ALL if you're using 64-bit Windows to run the compiler: itk.org/Bug/print_bug_page.php?bug_id=9065 (I can confirm that I cannot find any value for the function). Sigh... It's a MESS. Maybe I'll try to write something that reliably scans CMAKE_GENERATOR for the latest 32-bit keywords I just described, else treat as 64-bit... but even in that solution, ARM vs ARM64 is going to be risky (don't want them to be treated as the same).Packing
Documentation for latest release of CMake (3.17, at the time), provides this description for CMAKE_CL_64: "Discouraged. Use CMAKE_SIZEOF_VOID_P instead."Pubis
Maybe it will help someone: to distinguish the 32/64-bit platforms, I currently use code like "${CMAKE_GENERATOR_PLATFORM}" MATCHES "Win32". It works in CMake 3.18 and matches on the platform set by the -A command line option.Destine
B
50

The usual way to check if you're generating for a 64 bits architecture is to test CMAKE_SIZEOF_VOID_P:

project( ... )
...

# won't work before project()!    
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
    # 64 bits
elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
    # 32 bits
endif()

This variable is only set after the project command!

Brainwash answered 31/8, 2016 at 20:54 Comment(3)
Is this command reliable at all, it looks like CMAKE_SIZEOF_VOID_P isn't populated with any value whatsoever. I suspect you'd need to call try_compile for it to work, but that wasn't really an option for an header only INTERFACE library.Brazil
@DavidLedger try_compile() is not a generic command, it's used when you have specific things you want to try and compile. It is used internally by cmake to determine the size of void*. CMAKE_SIZEOF_VOID_P is not set before the project command, and that's likely your mistake.Lynch
Note that this solution doesn't appear to be portable to Unix: if I use gcc on Linux and pass -DCMAKE_CXX_FLAGS=-m32, I get a 32 bit executable, but CMAKE_SIZEOF_VOID_P is still 8.Gourley
P
30

There are several ways - also used by CMake itself - that will check for "not 64Bit":

if(NOT "${CMAKE_GENERATOR}" MATCHES "(Win64|IA64)")
    ...
endif()

if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")
    ...
endif()

if(NOT CMAKE_CL_64)
   ...
endif()

References

Pinot answered 31/8, 2016 at 20:51 Comment(7)
if(NOT "${CMAKE_GENERATOR}" MATCHES "(Win64|IA64)") seems to be what I'm after, I didn't try the CMAKE_SIZEOF_VOID_P but I guess that should work tooStotts
The first method does not work for me in Qt Creator. CMAKE_GENERATOR equals NMake Makefiles. However, CMAKE_SIZEOF_VOID_P looks fine.Forebrain
Beware of the cmake 3.14 release, now the bitness in the generator name is dropped: cmake.org/cmake/help/v3.14/generator/…Syman
2019: The CMAKE_GENERATOR code above is very out of date. Visual Studio 2019's generator has no bitness suffix (and defaults to 64 bit) unless the user manually specifies a "generator target platform" in CMake. And the platforms have been renamed to ARM64, ARM, Win32 and x64. It's not a reliable method for finding the target generator's platform... Do NOT use the CMAKE_GENERATOR code above. Also, the CMAKE_SIZEOF_VOID_P does NOT work either. It's always 64-bit even when building 32-bit apps. CMAKE_CL_64 seems best (when understood): gitlab.kitware.com/cmake/cmake/issues/16504Packing
2019 PART 2: However, CMAKE_CL_64 has its own problems, with people here claiming it is not set AT ALL if you're using 64-bit Windows to run the compiler: itk.org/Bug/print_bug_page.php?bug_id=9065 (I can confirm that I cannot find any value for the function). Sigh... It's a MESS. Maybe I'll try to write something that reliably scans CMAKE_GENERATOR for the latest 32-bit keywords I just described, else treat as 64-bit... but even in that solution, ARM vs ARM64 is going to be risky (don't want them to be treated as the same).Packing
Documentation for latest release of CMake (3.17, at the time), provides this description for CMAKE_CL_64: "Discouraged. Use CMAKE_SIZEOF_VOID_P instead."Pubis
Maybe it will help someone: to distinguish the 32/64-bit platforms, I currently use code like "${CMAKE_GENERATOR_PLATFORM}" MATCHES "Win32". It works in CMake 3.18 and matches on the platform set by the -A command line option.Destine
B
4

On recent versions of CMake/Visual Studio the bitness is selected with CMAKE_GENERATOR_PLATFORM, which can be specified on the command line with -A option:

cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_BUILD_TYPE=Release ..

So, based on this feature you can then query the value from within the CMakeLists.txt:

if(NOT ("${CMAKE_GENERATOR_PLATFORM}" STREQUAL "Win64"))
    ...
ENDIF()
Brill answered 10/8, 2019 at 18:54 Comment(2)
cmake 3.22.0 is required this: "${CMAKE_GENERATOR_PLATFORM}" STREQUAL "x64"Syman
But why not just use CMAKE_SIZEOF_VOID_P?Gourley
F
3

Just encountered a similar issue myself and discovered CMAKE_VS_PLATFORM_NAME, which is calculated based on the generator and I think it's worth a mention.

I've tried it with cmake 3.16.1 (According to the docs it's been available since 3.1.3) using: -GVisual Studio 15 2017, -GVisual Studio 15 2017 Win64 and -GVisual Studio 15 2017 -Ax64 and it was set to: Win32, x64 and x64 respectively. So for this scenario I think checking against Win32 should work e.g.

if ("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "Win32")
    ...
endif ()
Flummery answered 18/12, 2019 at 14:11 Comment(0)
S
2

Here is a script I originally started writing to detect whether a target was x86 (32-bit x86) or amd64 (64-bit x86). I then found Jake Petroules' script and continued to assimilate bits and pieces from multiple sources scoured off the Internet to detect target architectures in general using the C compiler. There are many different C compilers and they give many inconsistent answers back. This script is designed to handle them all (in theory), and give CMake much higher detection fidelity. It works by using the preprocessor to communicate back to the CMake script. Put it in your CMake modules folder and include() it at least once in your project (it has an include guard so multiple inclusions are okay). Then you will have a variable CMAKE_CPU_ARCHITECTURES in the folder where you included() this script, and all subfolders. This variable is a list (because XCode project files can target multiple architectures at once). You should check to see if the one you want is in this list.

if("amd64" IN_LIST CMAKE_CPU_ARCHITECTURES) # I am targeting 64-bit x86.
...
if("x86" IN_LIST CMAKE_CPU_ARCHITECTURES) # I am targeting 32-bit x86.
...

It will either be x86 (32 bit) or amd64 (64 bit). This is only the target architecture (i.e. the CPU chip). To detect operating system, generator, compiler, etc., is another story, but you can use the architecture check nested inside various other if-statement, e.g. if(WIN32) (confusingly just means targeting some kind of Windows, not 32-bit necessarily) or if(MSVC) (if you're interested in the compiler), and so forth, or whatever as the case may be, independently.

(I have a similar script for checking specific operating systems, and it is able to detect specific things and returns values like watchOS vs. iOS vs. macOS or Windows CE, and so on, but that's another story).

detect_cpu_architectures.cmake:

if(NOT detect_cpu_architectures_INC) # include guard
set(detect_cpu_architectures_INC)

# This code was originally written by Jake Petroules,
# https://github.com/axr/solar-cmake/blob/master/TargetArch.cmake

# then edited/extended by Daniel Russell with help from
# https://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/global/qprocessordetection.h
# https://opensource.apple.com/source/WTFEmbedded/WTFEmbedded-95/wtf/Platform.h.auto.html
# http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.kui0098a/armccref_babjidgc.htm
# https://sourceforge.net/p/predef/wiki/Architectures/
# https://blog.kowalczyk.info/article/j/guide-to-predefined-macros-in-c-compilers-gcc-clang-msvc-etc..html

# Regarding POWER/PowerPC, just as is noted in the Qt source,
# "There are many more known variants/revisions that we do not handle/detect."
# Set ppc_support to TRUE before including this file or ppc and ppc64
# will be treated as invalid architectures since they are no longer supported by Apple

# TODO: add more chips!!!! Tedious though.    
enable_language(C) # this must be at file-scope not function scope. Thus include-guard.

set(archdetect_c_code "
/* -----------------------------------ARM ---------------------------------- */

#if defined(__ARM64_ARCH_8__) || defined(__aarch64__) || defined(__ARMv8__) || defined(__ARMv8_A__) || defined(_M_ARM64)
    #if defined(__thumb__) || defined(_M_ARMT)
        #error cmake_ARCH armthumbv3
    #elif
        #error cmake_ARCH armv8
    #endif

#elif defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__  ) || defined(__ARM_ARCH_7F__) || defined(__ARM_ARCH_7S__) || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 7)
    #if defined(__thumb__) || defined(__thumb2__) || defined(_M_ARMT)
        #error cmake_ARCH armthumbv2
    #elif
        #error cmake_ARCH armv7
    #endif

#elif defined(__ARM_ARCH_6__) ||  defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) || defined(__ARM_ARCH_6M__) || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 6)
    #if defined(__thumb__) || defined(_M_ARMT)
        #error cmake_ARCH armthumbv1
    #elif
        #error cmake_ARCH armv6
    #endif

#elif defined(__ARM_ARCH_5__) || defined(__ARM_ARCH_5E__ ) || defined(__ARM_ARCH_5T__) || defined(__ARM_ARCH_5TE__) || defined(__ARM_ARCH_5TEJ__) || defined(__MARM_ARMV5__) || (defined(__TARGET_ARCH_ARM) && __TARGET_ARCH_ARM-0 >= 5)
    #error cmake_ARCH armv5

#elif defined(__ARM_ARCH_4__) ||  defined(__ARM_ARCH_4T__) || defined(__MARM_ARMV4__)
    #error cmake_ARCH armv4

#elif defined(__TARGET_ARCH_ARM)
    #error cmake_ARCH armv ## __TARGET_ARCH_ARM

#elif defined(__arm__) || defined(_M_ARM)
    #error arm

/* ----------------------------------- x86 ---------------------------------- */

#elif defined(__i386) || defined(__i386__) || defined(_M_IX86) || defined(i386)  || defined(_M_IX86) || defined(_X86_) || defined(__THW_INTEL)
    #error cmake_ARCH x86

#elif defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(_M_X64) || defined(__amd64__) || defined(_M_AMD64)
    #error cmake_ARCH amd64

// AMD wrote the original x86_64 bit spec after cloning x86 for years.
// Then, in 2003, they renamed it to amd64. So basically x64 ~ amd64 ~ x86_64
// But the trend seems to be to tidy this up and go with the most officialish, amd64.
// Think of it this way, AMD was in fact the one pushing the architecture forwards at times.
// Even if Intel invented it. Therefore, I say, let's stick with amd64 and be over this mess already.

/* ------------------------------- Itanium ---------------------------------- */

#elif defined(__ia64) || defined(__ia64__) || defined(_M_IA64) || defined(_IA64) || defined(__IA64__) || defined(__itanium__)
    /* 32-bit mode on Itanium */
    #if !defined(__LP64__) && !defined(_M_X64) /* <-- Visual Studio macro (it USED to support Itanium) */
        #error cmake_ARCH ia32
    #else
        #error cmake_ARCH ia64
    #endif

/* ------------------------------ Power PC  --------------------------------- */

#elif defined(__ppc__) || defined(__ppc) || defined(__PPC__)  || defined(__PPC) || defined(__powerpc__) || defined(__powerpc) || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC) || defined(_M_MPPC) || defined(_M_PPC) || defined(__POWERPC__)
    #if defined(__ppc64__) || defined(__PPC64__) || defined(__powerpc64__) || defined(__64BIT__) 
        #error cmake_ARCH ppc64
    #else
        #error cmake_ARCH ppc32
    #endif

/* ---------------------------------- Sparc --------------------------------- */

#elif defined(__sparc) && !defined(__arch64__) || defined(__sparcv8)
    #error cmake_ARCH sparc32

#elif defined(__sparc__) && defined(__arch64__) || defined (__sparcv9)
    #error cmake_ARCH sparc64

/* --------------------------- IBM Mainframes ------------------------------- */

/* 64 bit IBM z/Architecture */
#elif defined(__s390x__) || defined(__s390X__) || defined(__zarch__) || defined(__SYSC_ZARCH__)
    #error cmake_ARCH zArch

/* Older 32 bit IBM System/390, maybe useful for Hercules emulation? */
#elif defined(__s390__)
    #error cmake_ARCH s390

/* --------------------------- Hitachi SuperH ------------------------------- */

#elif defined(__sh1__)
    #error cmake_ARCH superH1
#elif defined(__sh2__)
    #error cmake_ARCH superH2
#elif defined(__sh3__) || defined(__SH3__)
    #error cmake_ARCH superH3
#elif defined(__SH4__)
    #error cmake_ARCH superH4
#elif defined(__SH5__)
    #error cmake_ARCH superH5
#elif defined(__sh__) // not sure which version...unlikely?
    #error cmake_ARCH superH 

/* -------------------------- Analog Devices --------------------------------- */

#elif defined(__bfin) || defined(__BFIN__)
    #error cmake_ARCH blackfin
#elif defined(__ADSPTS101__)
    #error cmake_ARCH adps-ts101
#elif defined(__ADSPTS201__)
    #error cmake_ARCH adps-ts201
#elif defined(__ADSPTS202__)
    #error cmake_ARCH adps-ts202
#elif defined(__ADSPTS203__)
    #error cmake_ARCH adps-ts203
#elif defined(__ADSPTS20x__) // this will be true for any of the above but we use as  catchall
    #error cmake_ARCH adps-ts20x

/* ------------------------------- Alpha ------------------------------------- */

#elif defined(__alpha_ev4__)
    #error cmake_ARCH alpha_ev4
#elif defined(__alpha_ev5__)
    #error cmake_ARCH alpha_ev5
#elif defined(__alpha_ev6__)
    #error cmake_ARCH alpha_ev6
#elif defined(__alpha__) || defined(__alpha) || defined(_M_ALPHA)
    #error cmake_ARCH alpha

/* ------------------------- Texas Instruments ------------------------------ */

#elif defined(_TMS320C6200)
    #error cmake_ARCH c6200
#elif defined(_TMS320C6400)
    #error cmake_ARCH c6400
#elif defined(_TMS320C6400_PLUS)
    #error cmake_ARCH c6400+
#elif defined(_TMS320C6600)
    #error cmake_ARCH c6600
#elif defined(_TMS320C6700)
    #error cmake_ARCH c6700
#elif defined(_TMS320C6700_PLUS)
    #error cmake_ARCH c6700+
#elif defined(_TMS320C6740)
    #error cmake_ARCH c6740
#elif defined(__TMS470__)
    #error cmake_ARCH tms470

/* -------------------------- Hewlett Packard ------------------------------ */

#elif defined(_PA_RISC1_0)
    #error cmake_ARCH parisc1_0

#elif defined(_PA_RISC1_1) || defined(__HPPA11__) || defined(__PA7100__)
    #error cmake_ARCH parisc1_1

#elif defined(_PA_RISC2_0) || defined(__RISC2_0__) || defined(__HPPA20__) || defined(__PA8000__)
    #error cmake_ARCH parisc2_0

#elif defined(__hppa__) || defined(__HPPA__) || defined(__hppa)
    #error cmake_ARCH parisc

/* ---------------------------- Adapteva --------------------------------- */

#elif defined(__epiphany__)
    #error cmake_ARCH ephiphany

/* ------------------------------ MIPS  ---------------------------------- */

#elif defined(_MIPS_ISA_MIPS1) || (__mips == 1)
    #error cmake_ARCH mips1
#elif defined(_MIPS_ISA_MIPS2) || defined(__MIPS_ISA2__) || (__mips == 2)
    #error cmake_ARCH mips2
#elif defined(_MIPS_ISA_MIPS3) || defined(__MIPS_ISA3__) || (__mips == 3)
    #error cmake_ARCH mips3
#elif defined(_MIPS_ISA_MIPS4) || defined(__MIPS_ISA4__) || (__mips == 4)
    #error cmake_ARCH mips4
#elif defined(__MIPS__) || defined(__mips__) || defined(mips) || defined(__mips)
    #error cmake_ARCH mips

/* ---------------------------- Motorola  -------------------------------- */

#elif defined(__mc68000__) || defined(__MC68000__)
    #error cmake_ARCH mc68000
#elif defined(__mc68010__)
    #error cmake_ARCH mc68010
#elif defined(__mc68020__) || defined(__MC68020__)
    #error cmake_ARCH mc68020
#elif defined(__mc68030__) || defined(__MC68030__)
    #error cmake_ARCH mc68030
#elif defined(__mc68040__) || defined(__MC68040__)
    #error cmake_ARCH mc68040
#elif defined(__mc68060__) || defined(__MC68060__)
    #error cmake_ARCH mc68060
#elif defined(__m68k__) || defined(M68000) || defined(__MC68K__)
    #error cmake_ARCH mc68

#endif
")


if(APPLE AND CMAKE_OSX_ARCHITECTURES)
    # On OS X we use CMAKE_OSX_ARCHITECTURES *if* it was set
    # First let's normalize the order of the values

    # Note that it's not possible to compile PowerPC applications if you are using
    # the OS X SDK version 10.6 or later - you'll need 10.4/10.5 for that, so we
    # disable it by default
    # See this page for more information:
    # https://mcmap.net/q/223691/-how-can-we-restore-ppc-ppc64-as-well-as-full-10-4-10-5-sdk-support-to-xcode-4

    # Architecture defaults to x86  or ppc on OS X 10.5 and earlier, depending on the CPU type detected at runtime.
    # On OS X 10.6+ the default is x86_64 if the CPU supports it, x86 otherwise.

    foreach(osx_arch ${CMAKE_OSX_ARCHITECTURES})
        if("${osx_arch}" STREQUAL "ppc" AND ppc_support)
            set(osx_arch_ppc TRUE)
        elseif("${osx_arch}" STREQUAL "i386")
            set(osx_arch_i386 TRUE)
        elseif("${osx_arch}" STREQUAL "x86_64")
            set(osx_arch_x86_64 TRUE)
        elseif("${osx_arch}" STREQUAL "ppc64" AND ppc_support)
            set(osx_arch_ppc64 TRUE)
        else()
            message(FATAL_ERROR "Invalid OS X arch name: ${osx_arch}")
        endif()
    endforeach()

    # Now add all the architectures in our normalized order
    if(osx_arch_ppc)
        list(APPEND ARCH ppc32)
    endif()

    if(osx_arch_i386)
        list(APPEND ARCH x86)
    endif()

    if(osx_arch_x86_64)
        list(APPEND ARCH x64)
    endif()

    if(osx_arch_ppc64)
        list(APPEND ARCH ppc64)
    endif()
else()
    file(WRITE "${CMAKE_BINARY_DIR}/CMakeFiles/detect_cpu_arch.c" "${archdetect_c_code}")


    # Detect the architecture in a rather creative way...
    # This compiles a small C program which is a series of ifdefs that selects a
    # particular #error preprocessor directive whose message string contains the
    # target architecture. The program will always fail to compile (both because
    # file is not a valid C program, and obviously because of the presence of the
    # #error preprocessor directives... but by exploiting the preprocessor in this
    # way, we can detect the correct target architecture even when cross-compiling,
    # since the program itself never needs to be run (only the compiler/preprocessor)
    try_run(
        run_result_unused
        compile_result_unused
        "${CMAKE_BINARY_DIR}/CMakeFiles"
        "${CMAKE_BINARY_DIR}/CMakeFiles/detect_cpu_arch.c"
        COMPILE_OUTPUT_VARIABLE ARCH
        CMAKE_FLAGS CMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}
    )

    # Parse the architecture name from the compiler output
    string(REGEX MATCH "cmake_ARCH ([a-zA-Z0-9_]+)" ARCH "${ARCH}")

    # Get rid of the value marker leaving just the architecture name
    string(REPLACE "cmake_ARCH " "" ARCH "${ARCH}")

    # If we are compiling with an unknown architecture this variable should
    # already be set to "unknown" but in the case that it's empty (i.e. due
    # to a typo in the code), then set it to unknown
    if (NOT ARCH)
        set(ARCH unknown)
    endif()
endif()


set(CMAKE_CPU_ARCHITECTURES "${ARCH}")

endif(NOT detect_cpu_architectures_INC) # include guard
Seline answered 2/5, 2020 at 6:52 Comment(1)
Do you maintain this Script somewhere on GitHub?Thimblerig
R
1

The optional solution is to build the condition base on used compiler name.

if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
Rubric answered 30/9, 2019 at 8:59 Comment(0)
D
1

This example worked for me on cmake 3.17.0:

if("${CMAKE_GENERATOR_PLATFORM}" STREQUAL "x64")
  ....
endif()

if("${CMAKE_GENERATOR_PLATFORM}" STREQUAL "Win32")
  ...
endif()
Dereliction answered 22/6, 2020 at 18:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.