如何在CMake中将字符串跨多行拆分?

117

在我的项目中,我通常有一个策略,即不要在文本文件中创建超过80个字符长度的行,以便在各种编辑器中轻松编辑(你懂的)。但是在使用CMake时,我遇到了这样的问题:我不知道如何将一个简单的字符串拆分成多行以避免出现一行巨大的代码。考虑以下基本代码:

set(MYPROJ_VERSION_MAJOR "1")
set(MYPROJ_VERSION_MINOR "0")
set(MYPROJ_VERSION_PATCH "0")
set(MYPROJ_VERSION_EXTRA "rc1")
set(MYPROJ_VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}-${VERSION_EXTRA}")

已经超过了80个字符的限制。那么,我如何在CMake中将一行分成多行,而不会变得过于冗长(例如多个list(APPEND ...))?

7个回答

116

更新至CMake 3.0及以上版本

使用\可以实现行连续。请参阅最新的CMake文档

message("\
This is the first line of a quoted argument. \
In fact it is the only line but since it is long \
the source code uses line continuation.\
")

CMake版本的可用性:

Debian Wheezy (2013): 2.8.9
Debian Wheezy-backports: 2.8.11
Debian Jessy (2015): 3.0.2
Ubuntu 14.04 (LTS): 2.8.12
Ubuntu 15.04 : 3.0.2
Mac OSX :通过HomebrewMacportsFink提供cmake-3
Windows:通过Chocolatey提供cmake-3


33
CMake 3.0 的问题在于它不会忽略缩进。这意味着多行字符串不能与代码的其余部分缩进。 - void.pointer
@void.pointer 我也遇到了同样的问题,你解决了如何使用多行缩进的问题吗? - user3667089
如果想要使用缩进并且限制在80个字符以内,那么另一种方法是这样做:<code> message("这是变量的值: " <br> "${varValue}") </code> - munsingh

71

CMake 3.0及以上版本

使用string(CONCAT)命令:

set(MYPROJ_VERSION_MAJOR "1")
set(MYPROJ_VERSION_MINOR "0")
set(MYPROJ_VERSION_PATCH "0")
set(MYPROJ_VERSION_EXTRA "rc1")
string(CONCAT MYPROJ_VERSION "${MYPROJ_VERSION_MAJOR}"
                             ".${MYPROJ_VERSION_MINOR}"
                             ".${MYPROJ_VERSION_PATCH}"
                             "-${MYPROJ_VERSION_EXTRA}")

虽然CMake 3.0及以上版本支持引号参数的行续传递,但是如果你在第二行或后续行缩进,那么缩进空格将会包含在你的字符串中。

CMake 2.8及更早版本

你可以使用列表,每个列表元素可以放在新的一行上:

set(MYPROJ_VERSION_MAJOR "1")
set(MYPROJ_VERSION_MINOR "0")
set(MYPROJ_VERSION_PATCH "0")
set(MYPROJ_VERSION_EXTRA "rc1")
set(MYPROJ_VERSION_LIST "${MYPROJ_VERSION_MAJOR}"
                        ".${MYPROJ_VERSION_MINOR}"
                        ".${MYPROJ_VERSION_PATCH}"
                        "-${MYPROJ_VERSION_EXTRA}")

未使用引号的列表将不带空格连接:

message(STATUS "Version: " ${MYPROJ_VERSION_LIST})
-- Version: 1.0.0-rc1

如果你真的需要一个字符串,你可以先将列表转换为字符串:
string(REPLACE ";" "" MYPROJ_VERSION "${MYPROJ_VERSION_LIST}")
message(STATUS "Version: ${MYPROJ_VERSION}")
-- Version: 1.0.0-rc1

任何原始字符串中的分号将被视为列表元素分隔符并被删除。必须进行转义:
set(MY_LIST "Hello World "
            "with a \;semicolon")

1
对于非常长的行,即使在这个例子中不必要,在变量名后换行可以进一步改善这种模式。 - sage
出于好奇,猜测字符串中的双引号是否也需要用反斜杠转义,以及任何需要出现为字符的反斜杠是否也需要转义? - Jonathan Leffler
@JonathanLeffler 是的,它们需要转义。语言规则在这里:https://cmake.org/cmake/help/latest/manual/cmake-language.7.html#escape-sequences 但是有时会令人困惑。另请参阅:https://stackoverflow.com/a/40728463 - Douglas Royds

12
原问题中的示例仅涉及相对较短的字符串。 对于更长的字符串(包括其他答案中给出的示例),可能更适合使用方括号参数。来自文档的说明:

An opening bracket is written [ followed by zero or more = followed by [. The corresponding closing bracket is written ] followed by the same number of = followed by ]. Brackets do not nest. A unique length may always be chosen for the opening and closing brackets to contain closing brackets of other lengths.

[...]

For example:

message([=[
This is the first line in a bracket argument with bracket length 1.
No \-escape sequences or ${variable} references are evaluated.
This is always one argument even though it contains a ; character.
The text does not end on a closing bracket of length 0 like ]].
It does end in a closing bracket of length 1.
]=])

如果我理解正确的话,源代码中的换行符也会在字符串中引入一个换行符。例如,在“length 1.”之后将会有一个 \n。有什么方法可以避免这种情况吗? - Lukas Schmelzeisen
没错,会有\n。如果你不想要它,我认为括号参数不是你的解决方案。 - ingomueller.net
顺带一提,这适用于 3.x 版本,不适用于 2.x。 - Maxim Suslov

11

对于那些从如何将CMake生成器表达式拆分为多行?转到这里的人,我想添加一些注释。

行继续方法行不通,CMake无法解析由空格(缩进)和行继续制作的生成器列表。

