Skip to content
GitLab
  • Menu
Projects Groups Snippets
  • /
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Sign in / Register
  • CMake CMake
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
  • Issues 3,923
    • Issues 3,923
    • List
    • Boards
    • Service Desk
    • Milestones
  • Merge requests 15
    • Merge requests 15
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Deployments
    • Deployments
    • Releases
  • Packages & Registries
    • Packages & Registries
    • Container Registry
  • Analytics
    • Analytics
    • Value stream
    • CI/CD
    • Repository
  • External wiki
    • External wiki
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • CMake
  • CMakeCMake
  • Issues
  • #21905
Closed
Open
Created Mar 08, 2021 by Ghost User@ghostContributor

VS: Custom MSVC toolchain investigation

We have a custom MSVC toolchain. However, we were getting an issue related to RC files. That only manifested on Ninja dirty builds.

# Here is how the bug will manifest itself:
# "ninja: error: FindFirstFileExA(note: including file: d:/foobar/ms_sdk/n20246/10/include/10.0.20246.0/shared): The filename, directory name, or volume label syntax is incorrect."

I've posted about the issue here on the discourse:

https://discourse.cmake.org/t/setting-cmake-lang-architecture-id-cmake-cl-showincludes-prefix-to-get-rc-files-working/2920/1

cmake_minimum_required(VERSION 3.15)

set(DEVELOPMENT_KIT_ROOT "C:/foobar")
set(FOOBAR_MSVC_VERSION "14.24.28314")
set(FOOBAR_WINDOWS_VERSION "19041")
set(FOOBAR_ARCH "x64")
set(FOOBAR_TOOLSET "x64")

list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES
        DEVELOPMENT_KIT_ROOT
        FOOBAR_MSVC_VERSION
        FOOBAR_WINDOWS_VERSION
        FOOBAR_ARCH
        FOOBAR_TOOLSET
)

