如何将配置特定的CMake生成表达式添加到add_custom_target中

3
我正在努力说服cmake添加一个自定义目标,该目标在Visual Studio上构建预编译头文件(注意:请不要建议我使用自定义构建步骤,我特别需要构建目标来构建预编译头文件)。请注意,在配置阶段不能简单地添加CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE},因为在生成的.vcxproj中用户可以更改生成配置,因此我需要以某种方式告诉cmake在其构建阶段为每个可能的配置输出正确的内容,即适当配置的CMAKE_CXX_FLAGS和CMAKE_CXX_FLAGS_$<CMAKE_BUILD_TYPE>。
我已经非常接近解决这个问题了,除了一个我需要帮助解决的问题。
# 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()

这个几乎可以工作,只要每个构建配置的正确标志已经扩展到其相应的命令段落中的 .vcxproj 文件中:
<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 &amp; call :cmErrorLevel %errorlevel% &amp; goto :cmDone
:cmErrorLevel
  exit /b %1
:cmDone
  if %errorlevel% neq 0 goto :VCEnd
</Command>

问题在于生成器$<$<CONFIG:Release>:${CMAKE_CXX_FLAGS_RELEASE}>中包含空格,因此会扩展为带引号的字符串"/MD /O2 /Ob2 /D NDEBUG",这当然会导致编译器大声抱怨。
因此,我需要以下之一:
  1. 告诉cmake生成器表达式不要将包含空格的内容扩展为带引号的字符串。
或者
  1. 通过其他方法将CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE}扩展到每个add_custom_target的特定配置部分。
提前致谢。 编辑:根据Tsyvarev下面的答案,我得出了这个:
# 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()

这很有效。非常感谢Tsyvarev! 编辑2:原来cmake支持预编译头文件生成,至少对于MSVC是如此,可以查看https://github.com/ned14/boost-lite/blob/master/cmake/BoostLitePrecompiledHeader.cmake中的add_precompiled_header()函数,你只需要为OBJECT类型库提供/Yc标志,Visual Studio生成器就会正确处理,包括正确的.vcxproj XML代码块。
2个回答

3

FLAGS按空格分割,对于每个标记,附加相应的生成表达式:

# Process Release flags.
set(FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE})
separate_arguments(FLAGS_RELEASE) # Flags are ready for iterate

foreach(FLAG_RELEASE ${FLAGS_RELEASE})
    list(APPEND flags $<$<CONFIG:Release>:${FLAG_RELEASE}>)
endforeach(FLAGS_RELEASE ${FLAGS_RELEASE})
# Flags for other build types are processed in the same way.
# ...

# Now 'flags' variable may be used for COMMAND

如果你已经将参数放在列表中,那么只需在add_custom_target()中添加VERBATIM是否就足够了呢?我认为你不必在每个标志之前迭代并添加$<$<CONFIG:Release>:...>。例如,请参见这里 - Florian
不清楚在Windows上的情况,但在Linux上(Makefiles),如果值是列表,CMake会错误地扩展生成器表达式。* VERBATIM *对此无益。 - Tsyvarev
恶心。但可能会起作用。我明天进行测试后再回复你。 - Niall Douglas
@NiallDouglas 只是跟进我的评论:我尝试使用 VERBATIM 来处理你问题的示例,但并没有改善情况。 - Florian
@Florian 我已经尝试过VERBATIM,但它并没有起到帮助作用。此外,该选项会在预编译头文件路径周围的双引号前添加反斜杠,这非常不方便。 - Niall Douglas
确认,我刚刚根据你的答案添加了一个示例实现。谢谢! - Niall Douglas

1
我尝试了@Tsyvarev的方法,它完美地解决了问题。
一般来说,你遇到的问题被认为是CMake本身的缺陷或功能请求。你可能希望支持 CMake 仍然未解决的Issue #14353
当编译器选项的生成表达式不起作用时,我只想添加一个简单的替代方案,使用响应文件
file(
    WRITE "${CMAKE_CURRENT_BINARY_DIR}/Debug.flags" 
        "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_DEBUG}"
)
file(
    WRITE "${CMAKE_CURRENT_BINARY_DIR}/Release.flags" 
        "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_RELEASE}"
)
file(
    WRITE "${CMAKE_CURRENT_BINARY_DIR}/RelWithDebInfo.flags" 
        "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}"
)
file(
    WRITE "${CMAKE_CURRENT_BINARY_DIR}/MinSizeRel.flags" 
        "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_MINSIZEREL}"
)

# Now 'flags' files may be used for COMMAND
add_custom_target(
    ${outvar}
    COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/${header}.dir/${CMAKE_CFG_INTDIR}"
    COMMAND ${CMAKE_CXX_COMPILER} /c @$<CONFIG>.flags /Fp"${pchpath}" /Yc"${header}" /Tp"${CMAKE_CURRENT_SOURCE_DIR}/${headerpath}"
    COMMENT "Precompiling header ${headerpath} ..."
    SOURCES "${headerpath}"
)

参考资料


另一个非常相关的CMake问题是https://gitlab.kitware.com/cmake/cmake/issues/9974 - Niall Douglas
我不太愿意使用单独的文件,因为与Linux、FreeBSD或OS X相比,Windows文件系统在创建文件时特别慢。如果应用于大规模的项目中,这种技术会使cmake在Windows上明显变慢。不过,还是感谢您提供的想法,它可能对其他人有用。 - Niall Douglas
@NiallDouglas 不用谢。顺便说一下,优化CMake启用的项目的周转时间是我的一个正在进行的项目(请参见此处)。 - Florian
我正在逐步构建用于下一代标准的C++库的cmake工具,类似于“Boost 2.0”。这个cmake工具与通常的工具有很大不同,它可以自适应任何具有Boost目录布局的C++库,并将在未来自动使用C++模块等。由于我只使用了最快的cmake编程技术来遍历嵌套头文件库的深层次层次结构,因此它也非常快速。我认为它在明年之前对任何人都没有用处,但您可能希望给https://github.com/ned14/boost-lite点个赞,并不时地查看一下。 - Niall Douglas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接