As you've observed, set(varname ${varname} value)
appends a value to a variable using a semicolon as a separator (which you). (Note that there's another way you can append to list variables: list(APPEND <list> [<element> ...])
).
To understand how this is working / why this is happening, you need to understand how variable referencing works, how lists work, how the set
command works, and how command argument handling works.
Variable Referencing in CMake
The docs for variable referencing can be found here. TL;DR is that if you reference a non-existant variable without passing --warn-uninitialized
, CMake will evaluate the reference to an empty string. That's why the first message
call above prints "foo: ", and why the first call to set(foo ${foo} abc)
(which references foo
in the second argument) will not error (as long as you don't use --warn-uninitialized
)
Lists in CMake
In CMake, lists are just strings where each entry of the list is separated by a semicolon character. semicolons can be escaped with a backslash. You can find the full docs on lists here.
The set
command in CMake
From the CMake docs for the set
command:
Signatures of this command that specify a <value>...
placeholder expect zero or more arguments. Multiple arguments will be joined as a semicolon-separated list to form the actual variable value to be set.
One behaviour of set
that I'm pretty sure is undocumented is that if you pass a single argument that looks like a list, it won't escape the semicolons in that list.
Command Argument Handling in CMake
The docs for command arguments can be found here.
From the section on quoted arguments:
Quoted argument content consists of all text between opening and closing quotes. Both Escape Sequences and Variable References are evaluated. A quoted argument is always given to the command invocation as exactly one argument.
From the section on unquoted arguments:
Unquoted argument content consists of all text in a contiguous block of allowed or escaped characters. Both Escape Sequences and Variable References are evaluated. The resulting value is divided in the same way Lists divide into elements. Each non-empty element is given to the command invocation as an argument. Therefore an unquoted argument may be given to a command invocation as zero or more arguments.
Tying it all Together
When you use set(foo ${foo} abc)
(unquoted argument),
In the first call, ${foo}
is evaluated to an empty string, so it is not passed as an argument, but the string argument abc
is not empty, so it's passed as the first value argument- thus, foo
gets set to abc
.
In the second call, ${foo}
is evaluated to a regular string / a list with one element (abc
), and that string / one element is passed as the first value argument, along with def
as the second value argument. set
joins the two value arguments with a semicolon and the resulting string is assigned to foo
.
In the third call, ${foo}
is evaluated to a list containing abc
and def
, which get passed as the first and second value argument, respectively. Then ghi
as the third argument. set
joins the three value arguments by with a semicolon and the resulting string is assigned to foo
.
When you use set(foo "${foo}" abc)
(quoted argument),
In the first call, "${foo}"
is evaluated to an empty string, but recall that "A quoted argument is always given to the command invocation as exactly one argument.", so that empty string gets passed as the first argument to the set
invocation. The string argument abc
is not empty, so it's passed as the second value argument. set
joins the two value arguments with a semicolon and the resulting string is assigned to foo
. Thus, you get ;abc
(a list where the first value is an empty string).
In the second call, "${foo}"
is evaluated to ;abc
. It's a list, but since it got specified as a quoted argument, the entries of the list are not each passed as individual arguments and ;abc
is passed as the first value argument, along with def
as the second value argument. The two value arguments are joined with a semicolon and the resulting string is assigned to foo
(;abc;foo
- a list with three elements).
A similar thing happens as in the second call in the third call.