How to access enclosing function's arguments from within a macro? - cmake

I want to implement a macro that works like cmake_parse_arguments(PARSE_ARGV ...), i.e. one that accesses the arguments of the enclosing function. Unfortunately, ${ARGN}, ${ARGC} and ${ARGVn} have a special meaning within a macro and they invoke macro parameter string substitutions. I would want to access the ARGN and ARGC and ARGVn variables of the enclosing function's scope.

The way around it is to use an intervening variable reference as an escape. Only the innermost substitution is a macro substition, thus ${${}ARGV0} is macro-substituted to ${${}ARGV0}, then first variable substitution gives ${ARGV0} and the second variable substitution yields the value of first argument in the innermost function scope enclosing the macro.
Thus:
macro (show_arg1)
message("1:${${}ARGV1}")
endmacro()
function (test_function)
show_arg1()
endfunction()
test_function(foo bar baz)
Output:
1:bar
This allows to implement macros that behave like cmake_parse_arguments but do something else while still parsing the arguments of the enclosing functions.
Should the ${} variable substitution ever trigger an error, one could use any unset variable, eg. ${${mgUXpKW8srYnwnSP}ARGV1}.

Related

CMake - How does the if() command treat a symbol? As string or as variable?

I am not sure the CMake if() command will treat a symbol in the condition clause as a variable or a string literal. So I did some experiments.
Script1.cmake
cmake_minimum_required(VERSION 3.15)
set(XXX "YYY") #<========== HERE!!
if(XXX STREQUAL "XXX")
message("condition 1 is true") # If reach here, XXX is treated as string
elseif(XXX STREQUAL "YYY")
message("condition 2 is true") # If reach here, XXX is treated as variable
endif()
The output is:
condition 2 is true
So I come to below conclusion 1.
For a symbol in the condition clause:
If the symbol is defined as a variable before, CMake will treat it as variable and use its value for evaluation.
If the symbol is not defined as a variable before, CMake will treat it literally as a string.
Then I did another experiment.
set(ON "OFF")
if(ON)
message("condition 3 is true") # If reach here, ON is treated as a constant.
else()
message("condition 4 is true") # If reach here. ON is treated as a variable.
endif()
The output is:
condition 3 is true
So, though ON is explicitly defined as a variable, the if command still treat it as a constant of TRUE value. This directly contradicts to my previous conclusion 1.
So how can I know for sure the CMake if() command will treat a symbol as string or variable??
ADD 1 - 11:04 AM 7/11/2019
It seems the if(constant) form precedes other forms of if() statement. (src)
if(<constant>)
True if the constant is 1, ON, YES, TRUE, Y, or a non-zero number.
False if the constant is 0, OFF, NO, FALSE, N, IGNORE, NOTFOUND, the
empty string, or ends in the suffix -NOTFOUND. Named boolean constants
are case-insensitive. If the argument is not one of these specific
constants, it is treated as a variable or string and the following
signature is used.
So for now, I have to refer to the above rule first before applying my conclusion 1.
(This may be an answer, but I am not sure enough yet.)
Welcome to the wilderness of CMake symbol interpretation.
If the symbol exists as a variable, then the expression is evaluated with the value of the variable. Otherwise, the name of the variable (or literal, as you said) is evaluated instead.
The behavior becomes a little more consistent if you add the ${ and } sequences. Then the value of the variable is used in the evaluation every single time. If the variable doesn't exist or has not been assigned a value, then CMake uses several placeholder values that evaluate to "false". These are the values you mentioned in the latter part to your post.
I believe this is done this way for backwards compatibility, which CMake is really good about. For most of the quirky things CMake does, it's usually in the name of backwards compatibility.
As for the inconsistent behavior you mentioned in the "ON" variable, this is probably due to the precedence in which CMake processes the command arguments. I would have to figure that the constants are parsed before the symbol lookup occurs.
So when it comes to knowing/predicting how an if statement will evaluate, my best answer is experience. The CMake source tree and logic is one magnificent, nasty beast.
There's been discussions on adding an alternative language (one with perhaps a functional paradigm), but it's a quite large undertaking.

When should I wrap variables with ${...} in CMake?

I wonder why often variables in CMake are wrapped with a dollar sign and curly brackets. For example, I saw this call in a CMake tutorial.
include_directories(${PROJECT_BINARY_DIR})
But from what I tried, this does the same thing.
include_directories(PROJECT_BINARY_DIR)
When is the wrapping with ${...} needed and what does it mean? Why are variables often wrapped with this even if it makes no difference?
Quoting the CMake documentation:
A variable reference has the form ${variable_name} and is evaluated
inside a Quoted Argument or an Unquoted Argument. A variable reference
is replaced by the value of the variable, or by the empty string if
the variable is not set.
In other words, writing PROJECT_BINARY_DIR refers, literally, to the string "PROJECT_BINARY_DIR". Encapsulating it in ${...} gives you the contents of the variable with the name PROJECT_BINARY_DIR.
Consider:
set(FOO "Hello there!")
message(FOO) # prints FOO
message(${FOO}) # prints Hello there!
As you have probably guessed already, include_directories(PROJECT_BINARY_DIR) simply attempts to add a subdirectory of the name PROJECT_BINARY_DIR to the include directories. On most build systems, if no such directory exists, it will simply ignore the command, which might have tricked you into the impression that it works as expected.
A popular source of confusion comes from the fact that if() does not require explicit dereferencing of variables:
set(FOO TRUE)
if(FOO)
message("Foo was set!")
endif()
Again the documentation explains this behavior:
if(<constant>)
True if the constant is 1, ON, YES, TRUE, Y, or a non-zero number. False if the constant is 0, OFF, NO, FALSE, N, IGNORE, NOTFOUND, the
empty string, or ends in the suffix -NOTFOUND. Named boolean constants
are case-insensitive. If the argument is not one of these constants,
it is treated as a variable.
if(<variable>)
True if the variable is defined to a value that is not a false constant. False otherwise. (Note macro arguments are not variables.)
In particular, one can come up with weird examples like:
unset(BLA)
set(FOO "BLA")
if(FOO)
message("if(<variable>): True")
else()
message("if(<variable>): False")
endif()
if(${FOO})
message("if(<constant>): True")
else()
message("if(<constant>): False")
endif()
Which will take the TRUE branch in the variable case, and the FALSE branch in the constant case. This is due to the fact that in the constant case, CMake will go look for a variable BLA to perform the check on (which is not defined, hence we end up in the FALSE branch).
it's per-case. it's poorly defined. You just have to look it up.
there are other places where you don't have to use {}'s to use the contents of the variable, besides IF. Yikes.