虽然string(CONCAT)解决方案将提供可评估的生成器表达式,但如果结果包含空格,则评估的表达式将被引号包围。

对于每个要添加的单独选项,必须构建单独的生成器列表,因此像我下面所做的堆叠选项会导致构建失败:

string(CONCAT WARNING_OPTIONS "$<"
    "$<OR:"
        "$<CXX_COMPILER_ID:MSVC>,"
        "$<STREQUAL:${CMAKE_CXX_SIMULATE_ID},MSVC>"
    ">:"
    "/D_CRT_SECURE_NO_WARNINGS "
">$<"
    "$<AND:"
        "$<CXX_COMPILER_ID:Clang,GNU>,"
        "$<NOT:$<STREQUAL:${CMAKE_CXX_SIMULATE_ID},MSVC>>"
    ">:"
    "-Wall -Werror "
">$<"
    "$<CXX_COMPILER_ID:GNU>:"
    "-Wno-multichar -Wno-sign-compare "
">")
add_compile_options(${WARNING_OPTIONS})

这是因为生成的选项会用引号括起来传递给编译器

/usr/lib64/ccache/c++  -DGTEST_CREATE_SHARED_LIBRARY=1 -Dgtest_EXPORTS -I../ThirdParty/googletest/googletest/include -I../ThirdParty/googletest/googletest -std=c++11 -fno-rtti -fno-exceptions -fPIC    -std=c++11 -fno-rtti -fno-exceptions -Wall -Wshadow -DGTEST_HAS_PTHREAD=1 -fexceptions -Wextra -Wno-unused-parameter -Wno-missing-field-initializers "-Wall -Werror -Wno-multichar -Wno-sign-compare " -fdiagnostics-color -MD -MT ThirdParty/googletest/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o -MF ThirdParty/googletest/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o.d -o ThirdParty/googletest/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o -c ../ThirdParty/googletest/googletest/src/gtest-all.cc
c++: error: unrecognized command line option ‘-Wall -Werror -Wno-multichar -Wno-sign-compare ’
为了评估使用字符串(CONCAT)方法表示的冗长生成器表达式,每个生成器表达式必须计算为单个选项,且不带空格。
string(CONCAT WALL "$<"
    "$<AND:"
        "$<CXX_COMPILER_ID:Clang,GNU>,"
        "$<NOT:$<STREQUAL:${CMAKE_CXX_SIMULATE_ID},MSVC>>"
    ">:"
    "-Wall"
">")
string(CONCAT WERROR "$<"
    "$<AND:"
        "$<CXX_COMPILER_ID:Clang,GNU>,"
        "$<NOT:$<STREQUAL:${CMAKE_CXX_SIMULATE_ID},MSVC>>"
    ">:"
    "-Werror"
">")
message(STATUS "Warning Options: " ${WALL} ${WERROR})
add_compile_options(${WALL} ${WERROR})

我即将要回答的问题可能与这个问题无关,不幸的是,我要回答的问题被错误地标记为与此问题重复。

生成器列表的处理和解析方式与字符串不同,因此,在将生成器列表跨多行拆分时,需要采取其他措施。


2
在链接的“重复”上发布此答案的版本可能也是值得的。当我遇到它时,这就是我正在寻找的答案。 - Keith Prussing
这个混乱的版本在哪里有可维护的版本? - Lothar
与语法问题无关,但我认为你的示例代码应该使用CXX_COMPILER_FRONTEND_VARIANT。 - Johan Boulé

9

这段代码还是有点啰嗦,但如果80个字符的限制让你感到很不舒服,那么你可以将内容重复添加到同一个变量中:

set(MYPROJ_VERSION_MAJOR "1")
set(MYPROJ_VERSION_MINOR "0")
set(MYPROJ_VERSION_PATCH "0")
set(MYPROJ_VERSION_EXTRA "rc1")
set(MYPROJ_VERSION "${MYPROJ_VERSION_MAJOR}.")
set(MYPROJ_VERSION "${MYPROJ_VERSION}${MYPROJ_VERSION_MINOR}.")
set(MYPROJ_VERSION "${MYPROJ_VERSION}${MYPROJ_VERSION_PATCH}-")
set(MYPROJ_VERSION "${MYPROJ_VERSION}${MYPROJ_VERSION_EXTRA}")
message(STATUS "version: ${MYPROJ_VERSION}")

输出结果为:
$ cmake  ~/project/tmp
-- version: 1.0.0-rc1
-- Configuring done
-- Generating done
-- Build files have been written to: /home/rsanderson/build/temp

7

在CMakeLists.txt文件或CMake脚本中,无法将字符串字面值跨多行拆分。如果您在字符串中包含换行符,则该字符串本身将包含一个字面意义的换行符。

# Don't do this, it won't work, MYPROJ_VERSION will contain newline characters:
set(MYPROJ_VERSION "${VERSION_MAJOR}.
  ${VERSION_MINOR}.${VERSION_PATCH}-
  ${VERSION_EXTRA}")

然而,CMake使用空格来分隔参数,因此您可以将作为参数分隔符的空格更改为任何您喜欢的换行符,而不会改变其行为。
您可以重新表达这个较长的语句:
set(MYPROJ_VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}-${VERSION_EXTRA}")

作为这两行较短的句子:
set(MYPROJ_VERSION
  "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}-${VERSION_EXTRA}")

它们是完全等价的。

7
为了保持代码的良好缩进,只需要简单地执行以下操作:
message("These strings " "will all be "
        "concatenated. Don't forget "
        "your trailing spaces!")

或直接使用字符串形式

string(CONCAT MYSTR "This and " "that "
                    "and me too!")

Douglas' answer中所述,其中有更多的细节。但是我认为这可能只总结了要点。


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