Search code examples
c++visual-studiovisual-c++cmakecross-compiling

CMake cross compiling Windows to Windows


Has anyone ever tried, to cross compile Windows to Windows in cmake?

I have an exotic scenario. I'm participating in an open source project, built with cmake. The project has several platform ports. Eg. Windows, Linux, etc. Each platform has its own cmake subscript and some also have separate toolchain scripts.

I'd like to add another port/platform: Win32 FreeRTOS. The win32 FreeRTOS is a curiosity in itself. Educational at best. And so it shouldn't pollute the existing Windows port. So, I've created a new platform script called win32freertos.cmake. In which a different set of files and libs are compiled. And I've created a new toolchain script, as one does when cross compiling:

It shouldn't really contain much, I'd think.

include_guard()

# The name of the target operating system
set(CMAKE_SYSTEM_NAME win32freertos)

And then it's built with the Microsoft Visual Studio CMake: (Same as the other Windows port.)

cmake -B build.win32freertos -DCMAKE_TOOLCHAIN_FILE=cmake/tools/toolchain/win32freertos.cmake

This works, to some degree. There're a few missing compiler args. This can be fixed with:

add_definitions(-DWIN32 -D_WINDOWS)
add_compile_options(/Zi /Od /Ob0 /WX /RTC1 /GR /MDd /EHsc)
add_link_options(/DEBUG /INCREMENTAL)

At this point, all seems fairly good, on the surface at least. The only thing is, that the compiled output is named ".a" (for libs) and "" for exe files. (Unix styled output files.) This makes the unit tests fail. (And it's just bad.) Normally this would be fixed with:

set(CMAKE_EXECUTABLE_SUFFIX ".exe")

Unfortunately, this doesn't work. (???) Nothing seems to work. It seems that when creating a new platform, there's some vital initialization that's missing from the MSVC (Visual Studio) / CMake. So, instead I've come up with this:

set(CMAKE_CXX_COMPILER_VERSION "19.28") #Visual Studio 16 2019
include("${CMAKE_ROOT}/Modules/Platform/Windows-MSVC-CXX.cmake")
include("${CMAKE_ROOT}/Modules/Platform/Windows.cmake")

This works! But it's a very fragile solution. Eg. I hardcode the Visual Studio compiler version (the MSC_VER actually.) And I hardcode some random visual studio cmake script paths. The next time VS is upgraded, this will probably fail. Or it'll fail when using an older VS. And it's just bad to write stuff like this, in a build script.

Btw, I've not found any way to retrieve the MSC_VER from the system. There's a few corelated values, that can be pulled:

CMAKE_GENERATOR Visual Studio 16 2019
VSCMD_VER=16.8.4
VCToolsVersion=14.28.29333

But they cannot be converted to the MSC_VER, that I've found. Also, forget about most of the cmake variables, that you think you know. At this point, they're not set. (The above Windows CMake script sets many of them.)

Anyone have any good advice? Has anyone ever tried to create a alternate toolchain script, when building Windows apps in CMake?


Solution

  • Avoid directory commands add_definitions, add_compile_options, add_link_options, etc. Instead, add these to your toolchain file in the standard _INIT variables.

    The suffixes can be overridden using their language-specific variants, too:

    set(CMAKE_SYSTEM_NAME win32freertos)
    
    set(CMAKE_CXX_FLAGS_INIT "-DWIN32 -D_WINDOWS /Zi /Od /Ob0 /WX /RTC1 /GR /MDd /EHsc")
    set(CMAKE_C_FLAGS_INIT "-DWIN32 -D_WINDOWS /Zi /Od /Ob0 /WX /RTC1 /GR /MDd /EHsc")
    set(CMAKE_EXE_LINKER_FLAGS_INIT "/DEBUG /INCREMENTAL")
    set(CMAKE_SHARED_LINKER_FLAGS_INIT "/DEBUG /INCREMENTAL")
    set(CMAKE_STATIC_LINKER_FLAGS_INIT "/DEBUG /INCREMENTAL")
    set(CMAKE_MODULE_LINKER_FLAGS_INIT "/DEBUG /INCREMENTAL")
    
    set(CMAKE_EXECUTABLE_SUFFIX_CXX ".exe")
    set(CMAKE_EXECUTABLE_SUFFIX_C ".exe")
    
    set(CMAKE_SHARED_LIBRARY_SUFFIX_CXX ".dll")
    set(CMAKE_SHARED_LIBRARY_SUFFIX_C ".dll")
    
    set(CMAKE_SHARED_MODULE_SUFFIX_CXX ".dll")
    set(CMAKE_SHARED_MODULE_SUFFIX_C ".dll")
    
    set(CMAKE_STATIC_LIBRARY_SUFFIX_CXX ".lib")
    set(CMAKE_STATIC_LIBRARY_SUFFIX_C ".lib")
    
    set(CMAKE_IMPORT_LIBRARY_SUFFIX_CXX ".lib")
    set(CMAKE_IMPORT_LIBRARY_SUFFIX_C ".lib")
    

    Without an MVE, it's hard to know if this will work.