# Define the target system and version:
# Only set CMAKE_SYSTEM_NAME when it's different than the host system name.
# Otherwise CMAKE_CROSSCOMPILING will be set to true unneccessarily.
# Which can cause longer builds for developers in some situations.
# https://discourse.cmake.org/t/do-i-need-to-set-the-cmake-system-name-when-making-a-toolchain-file/2861
if (NOT (CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") )
    set(CMAKE_SYSTEM_NAME     "Windows" CACHE INTERNAL "")
endif()
set(CMAKE_SYSTEM_VERSION  "10.0.${FOOBAR_WINDOWS_VERSION}.0" CACHE INTERNAL "")

if(FOOBAR_ARCH STREQUAL "x64")
    set(CMAKE_SYSTEM_PROCESSOR   AMD64)
elseif(FOOBAR_ARCH STREQUAL "x86")
    set(CMAKE_SYSTEM_PROCESSOR     x86)
else()
    message(FATAL_ERROR "The only supported target architectures are x86 and x64!")
endif()

# Error check toolset
if (FOOBAR_TOOLSET STREQUAL "x64")
elseif (FOOBAR_TOOLSET STREQUAL "x86")
else()
    message(FATAL_ERROR "The only supported toolsets are x64 and x86!")
endif()

# Setup FOOBAR DK directories
set(FOOBAR_MSVC_ROOT        ${DEVELOPMENT_KIT_ROOT}/vc/${FOOBAR_MSVC_VERSION})
set(FOOBAR_MSVC_BIN_TOOLSET ${FOOBAR_MSVC_ROOT}/bin/Host${FOOBAR_TOOLSET}/${FOOBAR_ARCH})
set(FOOBAR_SDK_ROOT         ${DEVELOPMENT_KIT_ROOT}/ms_sdk/n${FOOBAR_WINDOWS_VERSION}/10)
set(FOOBAR_SDK_BIN_TOOLSET  ${FOOBAR_SDK_ROOT}/bin/10.0.${FOOBAR_WINDOWS_VERSION}.0/${FOOBAR_TOOLSET})
set(FOOBAR_SDK_LIB          ${FOOBAR_SDK_ROOT}/lib/10.0.${FOOBAR_WINDOWS_VERSION}.0)
set(FOOBAR_SDK_INC          ${FOOBAR_SDK_ROOT}/include/10.0.${FOOBAR_WINDOWS_VERSION}.0)
set(FOOBAR_WDK_ROOT         ${DEVELOPMENT_KIT_ROOT}/ms_wdk/n${FOOBAR_WINDOWS_VERSION})
set(FOOBAR_WDK_LIB          ${FOOBAR_WDK_ROOT}/lib/10.0.${FOOBAR_WINDOWS_VERSION}.0)
set(FOOBAR_WDK_INC          ${FOOBAR_WDK_ROOT}/include/10.0.${FOOBAR_WINDOWS_VERSION}.0)

# Error checking
# I'm checking if the DEVELOPMENT_KIT_ROOT is defined as an absolute path
# because IS_DIRECTORY is only well defined if the path is absolute
# https://cmake.org/cmake/help/latest/command/if.html?highlight=is_directory#if
if ( IS_ABSOLUTE ${DEVELOPMENT_KIT_ROOT} )

    set(FOOBAR_DK_DIRECTORIES_TO_ERROR_CHECK
        ${FOOBAR_MSVC_ROOT} ${FOOBAR_SDK_ROOT} ${FOOBAR_SDK_LIB}
        ${FOOBAR_SDK_INC} ${FOOBAR_WDK_ROOT} ${FOOBAR_WDK_LIB} ${FOOBAR_WDK_INC}
        ${FOOBAR_SDK_BIN_TOOLSET} ${FOOBAR_MSVC_BIN_TOOLSET}
    )

    # Make sure each directory exists
    foreach(dir IN LISTS FOOBAR_DK_DIRECTORIES_TO_ERROR_CHECK)
        if (NOT IS_DIRECTORY ${dir})
            message(FATAL_ERROR "Include directory is not valid: ${dir}\n"
                                "Make sure to check your perforce mappings!\n")
        endif()
    endforeach()
endif()

# Cross Compiler
# In order to pass Visual Studio compiler identification we have to establish these variables
foreach(LANG  C CXX)
    set(CMAKE_${LANG}_COMPILER "${FOOBAR_MSVC_BIN_TOOLSET}/cl.exe" CACHE FILEPATH "")

    set(CMAKE_${LANG}_COMPILER_ID "MSVC" CACHE STRING "")

    # Extract the compiler version
    execute_process(
        # Execute cl.exe on the command line to extract the compiler version
        COMMAND ${CMAKE_${LANG}_COMPILER}
        # cl.exe outputs some of its output to standard error
        # EX:
        #   Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27032.1 for x64
        #   Copyright (C) Microsoft Corporation.  All rights reserved.
        ERROR_VARIABLE  cl_exe_ouput
        # cl.exe outputs some of its output to standard output
        # So just ignore this it isn't important
        # EX:
        #  usage: cl [ option...  ] filename...  [ /link linkoption...  ]
        OUTPUT_QUIET
    )

    # Regex for the compiler version
    string(REGEX MATCH ".* Version ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\).*" out ${cl_exe_ouput})

    # If the regex fails then fatal error
    if (NOT (${CMAKE_MATCH_COUNT} EQUAL "1"))
        message(FATAL_ERROR "MSVC version regex failed")
    endif()

    # You have to set the compiler version
    set(CMAKE_${LANG}_COMPILER_VERSION "${CMAKE_MATCH_1}" CACHE STRING "")
endforeach()

# This variable is neccessary to set for MSVC compilers greater than version 19.0 and greater
#
# Here is my attempt at an explanation (since there is no documentation online):
# VS2015 update 3 and above support language standard level flags, with the default and minimum level
# being C++14. This allows cmake to set variables/compiler-flags much more appropriately.
#
# To see the exact details see this module in your cmake implemenation.
# ...\share\cmake-3.16\Modules\Compiler\MSVC-CXX.cmake
#
# If that still doesn't make sense remove this check/set below
#
# To be honest although I know this logic below fixes the problem and is correct.
# It is still pretty hacky, and is a good argument to not use the DK in the future.
if (${CMAKE_CXX_COMPILER_VERSION} VERSION_GREATER "19.0")
    set(CMAKE_CXX_STANDARD_COMPUTED_DEFAULT "14"  CACHE INTERNAL "")
endif()

# Cross Tools
set(CMAKE_RC_COMPILER "${FOOBAR_SDK_BIN_TOOLSET}/rc.exe" CACHE FILEPATH "")
set(CMAKE_MT          "${FOOBAR_SDK_BIN_TOOLSET}/mt.exe" CACHE FILEPATH "")

# Generator Tools
if (CMAKE_GENERATOR MATCHES "Visual Studio")
    # MSVC_IDE doesn't seem to work in toolchains?
    # https://discourse.cmake.org/t/cannot-use-msvc-ide-in-toolchain/2734
    set(FOOBAR_MSVC_IDE ON)
endif()

if (FOOBAR_MSVC_IDE)
    # Build with Multiple Processes, this is a nice quality of life for VS users
    # This is the preferred way of speeding up vs builds.
    # Also /MP option enables /FS by default.
    set(genFlag "/MP")
else()
    # If multiple CL.EXE write to the same .PDB file, please use /FS
    # This ensures multi-threads builds like Ninja work properly
    set(genFlag "/FS")
endif()

# Set the flags all projects will be forced to use.
# https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_FLAGS_INIT.html
foreach(LANG  C CXX)
    set(CMAKE_${LANG}_FLAGS_INIT "${genFlag}")
endforeach()

set(FOOBAR_DK_INCLUDE_DIRECTORIES
    "${FOOBAR_MSVC_ROOT}/include"
    "${FOOBAR_SDK_INC}/ucrt"
    "${FOOBAR_SDK_INC}/um"
    "${FOOBAR_SDK_INC}/shared"
    "${FOOBAR_WDK_INC}/km"
    "${FOOBAR_WDK_INC}/um"
    "${FOOBAR_WDK_INC}/shared"
)

set(FOOBAR_DK_LIBRARY_DIRECTORIES
    "${FOOBAR_MSVC_ROOT}/lib/${FOOBAR_ARCH}"
    "${FOOBAR_MSVC_ROOT}/atlmfc/lib/${FOOBAR_ARCH}"
    "${FOOBAR_SDK_LIB}/ucrt/${FOOBAR_ARCH}"
    "${FOOBAR_SDK_LIB}/um/${FOOBAR_ARCH}"
    "${FOOBAR_WDK_LIB}/km/${FOOBAR_ARCH}"
    "${FOOBAR_WDK_LIB}/um/${FOOBAR_ARCH}"
)

if (FOOBAR_MSVC_IDE)
    string(REGEX MATCHALL "Visual Studio [0-9]+ ([0-9]+)" vsVersions ${CMAKE_GENERATOR})
    set(vsYear ${CMAKE_MATCH_1})

    if (${vsYear} VERSION_LESS "2010")
        message(FATAL_ERROR "CMAKE_VS_SDK_*_DIRECTORIES require at least Visual Studio 2010")
    endif()

    # Set the appropriate binary directories
    set(CMAKE_VS_SDK_EXECUTABLE_DIRECTORIES "")
    list(APPEND CMAKE_VS_SDK_EXECUTABLE_DIRECTORIES ${FOOBAR_MSVC_BIN_TOOLSET})
    list(APPEND CMAKE_VS_SDK_EXECUTABLE_DIRECTORIES ${FOOBAR_SDK_BIN_TOOLSET})

    # Don't allow MSVC defaults
    set(CMAKE_VS_SDK_SOURCE_DIRECTORIES "")
    set(CMAKE_VS_SDK_LIBRARY_WINRT_DIRECTORIES "")
    set(CMAKE_VS_SDK_REFERENCE_DIRECTORIES "")
    set(CMAKE_VS_SDK_EXCLUDE_DIRECTORIES "")

    # Set include/library directories
    set(CMAKE_VS_SDK_LIBRARY_DIRECTORIES ${FOOBAR_DK_LIBRARY_DIRECTORIES})
    set(CMAKE_VS_SDK_INCLUDE_DIRECTORIES ${FOOBAR_DK_INCLUDE_DIRECTORIES})

    # Platform Selection
    # https://cmake.org/cmake/help/latest/generator/Visual%20Studio%2016%202019.html#platform-selection
    if ( ${vsYear} VERSION_GREATER_EQUAL "2019")
        if(FOOBAR_ARCH STREQUAL "x64")
            set(CMAKE_GENERATOR_PLATFORM "x64")
        elseif(FOOBAR_ARCH STREQUAL "x86")
            set(CMAKE_GENERATOR_PLATFORM "Win32")
        else()
            message(FATAL_ERROR "Unsupported platform ${FOOBAR_ARCH}")
        endif()
    endif()

    # Toolset Selection
    # https://cmake.org/cmake/help/latest/generator/Visual%20Studio%2016%202019.html#toolset-selection
    # host=<arch> specifies the host tools architecture as x64 or x86
    #
    # This is basically a quality of life to avoid cluttering the command line
    set(CMAKE_GENERATOR_TOOLSET "host=${FOOBAR_TOOLSET}")

    # Work Around:
    # This command is only meaningful in the sense that it prevents Cmake from
    # choosing a default toolset. 'FOOBAR_DK_TOOLSET' has no instrinsic value
    # If you get rid of this, Visual Studio will still build correctly
    # However, configuration will give weird output.
    # It will list the default MSVC compiler instead of the one we chose.
    set(CMAKE_VS_PLATFORM_TOOLSET_VERSION "FOOBAR_DK_TOOLSET")

else()

    # Link Directories
    foreach(TARGET  EXE SHARED STATIC MODULE)

        # W/A: CMake doesn't define CMAKE_<LANG>_STANDARD_LINK_DIRECTORIES.
        #      https://gitlab.kitware.com/cmake/cmake/issues/18222
        set(CMAKE_${TARGET}_LINKER_FLAGS_INIT "")

        foreach(dir IN LISTS FOOBAR_DK_LIBRARY_DIRECTORIES)
            list(APPEND CMAKE_${TARGET}_LINKER_FLAGS_INIT
                "/LIBPATH:${dir}"
            )
        endforeach()

        # Neccessary for linker syntax
        string(REPLACE ";" " " CMAKE_${TARGET}_LINKER_FLAGS_INIT "${CMAKE_${TARGET}_LINKER_FLAGS_INIT}")

    endforeach()

    # Include Directories
    # NOTE: C/CXX make sense. The tricky one is RC. This is neccessary to support resource files.
    foreach(LANG  C CXX RC)

        set(CMAKE_${LANG}_STANDARD_INCLUDE_DIRECTORIES ${FOOBAR_DK_INCLUDE_DIRECTORIES})

    endforeach()

    # This is neccessary to ensure RC files compile with our custom toolchain
    # Otherwise dirty builds will break. This code basically patches a cmake bug.
    #
    # Here is the bug will manifest itself:
    # "ninja: error: FindFirstFileExA(note: including file: d:/p4/DEVELOPMENT_KIT_ROOT/win/ms_sdk/n20246/10/include/10.0.20246.0/shared): The filename, directory name, or volume label syntax is incorrect."
    if (CMAKE_GENERATOR MATCHES "Ninja")
        # Compiling RC files needs to set this. The Ninja generator calls cmcldeps.exe to extract dependencies for RC
        # files. But it configurates DepType to "gcc" so cmake cannot filter show includes prefix and the dependencies
        # are stored in .ninja_deps with the prefix and cause next run of ninja to fail. The DepType cannot be forced
        # to "msvc" otherwise the dependency changes won't trigger recompilation of RC files.
        # The workaround is to set CMAKE_CL_SHOWINCLUDES_PREFIX to the exact English string "Note: including file: ".
        # function CMAKE_DETERMINE_MSVC_SHOWINCLUDES_PREFIX may be used if any locale issue occurs.
        set(CMAKE_CL_SHOWINCLUDES_PREFIX "Note: including file: " CACHE INTERNAL "FOOBAR WORKAROUND")

        # The Ninja generator relies on the following variables to determine MSVC style PDB names. But these variables
        # stored in cmake are only initialized to "" when it is first time run on a project before CMake${lang}Compiler
        # .cmake are generated. The next run doesn't initilazed them because CMake${lang}Compiler.cmake are there. So
        # they are null and SetMsvcTargetPdbVariable() returns false so ".dbg" suffix is used for PDBs.
        # The workaround is to set them to either x64 or x86 (actually "" is enough).
        #
        # https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_COMPILER_ARCHITECTURE_ID.html?highlight=architecture_id
        set(CMAKE_CXX_ARCHITECTURE_ID ${FOOBAR_ARCH} CACHE INTERNAL "FOOBAR WORKAROUND")
        set(CMAKE_C_ARCHITECTURE_ID ${FOOBAR_ARCH} CACHE INTERNAL "FOOBAR WORKAROUND")
    endif()
endif()

# In conjunction with the variables below this makes all find_* commands
# only look in the DK
set(CMAKE_FIND_ROOT_PATH "${DEVELOPMENT_KIT_ROOT}")

# Make the find_file command only look in the DK
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY)
# Make the find_package command only look in the DK
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# Make the find_library command only look in the DK
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
# Make the find_package command only look in the DK
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

I've done my best to sanitize my companies work and still providing a meaningful toolchain to the CMake team.

This toolchain is currently tested against Vs2019/Ninja. However it's only the Ninja path that is having problems.

So currently all 5 of these variables are necessary to compile our project with an MSVC toolchain.

set(CMAKE_CXX_STANDARD_COMPUTED_DEFAULT "14"  CACHE INTERNAL "")
set(CMAKE_C_STANDARD_COMPUTED_DEFAULT "90"  CACHE INTERNAL "")

# These are necessary for RC files
set(CMAKE_CL_SHOWINCLUDES_PREFIX "Note: including file: " CACHE INTERNAL "FOOBAR WORKAROUND")
set(CMAKE_CXX_ARCHITECTURE_ID "x64" CACHE INTERNAL "FOOBAR WORKAROUND")
set(CMAKE_C_ARCHITECTURE_ID "x64" CACHE INTERNAL "FOOBAR WORKAROUND")

Should CMake toolchain authors be aware of these variables?

Edited Mar 11, 2021 by Brad King
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information
Assignee
Assign to
Time tracking