CMake用STREQUAL与空字符串进行比较失败。

51
我一直认为,如果你想比较两个字符串(而不是变量),你只需要像这样引用它:
if("${A}" STREQUAL "some string")

但是现在我发现这段代码有时会打印"oops"。
cmake_minimum_required(VERSION 2.8)

if("d" STREQUAL "")
  message("oops...")
endif()

也许这是一个错误(因为它打印出 Xcode,但不打印 make)?
或者有一些特殊的变量?
cmake: 2.8.12, 2.8.11.2 xcode: 4.6.2, 5.0.1
更新
有一个 string 命令没有描述的问题:
string(COMPARE EQUAL "${A}" "" result)
if(result)
  message("...")
endif()

更新 2

我所期望的行为已从 CMake 3.1.0 版本开始实施(参见 CMP0054)。

3.0.2 版本的输出为 test

CMake version: 3.0.2
Quoted test
Surprise!
Unquoted test
Surprise!

3.1.0版本的输出test

CMake version: 3.1.0
Quoted test
OK
Unquoted test
Surprise!

有时候,它可能会进入(VAR STREQUAL“”)的条件,当“variable”未定义时。在这种情况下(NOT DEFINED VAR),可以设置默认值(例如)。参考链接:https://cmake.org/cmake/help/v3.3/command/if.html - parasrish
我以为中间操作符已经被弃用了?在CMAKE中,新旧之分很难确定。 - Sandburg
2个回答

67

你遇到了CMake的一个相当令人烦恼的“这不是一个错误,而是一个特性”的行为。根据if命令的文档所述:

 The if command was written very early in CMake's history, predating the ${} 
 variable evaluation syntax, and for convenience evaluates variables named
 by its arguments as shown in the above signatures.

嗯,方便之处竟然成了不便之处。在你的例子中,字符串"d"if命令视为一个名为d的变量。如果变量d恰好定义为空字符串,则消息语句将打印“oops...”,例如:

set (d "")
if("d" STREQUAL "")
  # this branch will be taken
  message("oops...")
else()
  message("fine")
endif()

这可能会给类似于以下语句的出人意料的结果:

if("${A}" STREQUAL "some string")

因为如果变量A恰好被定义为一个CMake变量的名称所对应的字符串,那么第一个参数可能会产生意外的二次扩展。

set (A "d")
set (d "some string")   
if("${A}" STREQUAL "some string")
  # this branch will be taken
  message("oops...")
else()
  message("fine")
endif()

可能的解决方法:

您可以在${}扩展后将后缀字符添加到字符串中,以防止if语句进行自动评估:

set (A "d")
set (d "some string")
if("${A} " STREQUAL "some string ")
  message("oops...")
else()
  # this branch will be taken
  message("fine")
endif()

不要使用${}扩展:

set (A "d")
set (d "some string")
if(A STREQUAL "some string")
  message("oops...")
else()
  # this branch will be taken
  message("fine")
endif()
为了避免在 STREQUAL 右侧的意外评估,请改用带有CMake 正则表达式MATCHES
if(A MATCHES "^value$")
  ...
endif()

附加说明:CMake 3.1不再对带引号的参数进行双重扩展。请参见新策略


1
是的,我当然已经阅读了这部分。文档中没有双引号注释,并且希望有某种扩展保护,使得 if(d ...) 不同于 if("d" ...). 结果发现它们是相同的:if(DEFINED "d") 就是 TRUE 。并且由于某些原因,d 被一个生成器(Xcode)定义了,但对另一个生成器(make)则未定义。顺便说一下,有人可以在变量名称上使用后缀:set("d " "other string") 可能更好使用一些重的词,如 _NEVER_NAME_VARIABLE_LIKE_THAT (: - user2288008
使用空格作为后缀字符可能已经足够了,因为您无法访问名称包含空格的变量(在您的示例中,变量 d 无法访问)。我同意您应该避免使用其他字符,因此如果不使用空格,请使用长字符串,就像您的示例一样。 - Fraser
2
@Fraser 为什么不能访问包含空格的变量名?例如:set("A " "some string") set(VAR_NAME "A ") message(${${VAR_NAME}}) 输出:some string。我知道这个例子看起来很人造,但如果你经常使用包装器,你会在实际代码中遇到这种情况。类似这样:set(VAR_NAME "${FIRST} ${SECOND}") 如果 SECOND 是空的,你将得到以空格结尾的 VAR_NAME - user2288008
你可能会好奇(因为SO不会通知兄弟回答),自CMake 3.1以来,这已经不再是真的了。 - Arthur Tacca
1
@ArthurTacca 不幸的是,仍然相当普遍地使用旧版本的CMake(例如2.8)。 - sakra
@sakra 绝对同意! - Arthur Tacca

16
自 CMake 3.1 起,if() 中有新的变量展开规则。只需在项目文件顶部设置cmake_minimum_required(3.1)(或更高版本),或者使用较低的最小版本号但手动将策略 CMP0054 设置为NEW即可启用它们。
即使在这种情况下,仍然是真的,if 的第一个参数将使用与该名称匹配的变量的值进行展开,如果存在:
set (d "")
if(d STREQUAL "")
  # this branch will be taken
  message("oops...")
else()
  message("fine")
endif()

然而,如果第一个参数被引用,这将被禁用:

set (d "")
if("d" STREQUAL "")
  message("oops...")
else()
  # due to quotes around "d" in if statement,
  # this branch will be taken
  message("fine")
endif()

如果你想测试一个变量的内容是否与某个值相等,可以使用经典的非引用语法,或者使用你提出的"${d}"语法。由于新规则的存在,这种方法将不会遭受sakra在回答中提到的双重扩展问题:

set (A "d")
set (d "some string")   
if("${A}" STREQUAL "d")
  # this branch will be taken
  message("fine")
elseif("${A}" STREQUAL "some string")
  message("oops...")
else()
  message("??")
endif()

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