CMake set function

I am trying to learn CMake from a tutorial. I am not crystal clear about how this set function works.
set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
According to CMake document:
set(<variable> <value> [[CACHE <type> <docstring> [FORCE]] | PARENT_SCOPE])
If in this specific case Variable is EXTRA_LIBS, Value is ${EXTRA_LIBS}, then CACHE is MathFunctions?
Correct?
What the command is trying to do is to append the string MathFunctions to whatever value is already stored in the variable EXTRA_LIBS.
To break it down:
set(VARNAME VALUE)
sets the variable VARNAME to the string VALUE. Note that you can assign multiple values to a single variable, which will effectively assign a list value to the variable:
set(VARNAME VALUE1 VALUE2 VALUE3)
To access the value of that variable later, you have to dereference it using ${}, as in
message(${VARNAME})
If the value assigned is a list, you might want to access the different elements separately instead:
foreach(element ${VARNAME})
message(${element})
endforeach()
The command from your question does both the dereferencing of the old value and the assignment of the new value in one line.
A more descriptive way of performing the same assignment is offered by the list command:
list(APPEND EXTRA_LIBS MathFunctions)
On a related note: Note that there is a subtle difference between appending to a list and string concatenation:
set (EXTRA_LIBS "${EXTRA_LIBS} MathFunctions")
Instead of appending, this command will assign a new singular string value to the variable, that is equal to the concatenation of the previous values with the string MathFunctions.

What's the difference between parenthesis $() and curly bracket ${} syntax in Makefile?

Is there any differences in invoking variables with syntax ${var} and $(var)? For instance, in the way the variable will be expanded or anything?
There's no difference – they mean exactly the same (in GNU Make and in POSIX make).
I think that $(round brackets) look tidier, but that's just personal preference.
(Other answers point to the relevant sections of the GNU Make documentation, and note that you shouldn't mix the syntaxes within a single expression)
The Basics of Variable References section from the GNU make documentation state no differences:
To substitute a variable's value, write a dollar sign followed by the
name of the variable in parentheses or braces: either $(foo) or
${foo} is a valid reference to the variable foo.
As already correctly pointed out, there is no difference but be be wary not to mix the two kind of delimiters as it can lead to cryptic errors like in the GNU make example by unomadh.
From the GNU make manual on the Function Call Syntax (emphasis mine):
[…] If the arguments themselves contain other function calls or variable references, it is wisest to use the same kind of delimiters for all the references; write $(subst a,b,$(x)), not $(subst a,b,${x}). This is because it is clearer, and because only one type of delimiter is matched to find the end of the reference.
The ${} style lets you test the make rules in the shell, if you have the corresponding environment variables set, since that is compatible with bash.
Actually, it seems to be fairly different:
, = ,
list = a,b,c
$(info $(subst $(,),-,$(list))_EOL)
$(info $(subst ${,},-,$(list))_EOL)
outputs
a-b-c_EOL
md/init-profile.md:4: *** unterminated variable reference. Stop.
But so far I only found this difference when the variable name into ${...} contains itself a comma. I first thought ${...} was expanding the comma not as part as the value, but it turns out i'm not able to hack it this way. I still don't understand this... If anyone had an explanation, I'd be happy to know !
It makes a difference if the expression contains unbalanced brackets:
${info ${subst ),(,:-)}}
$(info $(subst ),(,:-)))
->
:-(
*** insufficient number of arguments (1) to function 'subst'. Stop.
For variable references, this makes a difference for functions, or for variable names that contain brackets (bad idea)

Why does the DEBUGLOG syntax have 3 dots (...)?

I have seen DEBUGLOG(x,...) syntax. It supports DEBUGLOG(x,...) or DEBUGLOG(x...).
If we alter dots like DEBUGLOG(x,..), it gives an error. Can anyone explain this?
Thanks in advance..........
This is a "variadic" macro, meaning it takes multiple arguments. It's the macro equivalent of a C variadic function like printf. The ... means "and any other arguments beyond here are OK too".
http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
When the macro is invoked, all the
tokens in its argument list after the
last named argument (this macro has
none), including any commas, become
the variable argument. This sequence
of tokens replaces the identifier
VA_ARGS in the macro body wherever it appears.
(I'm not certain why "x..." works without a comma. That's not a form you'd usually see.)