I'm trying to persuade cmake to add a custom target which builds a precompiled header on Visual Studio (note: please do not suggest I used a custom build step instead, I specifically need a build target which builds a precompiled header). Note that you cannot simply add CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE} during the configuration stage as that won't work in generated .vcxproj's where the user can change the build configuration, so I need to somehow tell cmake in its build stage to output the right stuff for each possible configuration i.e. the appropriate configuration's CMAKE_CXX_FLAGS and CMAKE_CXX_FLAGS_$<CMAKE_BUILD_TYPE>
I'm very nearly there with this solution, except for one problem I'm asking for help with:
# Adds a custom target which generates a precompiled header
function(add_precompiled_header outvar headerpath)
get_filename_component(header "${headerpath}" NAME)
set(pchpath ${CMAKE_CURRENT_BINARY_DIR}/${header}.dir/${CMAKE_CFG_INTDIR}/${header}.pch)
set(flags ${CMAKE_CXX_FLAGS})
separate_arguments(flags)
set(flags ${flags}
$<$<CONFIG:Debug>:${CMAKE_CXX_FLAGS_DEBUG}>
$<$<CONFIG:Release>:${CMAKE_CXX_FLAGS_RELEASE}>
$<$<CONFIG:RelWithDebInfo>:${CMAKE_CXX_FLAGS_RELWITHDEBINFO}>
$<$<CONFIG:MinSizeRel>:${CMAKE_CXX_FLAGS_MINSIZEREL}>
)
if(MSVC)
add_custom_target(${outvar}
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/${header}.dir/${CMAKE_CFG_INTDIR}"
COMMAND ${CMAKE_CXX_COMPILER} /c ${flags} /Fp"${pchpath}" /Yc"${header}" /Tp"${CMAKE_CURRENT_SOURCE_DIR}/${headerpath}"
COMMENT "Precompiling header ${headerpath} ..."
SOURCES "${headerpath}"
)
endif()
endfunction()
This very nearly works where the correct flags for each build configuration have been expanded into their respective command stanzas in the .vcxproj file:
<Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">setlocal
"G:\Program Files\CMake\bin\cmake.exe" -E make_directory G:/boost.afio/cmake/afio.hpp.dir/$(Configuration)
if %errorlevel% neq 0 goto :cmEnd
"G:\Program Files\Microsoft Visual Studio 14.0\VC\bin\cl.exe" /c /DWIN32 /D_WINDOWS /W3 /GR /EHsc "/MD /O2 /Ob2 /D NDEBUG" /Fp"G:/boost.afio/cmake/afio.hpp.dir/$(Configuration)/afio.hpp.pch" /Yc"afio.hpp" /Tp"G:/boost.afio/include/boost/afio/afio.hpp"
if %errorlevel% neq 0 goto :cmEnd
:cmEnd
endlocal & call :cmErrorLevel %errorlevel% & goto :cmDone
:cmErrorLevel
exit /b %1
:cmDone
if %errorlevel% neq 0 goto :VCEnd
</Command>
The problem is that the generator $<$<CONFIG:Release>:${CMAKE_CXX_FLAGS_RELEASE}>
due to containing spaces expands into the quoted string "/MD /O2 /Ob2 /D NDEBUG"
and this of course causes the compiler to complain loudly.
What I need therefore is either one of:
- Some method of telling cmake generator expressions to not expand content containing a space into a quoted string.
OR
- Some other method of expanding CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE} into each of the configuration specific sections for an add_custom_target.
Many thanks in advance.
Edit: Based on Tsyvarev's answer below, I came up with this:
# Add generator expressions to appendvar expanding at build time any remaining parameters
# if the build configuration is config
function(expand_at_build_if_config config appendvar)
set(ret ${${appendvar}})
set(items ${ARGV})
list(REMOVE_AT items 0 1)
separate_arguments(items)
foreach(item ${items})
list(APPEND ret $<$<CONFIG:${config}>:${item}>)
endforeach()
set(${appendvar} ${ret} PARENT_SCOPE)
endfunction()
This is working well. Many thanks to Tsyvarev!
Edit 2: It turns out cmake has undocumented support for precompiled header generation at least for MSVC. Take a look at the add_precompiled_header()
function in https://github.com/ned14/boost-lite/blob/master/cmake/BoostLitePrecompiledHeader.cmake, all you need to do is supply the /Yc
flag to an OBJECT type library and voila, the Visual Studio generator does the right thing, correct .vcxproj XML stanzas and all.